@blife/rn-step-counter 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.kr.md +159 -0
- package/README.md +158 -0
- package/StepCounter.podspec +22 -0
- package/android/README.md +65 -0
- package/android/build.gradle +66 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/java/com/stepcounter/StepCounterModule.kt +190 -0
- package/android/src/main/java/com/stepcounter/StepCounterPackage.kt +53 -0
- package/android/src/main/java/com/stepcounter/services/AccelerometerService.kt +147 -0
- package/android/src/main/java/com/stepcounter/services/SensorListenService.kt +305 -0
- package/android/src/main/java/com/stepcounter/services/StepCounterService.kt +79 -0
- package/android/src/main/java/com/stepcounter/utils/AndroidVersionHelper.kt +33 -0
- package/android/src/main/java/com/stepcounter/utils/SensorFusionMath.kt +104 -0
- package/ios/SOMotionDetecter.h +44 -0
- package/ios/SOMotionDetecter.m +91 -0
- package/ios/StepCounter.h +7 -0
- package/ios/StepCounter.mm +264 -0
- package/lib/module/NativeStepCounter.js +21 -0
- package/lib/module/NativeStepCounter.js.map +1 -0
- package/lib/module/index.js +194 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/jest.setup.d.ts +2 -0
- package/lib/typescript/jest.setup.d.ts.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeStepCounter.d.ts +60 -0
- package/lib/typescript/src/NativeStepCounter.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +80 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +138 -0
- package/src/NativeStepCounter.ts +62 -0
- package/src/index.tsx +263 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
package com.stepcounter
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.hardware.SensorManager
|
|
5
|
+
import android.os.Build.VERSION_CODES
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import androidx.core.content.PermissionChecker.PERMISSION_GRANTED
|
|
8
|
+
import androidx.core.content.PermissionChecker.checkSelfPermission
|
|
9
|
+
import com.facebook.react.bridge.Arguments
|
|
10
|
+
import com.facebook.react.bridge.Promise
|
|
11
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
12
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
13
|
+
import com.facebook.react.bridge.ReactMethod
|
|
14
|
+
import com.facebook.react.bridge.WritableMap
|
|
15
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
|
|
16
|
+
import com.stepcounter.services.AccelerometerService
|
|
17
|
+
import com.stepcounter.services.SensorListenService
|
|
18
|
+
import com.stepcounter.services.StepCounterService
|
|
19
|
+
import com.stepcounter.utils.AndroidVersionHelper
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* This class is the native module for the react-native-step-counter package.
|
|
23
|
+
*
|
|
24
|
+
* It is responsible for the communication between the native and the react-native code.
|
|
25
|
+
* @param reactContext The context of the react-native application
|
|
26
|
+
* @property appContext The context of the react-native application from [context][com.facebook.react.bridge.ReactApplicationContext]
|
|
27
|
+
* @property sensorManager The sensor manager that is responsible for the sensor
|
|
28
|
+
* @property stepCounterListener The service that is responsible for the step counter sensor
|
|
29
|
+
* @constructor Creates a new StepCounterModule implements StepCounterSpec
|
|
30
|
+
* @see ReactContextBaseJavaModule
|
|
31
|
+
* @see ReactApplicationContext
|
|
32
|
+
* @see NativeStepCounterSpec
|
|
33
|
+
*/
|
|
34
|
+
class StepCounterModule(
|
|
35
|
+
reactContext: ReactApplicationContext,
|
|
36
|
+
) : NativeStepCounterSpec(reactContext) {
|
|
37
|
+
companion object {
|
|
38
|
+
const val NAME: String = NativeStepCounterSpec.NAME
|
|
39
|
+
private val TAG_NAME: String = StepCounterModule::class.java.name
|
|
40
|
+
private const val STEP_COUNTER = "android.permission.ACTIVITY_RECOGNITION"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private val appContext: ReactApplicationContext = reactContext
|
|
44
|
+
private var sensorManager: SensorManager =
|
|
45
|
+
reactContext.getSystemService(
|
|
46
|
+
Context.SENSOR_SERVICE,
|
|
47
|
+
) as SensorManager
|
|
48
|
+
private val stepsOK: Boolean
|
|
49
|
+
get() = checkSelfPermission(appContext, STEP_COUNTER) == PERMISSION_GRANTED
|
|
50
|
+
private val accelOK: Boolean
|
|
51
|
+
get() = AndroidVersionHelper.isHardwareAccelerometerEnabled(appContext)
|
|
52
|
+
private val supported: Boolean
|
|
53
|
+
get() = AndroidVersionHelper.isHardwareStepCounterEnabled(appContext)
|
|
54
|
+
private val walkingStatus: Boolean
|
|
55
|
+
get() = stepCounterListener !== null
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* gets the step counter listener
|
|
59
|
+
* @return the step counter listener
|
|
60
|
+
* @see SensorListenService
|
|
61
|
+
* @see StepCounterService
|
|
62
|
+
* @see AccelerometerService
|
|
63
|
+
* @see checkSelfPermission
|
|
64
|
+
* @see PERMISSION_GRANTED
|
|
65
|
+
*/
|
|
66
|
+
private var stepCounterListener: SensorListenService? = null
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The method that is called when the module is initialized.
|
|
70
|
+
* It checks the permission and the availability for the step counter sensor and initializes the step counter service.
|
|
71
|
+
*/
|
|
72
|
+
init {
|
|
73
|
+
stepCounterListener =
|
|
74
|
+
if (stepsOK) {
|
|
75
|
+
StepCounterService(this, sensorManager)
|
|
76
|
+
} else {
|
|
77
|
+
AccelerometerService(this, sensorManager)
|
|
78
|
+
}
|
|
79
|
+
appContext.addLifecycleEventListener(stepCounterListener)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The method ask if the step counter sensor is supported.
|
|
84
|
+
* @param promise the promise that is used to return the result to the react-native code
|
|
85
|
+
* @see Promise.resolve
|
|
86
|
+
* @see VERSION_CODES.ECLAIR
|
|
87
|
+
* @see VERSION_CODES.KITKAT
|
|
88
|
+
* @see WritableMap
|
|
89
|
+
*/
|
|
90
|
+
@ReactMethod
|
|
91
|
+
override fun isStepCountingSupported(promise: Promise) {
|
|
92
|
+
Log.d(TAG_NAME, "hardware_step_counter? $supported")
|
|
93
|
+
Log.d(TAG_NAME, "step_counter granted? $stepsOK")
|
|
94
|
+
Log.d(TAG_NAME, "accelerometer granted? $accelOK")
|
|
95
|
+
sendDeviceEvent("stepDetected", walkingStatus)
|
|
96
|
+
promise.resolve(
|
|
97
|
+
Arguments.createMap().apply {
|
|
98
|
+
putBoolean("supported", supported)
|
|
99
|
+
putBoolean("granted", stepsOK || accelOK)
|
|
100
|
+
putBoolean("working", walkingStatus)
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Start the step counter sensor.
|
|
107
|
+
* @param from the number of steps to start from
|
|
108
|
+
*/
|
|
109
|
+
@ReactMethod
|
|
110
|
+
override fun startStepCounterUpdate(from: Double) {
|
|
111
|
+
val sessionStartMillis = normalizeStartTimestampToMillis(from)
|
|
112
|
+
stepCounterListener = stepCounterListener ?: if (stepsOK) {
|
|
113
|
+
StepCounterService(this, sensorManager)
|
|
114
|
+
} else {
|
|
115
|
+
AccelerometerService(this, sensorManager)
|
|
116
|
+
}
|
|
117
|
+
Log.d(TAG_NAME, "startStepCounterUpdate")
|
|
118
|
+
stepCounterListener!!.startService(sessionStartMillis)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Stop the step counter sensor.
|
|
123
|
+
* @return Nothing.
|
|
124
|
+
*/
|
|
125
|
+
@ReactMethod
|
|
126
|
+
override fun stopStepCounterUpdate() {
|
|
127
|
+
Log.d(TAG_NAME, "stopStepCounterUpdate")
|
|
128
|
+
stepCounterListener!!.stopService()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Keep: Required for RN built in Event Emitter Support.
|
|
133
|
+
* @param eventName the name of the event. usually "stepCounterUpdate".
|
|
134
|
+
*/
|
|
135
|
+
@ReactMethod
|
|
136
|
+
override fun addListener(eventName: String) {
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Keep: Required for RN built in Event Emitter Support.
|
|
141
|
+
* @param count the number of listeners to remove.
|
|
142
|
+
* not implemented.
|
|
143
|
+
*/
|
|
144
|
+
@ReactMethod
|
|
145
|
+
override fun removeListeners(count: Double) {
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* StepCounterPackage requires this property for the module.
|
|
150
|
+
* @return the name of the module. usually "StepCounter".
|
|
151
|
+
*/
|
|
152
|
+
override fun getName(): String = NAME
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Send the step counter update event to the react-native code.
|
|
156
|
+
* @param eventPayload the object that contains information about the step counter update.
|
|
157
|
+
* @return Nothing.
|
|
158
|
+
* @see WritableMap
|
|
159
|
+
* @see RCTDeviceEventEmitter
|
|
160
|
+
* @see com.facebook.react.modules.core.DeviceEventManagerModule
|
|
161
|
+
* @throws RuntimeException if the event emitter is not initialized.
|
|
162
|
+
*/
|
|
163
|
+
fun sendDeviceEvent(
|
|
164
|
+
eventType: String,
|
|
165
|
+
eventPayload: Any,
|
|
166
|
+
) {
|
|
167
|
+
try {
|
|
168
|
+
appContext
|
|
169
|
+
.getJSModule(RCTDeviceEventEmitter::class.java)
|
|
170
|
+
.emit(eventName = "$NAME.$eventType", data = eventPayload)
|
|
171
|
+
} catch (e: RuntimeException) {
|
|
172
|
+
e.message?.let { Log.e(TAG_NAME, it) }
|
|
173
|
+
Log.e(TAG_NAME, eventType, e)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* JS passes ms; accept seconds too for backward-compat.
|
|
179
|
+
*/
|
|
180
|
+
private fun normalizeStartTimestampToMillis(from: Double): Long {
|
|
181
|
+
if (!from.isFinite() || from <= 0) {
|
|
182
|
+
return System.currentTimeMillis()
|
|
183
|
+
}
|
|
184
|
+
return if (from >= 1_000_000_000_000.0) {
|
|
185
|
+
from.toLong()
|
|
186
|
+
} else {
|
|
187
|
+
(from * 1000).toLong()
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
package com.stepcounter
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* This class is responsible for the creation of the ReactNative package.
|
|
11
|
+
* @see com.facebook.react.ReactPackage
|
|
12
|
+
* @see BaseReactPackage
|
|
13
|
+
* @see ReactApplicationContext
|
|
14
|
+
* @see ReactModuleInfo
|
|
15
|
+
* @see ReactModuleInfoProvider
|
|
16
|
+
*/
|
|
17
|
+
class StepCounterPackage : BaseReactPackage() {
|
|
18
|
+
/**
|
|
19
|
+
* This method is responsible for the creation of the ReactNative module.
|
|
20
|
+
* @param name The name of the module
|
|
21
|
+
* @param reactContext The context of the react-native application
|
|
22
|
+
* @return [com.facebook.react.module.model.ReactModuleInfo] ]The ReactNative module
|
|
23
|
+
* @see NativeModule
|
|
24
|
+
* @see ReactApplicationContext
|
|
25
|
+
* @see StepCounterModule
|
|
26
|
+
* @see StepCounterModule.NAME
|
|
27
|
+
*/
|
|
28
|
+
override fun getModule(
|
|
29
|
+
name: String,
|
|
30
|
+
reactContext: ReactApplicationContext,
|
|
31
|
+
): NativeModule? = if (name == StepCounterModule.NAME) StepCounterModule(reactContext) else null
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* This method is responsible for the creation of the ReactNative module info provider.
|
|
35
|
+
* @return The ReactNative module info provider
|
|
36
|
+
* @see ReactModuleInfoProvider
|
|
37
|
+
* @see ReactModuleInfo
|
|
38
|
+
*/
|
|
39
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider =
|
|
40
|
+
ReactModuleInfoProvider {
|
|
41
|
+
val moduleInfo: MutableMap<String, ReactModuleInfo> = HashMap()
|
|
42
|
+
moduleInfo[StepCounterModule.NAME] =
|
|
43
|
+
ReactModuleInfo(
|
|
44
|
+
name = StepCounterModule.NAME,
|
|
45
|
+
className = StepCounterModule.NAME,
|
|
46
|
+
canOverrideExistingModule = false,
|
|
47
|
+
needsEagerInit = false,
|
|
48
|
+
isCxxModule = false,
|
|
49
|
+
isTurboModule = true,
|
|
50
|
+
)
|
|
51
|
+
moduleInfo
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
package com.stepcounter.services
|
|
2
|
+
|
|
3
|
+
import android.hardware.Sensor
|
|
4
|
+
import android.hardware.SensorManager
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import com.stepcounter.StepCounterModule
|
|
7
|
+
import com.stepcounter.utils.SensorFusionMath.dot
|
|
8
|
+
import com.stepcounter.utils.SensorFusionMath.norm
|
|
9
|
+
import com.stepcounter.utils.SensorFusionMath.normalize
|
|
10
|
+
import com.stepcounter.utils.SensorFusionMath.sum
|
|
11
|
+
import kotlin.math.min
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* This class is responsible for listening to the accelerometer sensor.
|
|
15
|
+
* It is used to count the steps of the user.
|
|
16
|
+
* @constructor Creates a new AccelerometerService
|
|
17
|
+
* @param counterModule The module that is responsible for the communication with the react-native layer
|
|
18
|
+
* @param sensorManager The sensor manager that is responsible for the sensor
|
|
19
|
+
*
|
|
20
|
+
* @property sensorTypeString The type of the sensor as a string "Accelerometer"
|
|
21
|
+
* @property sensorType The type of the sensor
|
|
22
|
+
* @property detectedSensor The sensor that is detected
|
|
23
|
+
* @property currentSteps The current steps
|
|
24
|
+
* @property velocityRingCounter The velocity ring counter
|
|
25
|
+
* @property accelRingCounter The acceleration ring counter
|
|
26
|
+
* @property oldVelocityEstimate The old velocity estimate
|
|
27
|
+
* @property startDate The last step time in milliseconds
|
|
28
|
+
* @property accelRingX The acceleration ring for the x-axis
|
|
29
|
+
* @property accelRingY The acceleration ring for the y-axis
|
|
30
|
+
* @property accelRingZ The acceleration ring for the z-axis
|
|
31
|
+
* @property velocityRing The velocity ring
|
|
32
|
+
*
|
|
33
|
+
* @see SensorListenService
|
|
34
|
+
* @see Sensor
|
|
35
|
+
* @see SensorManager
|
|
36
|
+
* @see StepCounterModule
|
|
37
|
+
* @see SensorManager.SENSOR_DELAY_NORMAL
|
|
38
|
+
* @see Sensor.TYPE_ACCELEROMETER
|
|
39
|
+
*/
|
|
40
|
+
class AccelerometerService(
|
|
41
|
+
counterModule: StepCounterModule,
|
|
42
|
+
sensorManager: SensorManager,
|
|
43
|
+
) : SensorListenService(counterModule, sensorManager) {
|
|
44
|
+
override val sensorTypeString = "Accelerometer"
|
|
45
|
+
override val sensorType = Sensor.TYPE_ACCELEROMETER
|
|
46
|
+
override val detectedSensor: Sensor? = sensorManager.getDefaultSensor(sensorType)
|
|
47
|
+
override var currentSteps: Double = 0.0
|
|
48
|
+
private var velocityRingCounter: Int = 0
|
|
49
|
+
private var accelRingCounter: Int = 0
|
|
50
|
+
private var oldVelocityEstimate: Float = 0f
|
|
51
|
+
private var lastStepTimeNs: Long = 0
|
|
52
|
+
|
|
53
|
+
// We want to keep a history of values to do a rolling average of the current
|
|
54
|
+
private val accelRingX = FloatArray(ACCEL_RING_SIZE)
|
|
55
|
+
private val accelRingY = FloatArray(ACCEL_RING_SIZE)
|
|
56
|
+
private val accelRingZ = FloatArray(ACCEL_RING_SIZE)
|
|
57
|
+
|
|
58
|
+
// We want to keep a history of values to do a rolling average of the current
|
|
59
|
+
private val velocityRing = FloatArray(VELOCITY_RING_SIZE)
|
|
60
|
+
|
|
61
|
+
override fun resetSessionState() {
|
|
62
|
+
currentSteps = 0.0
|
|
63
|
+
velocityRingCounter = 0
|
|
64
|
+
accelRingCounter = 0
|
|
65
|
+
oldVelocityEstimate = 0f
|
|
66
|
+
lastStepTimeNs = 0
|
|
67
|
+
accelRingX.fill(0f)
|
|
68
|
+
accelRingY.fill(0f)
|
|
69
|
+
accelRingZ.fill(0f)
|
|
70
|
+
velocityRing.fill(0f)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* This function is responsible for updating the current steps.
|
|
75
|
+
* All [values][android.hardware.SensorEvent.values] are in SI units (m/s^2)
|
|
76
|
+
*
|
|
77
|
+
* - values[0]: Acceleration minus Gx on the x-axis
|
|
78
|
+
* - values[1]: Acceleration minus Gy on the y-axis
|
|
79
|
+
* - values[2]: Acceleration minus Gz on the z-axis
|
|
80
|
+
*
|
|
81
|
+
* @param eventData array of vector.
|
|
82
|
+
* @return If the current step is just updated
|
|
83
|
+
* @see android.hardware.SensorEvent
|
|
84
|
+
* @see android.hardware.SensorEvent.values
|
|
85
|
+
* @see android.hardware.SensorEvent.timestamp
|
|
86
|
+
*/
|
|
87
|
+
override fun updateCurrentSteps(eventData: FloatArray): Boolean {
|
|
88
|
+
val timeNs = System.nanoTime()
|
|
89
|
+
// First step is to update our guess of where the global z vector is.
|
|
90
|
+
accelRingCounter++
|
|
91
|
+
// We keep a rolling average of the last 50 values
|
|
92
|
+
accelRingX[accelRingCounter % ACCEL_RING_SIZE] = eventData[0]
|
|
93
|
+
accelRingY[accelRingCounter % ACCEL_RING_SIZE] = eventData[1]
|
|
94
|
+
accelRingZ[accelRingCounter % ACCEL_RING_SIZE] = eventData[2]
|
|
95
|
+
// Next we'll calculate the average of the last 50 vectors in the ring
|
|
96
|
+
val gravity: FloatArray =
|
|
97
|
+
floatArrayOf(
|
|
98
|
+
sum(accelRingX) / min(accelRingCounter, ACCEL_RING_SIZE),
|
|
99
|
+
sum(accelRingY) / min(accelRingCounter, ACCEL_RING_SIZE),
|
|
100
|
+
sum(accelRingZ) / min(accelRingCounter, ACCEL_RING_SIZE),
|
|
101
|
+
)
|
|
102
|
+
// Next step is to figure out the component of the current acceleration
|
|
103
|
+
// in the direction of world_z and subtract gravity's contribution
|
|
104
|
+
val currentZ: Float = dot(normalize(gravity), eventData) - norm(gravity)
|
|
105
|
+
// Now we just need to update our estimate of the velocity
|
|
106
|
+
velocityRingCounter++
|
|
107
|
+
// We keep a rolling average of the last 10 values
|
|
108
|
+
velocityRing[velocityRingCounter % VELOCITY_RING_SIZE] = currentZ
|
|
109
|
+
// Calculate the average of the last 10 values
|
|
110
|
+
val velocityEstimate: Float = sum(velocityRing)
|
|
111
|
+
// If the velocity estimate is greater than the threshold and the previous
|
|
112
|
+
val isWalkingOrRunning: Boolean =
|
|
113
|
+
STEP_THRESHOLD in oldVelocityEstimate..<velocityEstimate &&
|
|
114
|
+
timeNs - lastStepTimeNs > STEP_DELAY_NS
|
|
115
|
+
if (isWalkingOrRunning) {
|
|
116
|
+
currentSteps = currentSteps.plus(1)
|
|
117
|
+
Log.d(TAG_NAME, "STATUS: $currentSteps steps. TIMESTAMP: $timeNs")
|
|
118
|
+
lastStepTimeNs = timeNs
|
|
119
|
+
}
|
|
120
|
+
oldVelocityEstimate = velocityEstimate
|
|
121
|
+
return isWalkingOrRunning
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
companion object {
|
|
125
|
+
/**
|
|
126
|
+
* The delay between steps in nanoseconds
|
|
127
|
+
*/
|
|
128
|
+
private const val STEP_DELAY_NS = 250000000 // 250ms
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The size of the acceleration sensor data ring
|
|
132
|
+
*/
|
|
133
|
+
private const val ACCEL_RING_SIZE = 50
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The size of the acceleration's velocity ring
|
|
137
|
+
*/
|
|
138
|
+
private const val VELOCITY_RING_SIZE = 10
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The minimum acceleration that is considered a step
|
|
142
|
+
*/
|
|
143
|
+
private const val STEP_THRESHOLD = 12f // 4f-16f
|
|
144
|
+
|
|
145
|
+
val TAG_NAME: String = AccelerometerService::class.java.name
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
package com.stepcounter.services
|
|
2
|
+
|
|
3
|
+
import android.hardware.Sensor
|
|
4
|
+
import android.hardware.SensorEvent
|
|
5
|
+
import android.hardware.SensorEventListener
|
|
6
|
+
import android.hardware.SensorManager
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import com.facebook.react.bridge.Arguments
|
|
9
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
10
|
+
import com.facebook.react.bridge.WritableMap
|
|
11
|
+
import com.stepcounter.StepCounterModule
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* the base class for sensor listen service
|
|
15
|
+
* @param counterModule the step counter module
|
|
16
|
+
* @param sensorManager the sensor manager
|
|
17
|
+
* @see StepCounterModule
|
|
18
|
+
* @see Sensor
|
|
19
|
+
* @see SensorEvent
|
|
20
|
+
* @see <a href="https://developer.android.com/reference/android/hardware/SensorEventListener">SensorEventListener</a>
|
|
21
|
+
* @see <a href="https://developer.android.com/reference/android/hardware/SensorManager">SensorManager</a>
|
|
22
|
+
* @see <a href="https://reactnative.dev/docs/native-modules-android#listening-to-lifecycle-events">LifecycleEventListener: document</a>
|
|
23
|
+
* @see <a href="https://github.com/facebook/react-native/blob/main/ReactAndroid/src/main/java/com/facebook/react/bridge/LifecycleEventListener.java">LifecycleEventListener: original file</a>
|
|
24
|
+
*/
|
|
25
|
+
abstract class SensorListenService(
|
|
26
|
+
private val counterModule: StepCounterModule,
|
|
27
|
+
private val sensorManager: SensorManager,
|
|
28
|
+
) : SensorEventListener,
|
|
29
|
+
LifecycleEventListener {
|
|
30
|
+
/**
|
|
31
|
+
* the accelerometer sensor type
|
|
32
|
+
* [TYPE_ACCELEROMETER][Sensor.TYPE_ACCELEROMETER]: 1<br/>
|
|
33
|
+
*
|
|
34
|
+
* the step counter sensor type
|
|
35
|
+
* [TYPE_STEP_COUNTER][Sensor.TYPE_STEP_COUNTER]: 19<br/>
|
|
36
|
+
*/
|
|
37
|
+
abstract val sensorType: Int
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The `rate` of [sensor events][android.hardware.SensorEvent] are
|
|
41
|
+
* delivered at. This is only a hint to the system. Events may be received faster or
|
|
42
|
+
* slower than the specified rate. Usually events are received faster.
|
|
43
|
+
*
|
|
44
|
+
* [samplingPeriodUs] – The desired delay between two consecutive events in microseconds.
|
|
45
|
+
* This is only a hint to the system. Events may be received faster or slower than the specified rate.
|
|
46
|
+
* Typically, events are received faster. Can be one of [SENSOR_DELAY_NORMAL][SensorManager.SENSOR_DELAY_NORMAL], [SENSOR_DELAY_UI][SensorManager.SENSOR_DELAY_UI],
|
|
47
|
+
* [SENSOR_DELAY_GAME][SensorManager.SENSOR_DELAY_GAME], or [SENSOR_DELAY_FASTEST][SensorManager.SENSOR_DELAY_FASTEST]
|
|
48
|
+
* or, the desired delay between events in microseconds. Specifying the delay in microseconds only works
|
|
49
|
+
* from Android 2.3 (API level 9) onwards. For earlier releases, you must use one of the `SENSOR_DELAY_*` constants.
|
|
50
|
+
*
|
|
51
|
+
* @see
|
|
52
|
+
* The following are the constants for the sampling period in microseconds
|
|
53
|
+
* <pre class="prettyprint kotlin">
|
|
54
|
+
* private fun getDelay(rate: Int): Int {
|
|
55
|
+
* return when (rate) {
|
|
56
|
+
* SensorManager.SENSOR_DELAY_FASTEST -> 0
|
|
57
|
+
* SensorManager.SENSOR_DELAY_GAME -> 20000
|
|
58
|
+
* SensorManager.SENSOR_DELAY_UI -> 66667
|
|
59
|
+
* SensorManager.SENSOR_DELAY_NORMAL -> 200000
|
|
60
|
+
* else -> rate
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* </pre>
|
|
64
|
+
*/
|
|
65
|
+
private val samplingPeriodUs
|
|
66
|
+
get() =
|
|
67
|
+
when (sensorType) {
|
|
68
|
+
Sensor.TYPE_ACCELEROMETER -> SensorManager.SENSOR_DELAY_GAME
|
|
69
|
+
Sensor.TYPE_STEP_COUNTER -> SensorManager.SENSOR_DELAY_NORMAL
|
|
70
|
+
else -> SensorManager.SENSOR_DELAY_UI
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @return if the [sensor][detectedSensor] is
|
|
75
|
+
* [accelerometer][Sensor.TYPE_ACCELEROMETER],
|
|
76
|
+
* "Accelerometer"
|
|
77
|
+
* if it's [stepCounter][Sensor.TYPE_STEP_COUNTER],
|
|
78
|
+
* "Step Counter".
|
|
79
|
+
*/
|
|
80
|
+
abstract val sensorTypeString: String
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* the detected sensor
|
|
84
|
+
* @see SensorManager.getDefaultSensor
|
|
85
|
+
* @see SensorManager.getSensorList
|
|
86
|
+
* @see Sensor.TYPE_ACCELEROMETER
|
|
87
|
+
* @see Sensor.TYPE_STEP_COUNTER
|
|
88
|
+
*/
|
|
89
|
+
abstract val detectedSensor: Sensor?
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* the current steps data of the user
|
|
93
|
+
* @see currentSteps
|
|
94
|
+
* @see distance
|
|
95
|
+
* @see endDate
|
|
96
|
+
* @see sensorTypeString
|
|
97
|
+
* @see calories
|
|
98
|
+
* @see WritableMap
|
|
99
|
+
* @see Arguments.createMap
|
|
100
|
+
*/
|
|
101
|
+
val stepsParamsMap: WritableMap
|
|
102
|
+
get() =
|
|
103
|
+
Arguments.createMap().apply {
|
|
104
|
+
putNumber("steps", currentSteps)
|
|
105
|
+
putNumber("distance", distance)
|
|
106
|
+
putNumber("startDate", startDate)
|
|
107
|
+
putNumber("endDate", endDate)
|
|
108
|
+
putString("counterType", sensorTypeString)
|
|
109
|
+
putNumber("calories", calories)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
val stepsSensorInfo: WritableMap
|
|
113
|
+
get() =
|
|
114
|
+
Arguments.createMap().apply {
|
|
115
|
+
putNumber("minDelay", detectedSensor!!.minDelay)
|
|
116
|
+
putNumber("maxDelay", detectedSensor!!.maxDelay)
|
|
117
|
+
putString("name", detectedSensor!!.name)
|
|
118
|
+
putString("vendor", detectedSensor!!.vendor)
|
|
119
|
+
putNumber("power", detectedSensor!!.power)
|
|
120
|
+
putNumber("resolution", detectedSensor!!.resolution)
|
|
121
|
+
putBoolean("wakeUpSensor", detectedSensor!!.isWakeUpSensor)
|
|
122
|
+
putBoolean("additionalInfoSupported", detectedSensor!!.isAdditionalInfoSupported)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Number of in-database-saved calories.
|
|
127
|
+
* 0.045 is the average calories burned per step.
|
|
128
|
+
* to get more accurate result, you can use this formula:
|
|
129
|
+
* 0.045 * weight * distance,
|
|
130
|
+
* but then you need to get the weight of the user. so it needs some permission.
|
|
131
|
+
*/
|
|
132
|
+
private val calories: Double
|
|
133
|
+
get() = currentSteps * 0.045
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Distance of in-database-saved steps
|
|
137
|
+
*/
|
|
138
|
+
private val distance: Double
|
|
139
|
+
get() = currentSteps * 0.762
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Number of steps counted since service start
|
|
143
|
+
*/
|
|
144
|
+
abstract var currentSteps: Double
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Reset in-memory session state before a new counting session starts.
|
|
148
|
+
*/
|
|
149
|
+
protected open fun resetSessionState() {
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Start date of the step counting. UTC milliseconds
|
|
154
|
+
*/
|
|
155
|
+
private var startDate: Long = System.currentTimeMillis()
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* End date of the step counting. UTC milliseconds
|
|
159
|
+
*/
|
|
160
|
+
private val endDate: Long
|
|
161
|
+
get() = System.currentTimeMillis()
|
|
162
|
+
|
|
163
|
+
private val sensorDelay: Int
|
|
164
|
+
get() =
|
|
165
|
+
when (samplingPeriodUs) {
|
|
166
|
+
SensorManager.SENSOR_DELAY_FASTEST -> 0
|
|
167
|
+
SensorManager.SENSOR_DELAY_GAME -> 20000
|
|
168
|
+
SensorManager.SENSOR_DELAY_UI -> 66667
|
|
169
|
+
SensorManager.SENSOR_DELAY_NORMAL -> 200000
|
|
170
|
+
else -> samplingPeriodUs
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* this class is not implemented Service class now, but made it work so
|
|
175
|
+
* @see android.content.Context.startService
|
|
176
|
+
* @see android.content.Context.stopService
|
|
177
|
+
* @see SensorManager.registerListener
|
|
178
|
+
*/
|
|
179
|
+
fun startService(startDateMillis: Long? = null) {
|
|
180
|
+
startDate =
|
|
181
|
+
startDateMillis?.takeIf { it > 0 } ?: System.currentTimeMillis()
|
|
182
|
+
resetSessionState()
|
|
183
|
+
counterModule.sendDeviceEvent("stepsSensorInfo", stepsSensorInfo)
|
|
184
|
+
Log.d(TAG_NAME, "SensorManager.stepsSensorInfo: $stepsSensorInfo")
|
|
185
|
+
sensorManager.registerListener(this, detectedSensor, samplingPeriodUs)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* this class is not implemented Service class now, but made it work so
|
|
190
|
+
* @see android.content.Context.startService
|
|
191
|
+
* @see android.content.Context.stopService
|
|
192
|
+
* @see SensorManager.unregisterListener
|
|
193
|
+
*/
|
|
194
|
+
fun stopService() {
|
|
195
|
+
Log.d(TAG_NAME, "SensorListenService.stopService")
|
|
196
|
+
sensorManager.unregisterListener(this)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Called when there is a new sensor event. Note that "on changed"
|
|
201
|
+
* is somewhat of a misnomer, as this will also be called if we have a
|
|
202
|
+
* new reading from a sensor with the exact same sensor values (but a
|
|
203
|
+
* newer timestamp).
|
|
204
|
+
* See [SensorManager][android.hardware.SensorManager]
|
|
205
|
+
* for details on possible sensor types.
|
|
206
|
+
* See also [SensorEvent][android.hardware.SensorEvent].
|
|
207
|
+
*
|
|
208
|
+
* **NOTE1:** The application doesn't own the [event][android.hardware.SensorEvent]
|
|
209
|
+
* object passed as a parameter and therefore cannot hold on to it.
|
|
210
|
+
* The object may be part of an internal pool and may be reused by
|
|
211
|
+
* the framework. If you need to hold on to the event, you must make a copy.
|
|
212
|
+
*
|
|
213
|
+
* **NOTE2:** Since the timestamp delivered from JavaScript is based on milliseconds,
|
|
214
|
+
* mistakes can occur if the timestamp of sensor events recorded every moment in nanoseconds is delivered as it is.
|
|
215
|
+
* Thus, we convert timestamp (nanoseconds recorded in sensor events) using [toMillis][java.util.concurrent.TimeUnit.toMillis] method,
|
|
216
|
+
* or call [currentTimeInMillis][System.currentTimeMillis] method according to the function execution time.
|
|
217
|
+
*
|
|
218
|
+
* @param event the [SensorEvent][android.hardware.SensorEvent].
|
|
219
|
+
* @see <a href="https://developer.android.com/reference/android/hardware/SensorEvent">Hardware Activity Sensors</a>
|
|
220
|
+
* @see <a href="https://developer.android.com/reference/android/hardware/SensorEvent#sensor.type_accelerometer:">Accelerometer Sensor Event</a>
|
|
221
|
+
* @see <a href="https://developer.android.com/reference/android/hardware/Sensor#TYPE_STEP_COUNTER">Step Counter Sensor Event</a>
|
|
222
|
+
* @see <a href="https://developer.android.com/reference/android/hardware/Sensor#REPORTING_MODE_ON_CHANGE">Reporting Mode On Change</a>
|
|
223
|
+
*/
|
|
224
|
+
override fun onSensorChanged(event: SensorEvent?) {
|
|
225
|
+
if (event?.sensor == null ||
|
|
226
|
+
event.sensor != detectedSensor ||
|
|
227
|
+
event.sensor.type != sensorType ||
|
|
228
|
+
detectedSensor?.type != event.sensor.type
|
|
229
|
+
) {
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
if (updateCurrentSteps(event.values)) {
|
|
233
|
+
counterModule.sendDeviceEvent("stepCounterUpdate", stepsParamsMap)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* abstract method to update the current steps
|
|
239
|
+
* implemented in [StepCounterService] and [AccelerometerService]
|
|
240
|
+
* with different motion sensor handling algorithm.
|
|
241
|
+
* @param eventData the detected vector of sensor event
|
|
242
|
+
* @return if the current steps is updated, return true, otherwise return false
|
|
243
|
+
*/
|
|
244
|
+
abstract fun updateCurrentSteps(eventData: FloatArray): Boolean
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Called when the accuracy of the registered sensor has changed.
|
|
248
|
+
*
|
|
249
|
+
* Unlike onSensorChanged(), this is only called when this accuracy value changes.
|
|
250
|
+
* See the SENSOR_STATUS_* constants in
|
|
251
|
+
* [SensorManager][android.hardware.SensorManager] for details.
|
|
252
|
+
*
|
|
253
|
+
* @param sensor The [Sensor][android.hardware.Sensor] that has accuracy changed.
|
|
254
|
+
* @param accuracy The new accuracy of this sensor, one of
|
|
255
|
+
* `SensorManager.SENSOR_STATUS_*`
|
|
256
|
+
*/
|
|
257
|
+
override fun onAccuracyChanged(
|
|
258
|
+
sensor: Sensor?,
|
|
259
|
+
accuracy: Int,
|
|
260
|
+
) {
|
|
261
|
+
Log.d(TAG_NAME, "onAccuracyChanged.accuracy $accuracy")
|
|
262
|
+
Log.d(TAG_NAME, "onAccuracyChanged.sensor: $sensor")
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Called either when the host activity receives a resume event or
|
|
267
|
+
* if the native module that implements this is initialized while the host activity is already
|
|
268
|
+
* resumed. Always called for the most current activity.
|
|
269
|
+
* @see Activity.onResume
|
|
270
|
+
*/
|
|
271
|
+
override fun onHostResume() {
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Called when host activity receives pause event.
|
|
276
|
+
* Always called for the most current activity.
|
|
277
|
+
* @see Activity.onPause
|
|
278
|
+
*/
|
|
279
|
+
override fun onHostPause() {
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Called when host activity receives destroy event.
|
|
284
|
+
* Only called for the last React activity to be destroyed.
|
|
285
|
+
* @see Activity.onDestroy
|
|
286
|
+
*/
|
|
287
|
+
override fun onHostDestroy() {
|
|
288
|
+
this.stopService()
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
companion object {
|
|
292
|
+
val TAG_NAME: String = SensorListenService::class.java.name
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private fun WritableMap.putNumber(
|
|
297
|
+
key: String,
|
|
298
|
+
number: Number,
|
|
299
|
+
) {
|
|
300
|
+
when (number) {
|
|
301
|
+
is Double -> putDouble(key, number)
|
|
302
|
+
is Int -> putInt(key, number)
|
|
303
|
+
else -> putDouble(key, number.toDouble())
|
|
304
|
+
}
|
|
305
|
+
}
|