Android: merge manufacturer data with duplicate company id (don't drop a structure)#258
Android: merge manufacturer data with duplicate company id (don't drop a structure)#258ashekochikhin wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces custom parsing of raw BLE advertisement bytes in UniversalBleHelper.kt to extract and merge manufacturer-specific data by company ID, falling back to the standard API if no raw data is parsed. The reviewer recommends adding unit tests to verify this parsing logic, especially for handling duplicate company IDs, and provides a Kotlin test implementation using Mockito.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| val raw = scanRecord?.bytes | ||
| if (raw != null) { |
There was a problem hiding this comment.
To ensure the correctness of the custom BLE advertisement packet parsing and prevent future regressions, it is highly recommended to add unit tests covering this logic.
You can add a test case to android/src/test/kotlin/com/navideck/universal_ble/UniversalBlePluginTest.kt (or a new test file) to validate that:
- Duplicate company IDs are correctly merged/concatenated.
- Standard non-duplicate manufacturer data is parsed correctly.
- Malformed packets (e.g., invalid lengths) are handled gracefully without throwing exceptions.
Here is an example of how you can implement this test using Mockito:
import android.bluetooth.le.ScanRecord
import android.bluetooth.le.ScanResult
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Test
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
class UniversalBleHelperTest {
@Test
fun testManufacturerDataList_mergesDuplicateCompanyId() {
val scanResult = mock(ScanResult::class.java)
val scanRecord = mock(ScanRecord::class.java)
`when`(scanResult.scanRecord).thenReturn(scanRecord)
// Construct raw bytes with duplicate company IDs (0x004C)
// Structure 1: Length 5, Type 0xFF, Company ID 0x004C, Data: 0x01, 0x02
// Structure 2: Length 5, Type 0xFF, Company ID 0x004C, Data: 0x03, 0x04
val rawBytes = byteArrayOf(
5, 0xFF.toByte(), 0x4C, 0x00, 0x01, 0x02,
5, 0xFF.toByte(), 0x4C, 0x00, 0x03, 0x04,
0, 0, 0 // padding
)
`when`(scanRecord.bytes).thenReturn(rawBytes)
val dataList = scanResult.manufacturerDataList
assertEquals(1, dataList.size)
assertEquals(0x004CL, dataList[0].companyIdentifier)
assertArrayEquals(byteArrayOf(0x01, 0x02, 0x03, 0x04), dataList[0].data)
}
}
Fixes #256.
Summary
On Android,
manufacturerDataListis built fromScanRecord.getManufacturerSpecificData()— aSparseArray<byte[]>keyed bycompany id. When one advertisement carries two manufacturer-specific (0xFF) AD
structures with the same company id (e.g. split across ADV_IND and SCAN_RSP),
the framework's
SparseArray.putoverwrites one, so the plugin only ever surfacesa single structure.
This parses the raw
ScanRecord.getBytes()instead and concatenates every 0xFFstructure per company id (record order), so no payload is dropped. Falls back to
the framework
SparseArrayparse when raw bytes are unavailable.Changes
android/.../UniversalBleHelper.kt—ScanResult.manufacturerDataListnow walksthe raw scan-record bytes and merges same-company-id manufacturer data. No public
API change; Android-only.