Step Detector and Step Counters Sensors

In this article by Varun Nagpal, author of the book, Android Sensor Programming By Example, we will focus on learning about the use of step detector and step counter sensors. These sensors are very similar to each other and are used to count the steps. Both the sensors are based on a common hardware sensor, which internally uses accelerometer, but Android still treats them as logically separate sensors. Both of these sensors are highly battery optimized and consume very low power. Now, lets look at each individual sensor in detail.

(For more resources related to this topic, see here.)

In this article by Varun Nagpal, author of the book, Android Sensor Programming By Example, we will focus on learning about the use of step detector and step counter sensors. These sensors are very similar to each other and are used to count the steps. Both the sensors are based on a common hardware sensor, which internally uses accelerometer, but Android still treats them as logically separate sensors. Both of these sensors are highly battery optimized and consume very low power. Now, lets look at each individual sensor in detail.

The step counter sensor

The step counter sensor is used to get the total number of steps taken by the user since the last reboot (power on) of the phone. When the phone is restarted, the value of the step counter sensor is reset to zero. In the onSensorChanged() method, the number of steps is give by event.value[0]; although it's a float value, the fractional part is always zero. The event timestamp represents the time at which the last step was taken. This sensor is especially useful for those applications that don't want to run in the background and maintain the history of steps themselves. This sensor works in batches and in continuous mode. If we specify 0 or no latency in the SensorManager.registerListener() method, then it works in a continuous mode; otherwise, if we specify any latency, then it groups the events in batches and reports them at the specified latency. For prolonged usage of this sensor, it's recommended to use the batch mode, as it saves power. Step counter uses the on-change reporting mode, which means it reports the event as soon as there is change in the value.

The step detector sensor

The step detector sensor triggers an event each time a step is taken by the user. The value reported in the onSensorChanged() method is always one, the fractional part being always zero, and the event timestamp is the time when the user's foot hit the ground. The step detector sensor has very low latency in reporting the steps, which is generally within 1 to 2 seconds. The Step detector sensor has lower accuracy and produces more false positive, as compared to the step counter sensor. The step counter sensor is more accurate, but has more latency in reporting the steps, as it uses this extra time after each step to remove any false positive values. The step detector sensor is recommended for those applications that want to track the steps in real time and want to maintain their own history of each and every step with their timestamp.

Time for action – using the step counter sensor in activity

Now, you will learn how to use the step counter sensor with a simple example. The good thing about the step counter is that, unlike other sensors, your app doesn't need to tell the sensor when to start counting the steps and when to stop counting them. It automatically starts counting as soon as the phone is powered on. For using it, we just have to register the listener with the sensor manager and then unregister it after using it. In the following example, we will show the total number of steps taken by the user since the last reboot (power on) of the phone in the Android activity.

We created a PedometerActivity and implemented it with the SensorEventListener interface, so that it can receive the sensor events. We initiated the SensorManager and Sensor object of the step counter and also checked the sensor availability in the OnCreate() method of the activity. We registered the listener in the onResume() method and unregistered it in the onPause() method as a standard practice. We used a TextView to display the total number of steps taken and update its latest value in the onSensorChanged() method.

public class PedometerActivity extends Activity implements SensorEventListener{

  private SensorManager mSensorManager;
  private Sensor mSensor;
  private boolean isSensorPresent = false;
  private TextView mStepsSinceReboot;

  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pedometer);
        
        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(String.valueOf(event.values[0]));

  }

Time for action – maintaining step history with step detector sensor

