Skip to content

Android: merge manufacturer data with duplicate company id (don't drop a structure)#258

Open
ashekochikhin wants to merge 3 commits into
Navideck:mainfrom
ashekochikhin:android-manufacturer-data-merge
Open

Android: merge manufacturer data with duplicate company id (don't drop a structure)#258
ashekochikhin wants to merge 3 commits into
Navideck:mainfrom
ashekochikhin:android-manufacturer-data-merge

Conversation

@ashekochikhin

Copy link
Copy Markdown

Fixes #256.

Summary

On Android, manufacturerDataList is built from
ScanRecord.getManufacturerSpecificData() — a SparseArray<byte[]> keyed by
company 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.put overwrites one, so the plugin only ever surfaces
a single structure.

This parses the raw ScanRecord.getBytes() instead and concatenates every 0xFF
structure per company id
(record order), so no payload is dropped. Falls back to
the framework SparseArray parse when raw bytes are unavailable.

Changes

  • android/.../UniversalBleHelper.ktScanResult.manufacturerDataList now walks
    the raw scan-record bytes and merges same-company-id manufacturer data. No public
    API change; Android-only.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +154 to +155
val raw = scanRecord?.bytes
if (raw != null) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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:

  1. Duplicate company IDs are correctly merged/concatenated.
  2. Standard non-duplicate manufacturer data is parsed correctly.
  3. 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)
    }
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please check tests added

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Android: manufacturerDataList drops a structure when two manufacturer-specific entries share a company id

1 participant