skip to Main Content

I’m trying to obtain Appearance data of Bluetooth Low Energy devices in Android (in a Flutter app but seemingly iOS doesn’t provide appearance, so I’m focusing on Android). ScanRecord has a https://developer.android.com/reference/android/bluetooth/le/ScanRecord#getAdvertisingDataMap() function, however what can I use to index that dictionary?

I tried to talk with Bard and first it wanted me to tediously dig in the byte array returned by https://developer.android.com/reference/android/bluetooth/le/ScanRecord#getBytes(). I’m not sure if that’s a hallucination though, but I have a feeling I should use getAdvertisingDataMap instead of digging in a raw byte array.

Bard hallucinated a non existent constant to index into the dictionary, and says I cannot use https://developer.android.com/reference/android/bluetooth/le/ScanRecord#DATA_TYPE_APPEARANCE, is that true?

My concept:

int getAppearance(ScanResult result) {
  ScanRecord scanRecord = result.getScanRecord();
  Map<Integer, byte[]> advertisingDataMap = scanRecord.getAdvertisingDataMap();
  int appearance = 0;
  if (advertisingDataMap.containsKey(ScanRecord.DATA_TYPE_APPEARANCE)) {
    byte[] appearanceBytes = advertisingDataMap[ScanRecord.DATA_TYPE_APPEARANCE];
    appearance = appearanceBytes[0] * 256 + appearanceBytes[1];
  }

  return appearance;
}

2

Answers


  1. Chosen as BEST ANSWER

    The code section in my question actually works. However unfortunately only on API Level 33+, so Android 13 and above. Probably that's why I haven't found examples about it (it's too new?) and Bard was insistent on .getBytes().

    However what Bard was advising was a simple loop which didn't care about the actual structure of the advertising data, so it was simply searching for the 0x19 (ScanRecord.DATA_TYPE_APPEARANCE) value. That would be incorrect if the appearance would follow some other data where the payload would accidentally contain that same byte. We'd instead need to intelligently stride when looking for the payload.

    Another StackOverflow entry shows the info about the data structure: https://stackoverflow.com/a/24043510/292502 with some example code which interprets the whole byte array. Probably Google integrated some similar algorithm and made it available as the getAdvertisingDataMap(). Here is another generic example: https://github.com/DrJukka/BLETestStuff/blob/master/MyBLETest/app/src/main/java/org/thaliproject/p2p/mybletest/BLEBase.java

    Since I'm only looking for the appearance, here is the code I'm trying to PR into the Flutter Blue Plus plugin:

            ScanRecord scanRecord = scanResult.getScanRecord();
            int appearance = 0;
            if (scanRecord != null) {
                if (Build.VERSION.SDK_INT >= 33) { // Android 13
                    Map<Integer, byte[]> advertisingDataMap = scanRecord.getAdvertisingDataMap();
                    if (advertisingDataMap.containsKey(ScanRecord.DATA_TYPE_APPEARANCE)) {
                        byte[] appearanceBytes = advertisingDataMap.get(ScanRecord.DATA_TYPE_APPEARANCE);
                        appearance = appearanceBytes[1] * 256 + appearanceBytes[0];
                    }
                } else {
                    byte[] scanRecordBytes = scanRecord.getBytes(); // From API Level 21
                    int byteIndex = 0;
                    while (byteIndex < scanRecordBytes.length) {
                        int length = scanRecordBytes[byteIndex++];
                        // Zero value indicates that we are done with the record now
                        if (length == 0) break;
    
                        int dataType = scanRecordBytes[byteIndex];
                        // If the data type is zero, then we are pass the significant
                        // section of the data, and we are done
                        if (dataType == 0) break;
    
                        if (dataType == 0x00000019) { // ScanRecord.DATA_TYPE_APPEARANCE magic byte
                            appearance = scanRecordBytes[byteIndex + 2] * 256 + scanRecordBytes[byteIndex + 1];
                        }
    
                        byteIndex += length;
                    }
                }
            }
    

  2. The bandwidth of Bluetooth Advertising PDUs is limited and most AD elements are optional. It is therefore (very) rare for a device to insert its Appearance AD element into the Advertising PDU.

    So I think your code might work in general, but you won’t get a result because most devices don’t broadcast this information.

    In general, a "fingerprinting" approach could work well for such functionality. If you use a tool like nRF Connect for Mobile you could take a look at how it was implemented there.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search