The Step counter sensor works well when we have to deal with the total number of steps taken by the user since the last reboot (power on) of the phone. It doesn't solve the purpose when we have to maintain history of each and every step taken by the user. The Step counter sensor may combine some steps and process them together, and it will only update with an aggregated count instead of reporting individual step detail. For such cases, the step detector sensor is the right choice. In our next example, we will use the step detector sensor to store the details of each step taken by the user, and we will show the total number of steps for each day, since the application was installed. Our next example will consist of three major components of Android, namely service, SQLite database, and activity. Android service will be used to listen to all the individual step details using the step counter sensor when the app is in the background. All the individual step details will be stored in the SQLite database and finally the activity will be used to display the list of total number of steps along with dates. Let's look at the each component in detail.

  1. The first component of our example is PedometerListActivity. We created a ListView in the activity to display the step count along with dates. Inside the onCreate() method of PedometerListActivity, we initiated the ListView and ListAdaptor required to populate the list. Another important task that we do in the onCreate() method is starting the service (StepsService.class), which will listen to all the individual steps' events. We also make a call to the getDataForList() method, which is responsible for fetching the data for ListView.
    public class PedometerListActivity extends Activity{
    
      private ListView mSensorListView;
      private ListAdapter mListAdapter;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mSensorListView = (ListView)findViewById(R.id.steps_list);
        
        getDataForList();
        
        mListAdapter = new ListAdapter();
        mSensorListView.setAdapter(mListAdapter);
        
        Intent mStepsIntent = new Intent(getApplicationContext(), StepsService.class);
        startService(mStepsIntent);
        
      }
    
  2. In our example, the DateStepsModel class is used as a POJO (Plain Old Java Object) class, which is a handy way of grouping logical data together, to store the total number of steps and date. We also use the StepsDBHelper class to read and write the steps data in the database (discussed further in the next section). Inside the getDataForList() method, we initiated the object of the StepsDBHelper class and call the readStepsEntries() method of the StepsDBHelper class, which returns ArrayList of the DateStepsModel objects containing the total number of steps along with dates after reading from database. The ListAdapter class is used for populating the values for ListView, which internally uses ArrayList of DateStepsModel as the data source. The individual list item is the string, which is the concatenation of date and the total number of steps.
    class DateStepsModel {
    
        public String mDate;
        public int mStepCount;
      }
    
    private StepsDBHelper mStepsDBHelper;
    private ArrayList<DateStepsModel> mStepCountList;
    
      public void getDataForList()
      {
        mStepsDBHelper = new StepsDBHelper(this);
        mStepCountList = mStepsDBHelper.readStepsEntries();
      }
      
      
      private class ListAdapter extends BaseAdapter{
    
        private TextView mDateStepCountText;
    
        @Override
        public int getCount() {
    
          return mStepCountList.size();
        }
    
        @Override
        public Object getItem(int position) {
    
          return mStepCountList.get(position);
        }
    
        @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);
          }
    
          mDateStepCountText = (TextView)convertView.findViewById(R.id.sensor_name);
          mDateStepCountText.setText(mStepCountList.get(position).mDate + " - Total Steps: " + String.valueOf(mStepCountList.get(position).mStepCount));
    
          return convertView;
        }
      }
    
  3. The second component of our example is StepsService, which runs in the background and listens to the step detector sensor until the app is uninstalled. We implemented this service with the SensorEventListener interface so that it can receive the sensor events. We also initiated theobjects of StepsDBHelper, SensorManager, and the step detector sensor inside the OnCreate() method of the service. We only register the listener when the step detector sensor is available on the device. A point to note here is that we never unregistered the listener because we expect our app to log the step information indefinitely until the app is uninstalled. Both step detector and step counter sensors are very low on battery consumptions and are highly optimized at the hardware level, so if the app really requires, it can use them for longer durations without affecting the battery consumption much. We get a step detector sensor callback in the onSensorChanged() method whenever the operating system detects a step, and from CC: specify, we call the createStepsEntry() method of the StepsDBHelperclass to store the step information in the database.
    public class StepsService extends Service implements SensorEventListener{
    
      private SensorManager mSensorManager;
      private Sensor mStepDetectorSensor;
      private StepsDBHelper mStepsDBHelper;
    
    
      @Override
      public void onCreate() {
        super.onCreate();
    
        mSensorManager = (SensorManager) 
        this.getSystemService(Context.SENSOR_SERVICE);
        if(mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR) != null)
        {
          mStepDetectorSensor = 
    mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
          mSensorManager.registerListener(this, mStepDetectorSensor, SensorManager.SENSOR_DELAY_NORMAL);
          mStepsDBHelper = new StepsDBHelper(this);
        }
      }
    
      @Override
      public int onStartCommand(Intent intent, int flags, int 
      startId) {
        return Service.START_STICKY;
      }
    
      @Override
      public void onSensorChanged(SensorEvent event) {
        mStepsDBHelper.createStepsEntry();
      }
    
  4. The last component of our example is the SQLite database. We created a StepsDBHelper class and extended it from the SQLiteOpenHelper abstract utility class provided by the Android framework to easily manage database operations. In the class, we created a database called StepsDatabase, which is automatically created on the first object creation of the StepsDBHelper class by the OnCreate() method. This database has one table StepsSummary, which consists of only three columns (id, stepscount, and creationdate). The first column, id, is the unique integer identifier for each row of the table and is incremented automatically on creation of every new row. The second column, stepscount, is used to store the total number of steps taken for each date. The third column, creationdate, is used to store the date in the mm/dd/yyyy string format. Inside the createStepsEntry() method, we first check whether there is an existing step count with the current date, and we if find one, then we read the existing step count of the current date and update the step count by incrementing it by 1. If there is no step count with the current date found, then we assume that it is the first step of the current date and we create a new entry in the table with the current date and step count value as 1. The createStepsEntry() method is called from onSensorChanged() of the StepsService class whenever a new step is detected by the step detector sensor.
    public class StepsDBHelper extends SQLiteOpenHelper
    {
    
      private static final int DATABASE_VERSION = 1;
      private static final String DATABASE_NAME = "StepsDatabase";
      private static final String TABLE_STEPS_SUMMARY = "StepsSummary";
      private static final String ID = "id";
      private static final String STEPS_COUNT = "stepscount";
      private static final String CREATION_DATE = "creationdate";//Date format is mm/dd/yyyy
    
    
      private static final String CREATE_TABLE_STEPS_SUMMARY = "CREATE TABLE "
          + TABLE_STEPS_SUMMARY + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + CREATION_DATE + " TEXT,"+ STEPS_COUNT + " INTEGER"+")";
    
    
      StepsDBHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
      }
    
      @Override
      public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_TABLE_STEPS_SUMMARY);
        
      }
    
      public boolean createStepsEntry()
      {
        boolean isDateAlreadyPresent = false;
        boolean createSuccessful = false;
        int currentDateStepCounts = 0;
        Calendar mCalendar = Calendar.getInstance(); 
        String todayDate = 
        String.valueOf(mCalendar.get(Calendar.MONTH))+"/" + 
    String.valueOf(mCalendar.get(Calendar.DAY_OF_MONTH))+"/"+String.valueOf(mCalendar.get(Calendar.YEAR));
        String selectQuery = "SELECT " + STEPS_COUNT + " FROM " 
        + TABLE_STEPS_SUMMARY + " WHERE " + CREATION_DATE +" = 
         '"+ todayDate+"'";
        try {
          
              SQLiteDatabase db = this.getReadableDatabase();
              Cursor c = db.rawQuery(selectQuery, null);
              if (c.moveToFirst()) {
                  do {
                    isDateAlreadyPresent = true;
                    currentDateStepCounts = 
                    c.getInt((c.getColumnIndex(STEPS_COUNT)));
                  } while (c.moveToNext());
              }
              db.close();
          } catch (Exception e) {
          e.printStackTrace();
        }
        
        try {
          SQLiteDatabase db = this.getWritableDatabase();
          ContentValues values = new ContentValues();
          values.put(CREATION_DATE, todayDate);
          if(isDateAlreadyPresent)
          {
            values.put(STEPS_COUNT, ++currentDateStepCounts);
            int row = db.update(TABLE_STEPS_SUMMARY, values, 
             CREATION_DATE +" = '"+ todayDate+"'", null);
            if(row == 1)
            {
              createSuccessful = true;
            }
            db.close();
          }
          else
          {
            values.put(STEPS_COUNT, 1);
            long row = db.insert(TABLE_STEPS_SUMMARY, null, 
            values);
            if(row!=-1)
            {
              createSuccessful = true;
            }
            db.close();
          }
          
        } catch (Exception e) {
          e.printStackTrace();
        }
        return createSuccessful;
      }
    
  5. The readStepsEntries() method is called from PedometerListActivity to display the total number of steps along with the date in the ListView. The readStepsEntries() method reads all the step counts along with their dates from the table and fills the ArrayList of DateStepsModelwhich is used as a data source for populating the ListView in PedometerListActivity.
    public ArrayList<DateStepsModel> readStepsEntries()
      {
        ArrayList<DateStepsModel> mStepCountList = new ArrayList<DateStepsModel>();
        String selectQuery = "SELECT * FROM " + TABLE_STEPS_SUMMARY;
        try {
          
              SQLiteDatabase db = this.getReadableDatabase();
              Cursor c = db.rawQuery(selectQuery, null);
              if (c.moveToFirst()) {
                  do {
                    DateStepsModel mDateStepsModel = new DateStepsModel();
                    mDateStepsModel.mDate = c.getString((c.getColumnIndex(CREATION_DATE)));
                    mDateStepsModel.mStepCount = c.getInt((c.getColumnIndex(STEPS_COUNT)));
                    mStepCountList.add(mDateStepsModel);
                  } while (c.moveToNext());
              }
              db.close();
          } catch (Exception e) {
          e.printStackTrace();
        }
        return mStepCountList;
      } 
    

What just happened?

We created a small pedometer utility app that maintains the step history along with dates using the steps detector sensor. We used PedometerListActivityto display the list of the total number of steps along with their dates. StepsServiceis used to listen to all the steps detected by the step detector sensor in the background. And finally, the StepsDBHelperclass is used to create and update the total step count for each date and to read the total step counts along with dates from the database.

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Android Sensor Programming By Example

Explore Title