最近各平台都推出行走计步相关的活动,无奈博主的烂手机只有Accelerometer,Light sensor,Proximity sensor,无法记录行走。于是翻查源码,模拟了一个。

要模拟计步器,必须修改android源码,我动到的在\frameworks\native\servises\sensorservice\ 这个目录下。其中SensorService.cpp的onFirstRef()是注册虚拟传感器的代码点。这里我参考了Gravitity sensor、Orientation sensor等的这类虚拟sensor的添加方法。因为这样实现比较简单,不然就要去hardware那一层添加,麻烦度暴增。

而且因为我手机里连gyroscope也没有,所以只好想办法用Light sensor做了替换,以免条件判断不成立。如果你的手机已经有陀螺仪了,就不用替换Light相关的部分了。

void SensorService::onFirstRef()
{
    ALOGD("nuSensorService starting...");
    SensorDevice& dev(SensorDevice::getInstance());

    if (dev.initCheck() == NO_ERROR) {
        sensor_t const* list;
        ssize_t count = dev.getSensorList(&list);
        if (count > 0) {
            ssize_t orientationIndex = -1;
            bool hasGyro = false, hasAccel = false, hasMag = false;
            uint32_t virtualSensorsNeeds =
                    (1< 0) {
                    batchingSupported = true;
                    break;
                }
            }

            if (batchingSupported) {
                // Increase socket buffer size to a max of 100 KB for batching capabilities.
                mSocketBufferSize = MAX_SOCKET_BUFFER_SIZE_BATCHED;
            } else {
                mSocketBufferSize = SOCKET_BUFFER_SIZE_NON_BATCHED;
            }

            // Compare the socketBufferSize value against the system limits and limit
            // it to maxSystemSocketBufferSize if necessary.
            FILE *fp = fopen("/proc/sys/net/core/wmem_max", "r");
            char line[128];
            if (fp != NULL && fgets(line, sizeof(line), fp) != NULL) {
                line[sizeof(line) - 1] = '\0';
                size_t maxSystemSocketBufferSize;
                sscanf(line, "%zu", &maxSystemSocketBufferSize);
                if (mSocketBufferSize > maxSystemSocketBufferSize) {
                    mSocketBufferSize = maxSystemSocketBufferSize;
                }
            }
            if (fp) {
                fclose(fp);
            }

            mWakeLockAcquired = false;
            mLooper = new Looper(false);
            const size_t minBufferSize = SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT;
            mSensorEventBuffer = new sensors_event_t[minBufferSize];
            mSensorEventScratch = new sensors_event_t[minBufferSize];
            mMapFlushEventsToConnections = new SensorEventConnection const * [minBufferSize];
            mCurrentOperatingMode = NORMAL;

            mNextSensorRegIndex = 0;
            for (int i = 0; i < SENSOR_REGISTRATIONS_BUF_SIZE; ++i) {
                mLastNSensorRegistrations.push();
            }

            mInitCheck = NO_ERROR;
            mAckReceiver = new SensorEventAckReceiver(this);
            mAckReceiver->run("SensorEventAckReceiver", PRIORITY_URGENT_DISPLAY);
            run("SensorService", PRIORITY_URGENT_DISPLAY);
        }
    }
}

另外在SensorFusion.cpp的构造函数里也要将gyro替换成light,不然没有陀螺仪的话,Fusion没办法初始化成功。

