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 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 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.
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]));
}
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.
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);
}
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;
}
}
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();
}
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;
}
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;
}
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.
Further resources on this subject: