Implementing Motion Sensors in Android Application
Android Sensor Framework provides access to 13+ sensor types through a single SensorManager. Accelerometer and gyroscope are hardware. TYPE_LINEAR_ACCELERATION, TYPE_ROTATION_VECTOR, TYPE_GRAVITY are virtual: computed from hardware through sensor fusion in firmware. The difference is that virtual sensors consume more CPU at high frequency and may be unavailable on budget devices.
Registration and Lifecycle
class SensorViewModel(application: Application) : AndroidViewModel(application) {
private val sensorManager = application.getSystemService(SENSOR_SERVICE) as SensorManager
private val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
private val gyroscope = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE)
private val _sensorData = MutableStateFlow<SensorData>(SensorData.Empty)
val sensorData: StateFlow<SensorData> = _sensorData.asStateFlow()
private val sensorEventListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
when (event.sensor.type) {
Sensor.TYPE_ACCELEROMETER -> handleAccelerometer(event.values)
Sensor.TYPE_GYROSCOPE -> handleGyroscope(event.values)
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
fun startListening() {
accelerometer?.let {
sensorManager.registerListener(
sensorEventListener, it,
SensorManager.SENSOR_DELAY_GAME // ~20ms
)
}
}
fun stopListening() {
sensorManager.unregisterListener(sensorEventListener)
}
override fun onCleared() {
stopListening()
}
}
Key point: unregisterListener strictly in onCleared() ViewModel or in onPause() Activity. Forgotten listener works in background, drains battery and can crash application when Activity is destroyed.
Polling Frequencies: Constants and Reality
| Constant | Nominal Delay | Actual Frequency |
|---|---|---|
SENSOR_DELAY_NORMAL |
200 ms | ~5 Hz |
SENSOR_DELAY_UI |
60 ms | ~16 Hz |
SENSOR_DELAY_GAME |
20 ms | ~50 Hz |
SENSOR_DELAY_FASTEST |
0 ms | hardware maximum |
From Android API 9, you can set arbitrary interval in microseconds via registerListener(listener, sensor, samplingPeriodUs). For example, 10000 µs = 100 Hz.
SENSOR_DELAY_FASTEST on Snapdragon 8 Gen 2 yields up to 500 Hz on accelerometer — needed only for specialized applications (vibration analysis, balancing). For most tasks 50–100 Hz is sufficient.
Virtual Sensors and Sensor Fusion
TYPE_ROTATION_VECTOR — device orientation quaternion, computed from accelerometer + gyroscope + magnetometer. More accurate than manual fusion:
val rotationVector = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
// In onSensorChanged:
val rotationMatrix = FloatArray(9)
SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values)
val orientationAngles = FloatArray(3)
SensorManager.getOrientation(rotationMatrix, orientationAngles)
val azimuth = Math.toDegrees(orientationAngles[0].toDouble()) // 0-360° from north
val pitch = Math.toDegrees(orientationAngles[1].toDouble()) // -90 to +90°
val roll = Math.toDegrees(orientationAngles[2].toDouble()) // -180 to +180°
TYPE_LINEAR_ACCELERATION — accelerometer without gravity (analog of userAcceleration in iOS CoreMotion). Use instead of TYPE_ACCELEROMETER when measuring only dynamic acceleration.
Detecting Specific Events
Distinguishing Walking from Driving
val gravity = FloatArray(3)
val linearAccel = FloatArray(3)
val alpha = 0.8f
// In onSensorChanged for TYPE_ACCELEROMETER:
gravity[0] = alpha * gravity[0] + (1 - alpha) * event.values[0]
gravity[1] = alpha * gravity[1] + (1 - alpha) * event.values[1]
gravity[2] = alpha * gravity[2] + (1 - alpha) * event.values[2]
linearAccel[0] = event.values[0] - gravity[0]
linearAccel[1] = event.values[1] - gravity[1]
linearAccel[2] = event.values[2] - gravity[2]
val magnitude = sqrt(
linearAccel[0].pow(2) + linearAccel[1].pow(2) + linearAccel[2].pow(2)
)
Patterns: walking — regular peaks 1.5–2.5 m/s² with frequency 1.5–2.5 Hz. Driving — low-frequency vibrations < 0.5 Hz. Rest — magnitude < 0.1 m/s².
Fall Detection
For wearables (elderly, miners): free fall → magnitude < 0.5g for > 300 ms → impact → magnitude > 3g. This triggers SOS.
Batch Updates (Android 4.4+)
SensorManager.flush() and maxReportLatencyUs parameter in registerListener allow accumulating data in hardware FIFO and receiving in batches. Useful for background apps — sensor accumulates data while CPU sleeps, wakes every N seconds, delivers everything at once:
sensorManager.registerListener(
listener,
accelerometer,
10_000, // samplingPeriodUs = 100 Hz
500_000 // maxReportLatencyUs = 500 ms batch
)
FIFO volume varies by chipset (512–4096 samples). On overflow, old data is displaced — account for this in long sessions.
Timeline
Basic integration of 1–2 sensors with data processing — 3–5 working days. Multi-sensor algorithm with activity classification, background recording, and analytics — 2–4 weeks.