// 2016-12-22 morrowindxie replaced by light sensor if no gyroscope.
           //if (list[i].type == SENSOR_TYPE_GYROSCOPE) {
             if (list[i].type == SENSOR_TYPE_LIGHT) {

后来发现替换成Light倒有个好处是能借用它来自动判断白天黑夜。例如Lux小于多少认为在黑暗环境下,就停止记步等。不过我的样例里暂时还没用到它,而只是采用了固定的算法:每秒钟记一步,每天根据设定好的开始时间跟结束时间片,累计总的步数这样。

最后贴上StepSensor.cpp和.h的代码,这两个文件都是新增的。这个文件里面包含了两个sensor类,一个是StepCounter,一个是StepDetector。StepCounter根据官方定义,每次手机重启后归零,写的字段是u64.step_counter,为开机以来的总步数。StepDetector根据定义,检测到走步的时候,data[0]置1,否则data[0]置0,然后上报消息。

我的样例里,走路的时间,定死在代码里了(后续想弄个配置文件放到sd卡里,代码读取配置文件来加载走路的时间段,更加灵活一点),简单模拟了白天的随机走路(主要是根据tm结构中的wday跟yday来做简单的随机,具体算法见代码),这样看起来比较真实点,不然每天固定时间开始运动,步数都一样多那也太假了。当然你愿意,改成所有时间都在走路,每秒走3步也无不可,不过小心被一些平台抓到作弊给封号了,哈哈。

StepSensor.cpp

/*
 * Copyright (C) 2016 Morrowind.Xie
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * This project is an adaptation of the original fbvncserver for the iPAQ
 * and Zaurus.
 */

#include 
#include 
#include 

#include 

#include 

#include "StepSensor.h"
#include "SensorDevice.h"
#include "SensorFusion.h"

// Rule sample: 19:xx:yy ~ 21:yy:xx
// Which xx = (tm_wday+3)^2 % 60, yy = (tm_yday+3)^2 % 60.
// To get a random step count for each day.
namespace android {
// ---------------------------------------------------------------------------

StepCounterSensor::StepCounterSensor()
    : mSensorDevice(SensorDevice::getInstance()),
      mSensorFusion(SensorFusion::getInstance())
{
    ALOGD("StepCounterSensor initializing...");
    time_t timeNow;
    time(&timeNow);
    struct tm *localTimeNow = localtime(&timeNow);
    mLastReportTime = *localTimeNow;
    mStepCount = 0;
}

typedef struct {
    int startHour;
    int endHour;
} WALKING;
static WALKING walkingTimes[] = {
    {6, 7}, {8, 11}, {14, 16}, {19, 20}
};
static int nbrSegments = sizeof(walkingTimes)/sizeof(WALKING);
static int countDailyCumulateSteps(struct tm *now) {
    int walkingSeconds = 0;
    int xx = (now->tm_wday+3)*(now->tm_wday+3)%60;
    int yy = (now->tm_yday+3)*(now->tm_yday+3)%60;
    int secStart, secEnd, secNow;
    for(int i=0; itm_hour*60 + now->tm_min)*60 + now->tm_sec;
        if(secEnd < secNow) {
            walkingSeconds += (secEnd-secStart);
        } else if(secStart < secNow) {
            walkingSeconds += (secNow-secStart);
        }
    }
    return walkingSeconds;
}

static int countPeriodSteps(struct tm *beginTime, struct tm *endTime) {
    int beginSteps = countDailyCumulateSteps(beginTime);
    int endSteps = countDailyCumulateSteps(endTime);
    int steps = 0;
    int offsetDays = endTime->tm_yday-beginTime->tm_yday;
    int offsetHours = 0;
    if(offsetDays < 0) {
        offsetDays = 1;
    }
    if(offsetDays > 0) {
        for(int i=0; itm_year+1900, now->tm_mon+1, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec);

        if(now->tm_hour != mLastReportTime.tm_hour
                || now->tm_min != mLastReportTime.tm_min
                || now->tm_sec != mLastReportTime.tm_sec) {
            mStepCount += countPeriodSteps(&mLastReportTime, now);
            mLastReportTime = *now;
            *outEvent = event;
            outEvent->u64.step_counter = (uint64_t)mStepCount;
            outEvent->sensor = '_scs';
            outEvent->type = SENSOR_TYPE_STEP_COUNTER;
            return true;
        }
    }
    return false;
}

status_t StepCounterSensor::activate(void* ident, bool enabled) {
    return mSensorFusion.activate(ident, enabled);
}

status_t StepCounterSensor::setDelay(void* ident, int /*handle*/, int64_t ns) {
    return mSensorFusion.setDelay(ident, ns);
}

Sensor StepCounterSensor::getSensor() const {
    sensor_t hwSensor;
    hwSensor.name       = "Step Counter";
    hwSensor.vendor     = "MorrowindXie";
    hwSensor.version    = 1;
    hwSensor.handle     = '_scs';
    hwSensor.type       = SENSOR_TYPE_STEP_COUNTER;
    hwSensor.maxRange   = 90000000.0f;
    hwSensor.resolution = 1.0f;
    hwSensor.power      = mSensorFusion.getPowerUsage();
    hwSensor.minDelay   = mSensorFusion.getMinDelay();
    Sensor sensor(&hwSensor);
    return sensor;
}

// ---------------------------------------------------------------------------

StepDetectorSensor::StepDetectorSensor()
    : mSensorDevice(SensorDevice::getInstance()),
      mSensorFusion(SensorFusion::getInstance())
{
    ALOGD("StepDetectorSensor initializing...");
    mLastReportSeconds = 0;
}

bool StepDetectorSensor::process(sensors_event_t* outEvent,
        const sensors_event_t& event)
{
    if(event.type == SENSOR_TYPE_ACCELEROMETER) {
        *outEvent = event;
        outEvent->data[0] = 0.0f;
        outEvent->sensor = '_sds';
        outEvent->type = SENSOR_TYPE_STEP_DETECTOR;

        time_t timeNow;
        time(&timeNow);
        struct tm *now = localtime(&timeNow);
        //ALOGD("StepCounterSensor process() event.type=%d, now=%d-%d-%d %d:%d:%d\n", event.type,
        //    now->tm_year+1900, now->tm_mon+1, now->tm_mday, now->tm_hour, now->tm_min, now->tm_sec);

        int xx = (now->tm_wday+3)*(now->tm_wday+3)%60;
        int yy = (now->tm_yday+3)*(now->tm_yday+3)%60;
        int secStart, secEnd, secNow;
        for(int i=0; itm_hour*60 + now->tm_min)*60 + now->tm_sec;
            if(secNow > secStart && secNow <= secEnd && secNow != mLastReportSeconds) {
                mLastReportSeconds = secNow;
                outEvent->data[0] = 1.0f;
                break;
            }
        }
        return true;
    }
    return false;
}

