Skip to content

Commit 10d6901

Browse files
oschwaldclaude
andcommitted
feat: Add sensor enumeration for device fingerprinting
Enumerate all device sensors via SensorManager: - Sensor name and vendor - Type, version, and specifications - Power consumption and resolution The complete sensor list provides a unique fingerprint of the device's hardware capabilities. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 98272fe commit 10d6901

File tree

3 files changed

+152
-0
lines changed

3 files changed

+152
-0
lines changed

device-sdk/src/main/java/com/maxmind/device/collector/DeviceDataCollector.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal class DeviceDataCollector(private val context: Context) {
2828
private val storedIDsCollector = StoredIDsCollector(context)
2929
private val gpuCollector = GpuCollector()
3030
private val audioCollector = AudioCollector(context)
31+
private val sensorCollector = SensorCollector(context)
3132

3233
/**
3334
* Collects current device data.
@@ -42,6 +43,7 @@ internal class DeviceDataCollector(private val context: Context) {
4243
hardware = collectHardwareInfo(),
4344
gpu = gpuCollector.collect(),
4445
audio = audioCollector.collect(),
46+
sensors = sensorCollector.collect(),
4547
installation = collectInstallationInfo(),
4648
locale = collectLocaleInfo(),
4749
// Timezone offset in minutes
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.maxmind.device.collector
2+
3+
import android.content.Context
4+
import android.hardware.Sensor
5+
import android.hardware.SensorManager
6+
import com.maxmind.device.model.SensorInfo
7+
8+
/**
9+
* Collects information about device sensors.
10+
*
11+
* Enumerates all sensors available on the device and captures
12+
* their properties for device fingerprinting.
13+
*/
14+
internal class SensorCollector(
15+
private val context: Context,
16+
) {
17+
/**
18+
* Collects information about all sensors on the device.
19+
*
20+
* @return List of [SensorInfo] for each available sensor
21+
*/
22+
fun collect(): List<SensorInfo> {
23+
val sensorManager =
24+
context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager
25+
?: return emptyList()
26+
27+
val sensors = sensorManager.getSensorList(Sensor.TYPE_ALL)
28+
29+
return sensors.map { sensor ->
30+
SensorInfo(
31+
name = sensor.name,
32+
vendor = sensor.vendor,
33+
type = sensor.type,
34+
version = sensor.version,
35+
maxRange = sensor.maximumRange,
36+
resolution = sensor.resolution,
37+
power = sensor.power,
38+
)
39+
}
40+
}
41+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.maxmind.device.collector
2+
3+
import android.content.Context
4+
import android.hardware.Sensor
5+
import android.hardware.SensorManager
6+
import io.mockk.every
7+
import io.mockk.mockk
8+
import org.junit.jupiter.api.Assertions.assertEquals
9+
import org.junit.jupiter.api.Assertions.assertNotNull
10+
import org.junit.jupiter.api.Assertions.assertTrue
11+
import org.junit.jupiter.api.BeforeEach
12+
import org.junit.jupiter.api.Test
13+
14+
internal class SensorCollectorTest {
15+
private lateinit var mockContext: Context
16+
private lateinit var mockSensorManager: SensorManager
17+
private lateinit var collector: SensorCollector
18+
19+
@BeforeEach
20+
internal fun setUp() {
21+
mockContext = mockk(relaxed = true)
22+
mockSensorManager = mockk(relaxed = true)
23+
every { mockContext.getSystemService(Context.SENSOR_SERVICE) } returns mockSensorManager
24+
collector = SensorCollector(mockContext)
25+
}
26+
27+
@Test
28+
internal fun `collect returns sensor list with properties`() {
29+
val mockSensor =
30+
mockk<Sensor> {
31+
every { name } returns "Accelerometer"
32+
every { vendor } returns "Qualcomm"
33+
every { type } returns Sensor.TYPE_ACCELEROMETER
34+
every { version } returns 1
35+
every { maximumRange } returns 39.2266f
36+
every { resolution } returns 0.0012f
37+
every { power } returns 0.13f
38+
}
39+
every { mockSensorManager.getSensorList(Sensor.TYPE_ALL) } returns listOf(mockSensor)
40+
41+
val result = collector.collect()
42+
43+
assertNotNull(result)
44+
assertEquals(1, result.size)
45+
assertEquals("Accelerometer", result[0].name)
46+
assertEquals("Qualcomm", result[0].vendor)
47+
assertEquals(Sensor.TYPE_ACCELEROMETER, result[0].type)
48+
assertEquals(1, result[0].version)
49+
assertEquals(39.2266f, result[0].maxRange)
50+
assertEquals(0.0012f, result[0].resolution)
51+
assertEquals(0.13f, result[0].power)
52+
}
53+
54+
@Test
55+
internal fun `collect returns multiple sensors`() {
56+
val accelerometer =
57+
mockk<Sensor> {
58+
every { name } returns "Accelerometer"
59+
every { vendor } returns "Qualcomm"
60+
every { type } returns Sensor.TYPE_ACCELEROMETER
61+
every { version } returns 1
62+
every { maximumRange } returns 39.2266f
63+
every { resolution } returns 0.0012f
64+
every { power } returns 0.13f
65+
}
66+
val gyroscope =
67+
mockk<Sensor> {
68+
every { name } returns "Gyroscope"
69+
every { vendor } returns "STMicroelectronics"
70+
every { type } returns Sensor.TYPE_GYROSCOPE
71+
every { version } returns 2
72+
every { maximumRange } returns 34.9066f
73+
every { resolution } returns 0.001f
74+
every { power } returns 0.3f
75+
}
76+
every { mockSensorManager.getSensorList(Sensor.TYPE_ALL) } returns
77+
listOf(
78+
accelerometer,
79+
gyroscope,
80+
)
81+
82+
val result = collector.collect()
83+
84+
assertEquals(2, result.size)
85+
assertEquals("Accelerometer", result[0].name)
86+
assertEquals("Gyroscope", result[1].name)
87+
}
88+
89+
@Test
90+
internal fun `collect returns empty list when no sensors available`() {
91+
every { mockSensorManager.getSensorList(Sensor.TYPE_ALL) } returns emptyList()
92+
93+
val result = collector.collect()
94+
95+
assertNotNull(result)
96+
assertTrue(result.isEmpty())
97+
}
98+
99+
@Test
100+
internal fun `collect returns empty list when SensorManager unavailable`() {
101+
every { mockContext.getSystemService(Context.SENSOR_SERVICE) } returns null
102+
val collectorWithNoSensorManager = SensorCollector(mockContext)
103+
104+
val result = collectorWithNoSensorManager.collect()
105+
106+
assertNotNull(result)
107+
assertTrue(result.isEmpty())
108+
}
109+
}

0 commit comments

Comments
 (0)