status_t StepDetectorSensor::activate(void* ident, bool enabled) {
    return mSensorFusion.activate(ident, enabled);
}

status_t StepDetectorSensor::setDelay(void* ident, int /*handle*/, int64_t ns) {
    return mSensorFusion.setDelay(ident, ns);
}

Sensor StepDetectorSensor::getSensor() const {
    sensor_t hwSensor;
    hwSensor.name       = "Step Detector";
    hwSensor.vendor     = "MorrowindXie";
    hwSensor.version    = 1;
    hwSensor.handle     = '_sds';
    hwSensor.type       = SENSOR_TYPE_STEP_DETECTOR;
    hwSensor.maxRange   = 1.0f;
    hwSensor.resolution = 1.0f;
    hwSensor.power      = mSensorFusion.getPowerUsage();
    hwSensor.minDelay   = mSensorFusion.getMinDelay();
    Sensor sensor(&hwSensor);
    return sensor;
}

// ---------------------------------------------------------------------------
}; // namespace android

StepSensor.h

/*
 * Copyright (C) 2016 Morrowind.Xie
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * This project is an adaptation of the original fbvncserver for the iPAQ
 * and Zaurus.
 */

#ifndef ANDROID_STEP_SENSOR_H
#define ANDROID_STEP_SENSOR_H

#include 
#include 
#include 
#include 

#include "SensorInterface.h"

// ---------------------------------------------------------------------------
namespace android {
// ---------------------------------------------------------------------------

class SensorDevice;
class SensorFusion;

class StepCounterSensor : public SensorInterface {
    SensorDevice& mSensorDevice;
    SensorFusion& mSensorFusion;
    int mStepCount;
    struct tm mLastReportTime;

public:
    StepCounterSensor();
    virtual bool process(sensors_event_t* outEvent,
            const sensors_event_t& event);
    virtual status_t activate(void* ident, bool enabled);
    virtual status_t setDelay(void* ident, int handle, int64_t ns);
    virtual Sensor getSensor() const;
    virtual bool isVirtual() const { return true; }

};

class StepDetectorSensor : public SensorInterface {
    SensorDevice& mSensorDevice;
    SensorFusion& mSensorFusion;
    int mLastReportSeconds;

public:
    StepDetectorSensor();
    virtual bool process(sensors_event_t* outEvent,
            const sensors_event_t& event);
    virtual status_t activate(void* ident, bool enabled);
    virtual status_t setDelay(void* ident, int handle, int64_t ns);
    virtual Sensor getSensor() const;
    virtual bool isVirtual() const { return true; }
};

// ---------------------------------------------------------------------------
}; // namespace android

#endif // ANDROID_STEP_SENSOR_H

代码添加修改完成后,将同目录下的Android.mk文件里,LOCAL_SRC_FILES段增加StepSensor.cpp。然后就可以mm进行模块编译了。

编译结果在out\target\product\%prj_name%\system\lib\libsensorservice.so,还有一个lib64下同名的so库。我的版本是Android6.0,所以有lib64。如果代码比较老,可能只有一个so。

手机root之后,把这两个so对应替换到手机文件系统的/system/lib跟/system/lib64下,然后重启手机就可以工作了。

apk里对应的sensor type是Sensor.TYPE_STEP_COUNTER跟Sensor.TYPE_STEP_DETECTOR。


另外,这个样例里只是虚拟了Step Sensor,人不用真的在走路就能上报计步消息。

其实,也可以通过SensorFusion里的加速度传感器,来计算是否真的在走路,是的话才记步。怎么使用加速度传感器的数据,可以参考原有的GravitySensor.cpp源码。至于通过加速度传感器来判断走路的算法,那就是另外一个大大大话题了。