81 · QR 码协议与编解码

对象:base/settingsLibAndroid/src/{xcddif,f3dif}/java/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt(xcddif 版 362 行 / f3dif 版 387 行)。 常量板:同目录 BluetoothBroadcastUtils.java。 本文行号一律为 xcddif 版(f3dif 为 SIG 格式,结构不同,见 §5)。

1. 两种前缀 = 两个协议版本

flavor前缀(SCHEME_BT_BROADCAST_METADATA)协议
xcddif (A14)BT:Android 14 AOSP 私有格式(AOSP 生态内互通)
f3dif (A17)BLUETOOTH:UUID:184F;蓝牙 SIG Auracast 标准格式(0x184F = Broadcast Audio Announcement 服务 UUID)

注意 f3dif 前缀自带尾分号——解析端 removePrefix 后直接进入 k:v 序列(Ext.kt :119 的写法在两 flavor 一致)。

2. 报文结构(xcddif 生成侧)

BT:R:65536;T:0;D:AA-BB-CC-DD-EE-FF;AS:12;B:34;BN:<b64>;PM:<b64>;SI:0;C:<b64>;SG:BS:…,BM:…,AC:<b64>;VN:U;;
└┬┘└───────────── 外层 k:v 用 ; 分隔 (:58) ─────────────┘            └─ SG 子组内用 , (:59)    └┬┘└┬┘
前缀(:46)                                                                                后缀;;(:61)

字段表(键常量锚点 = xcddif Ext.kt):

含义来源字段出现
R:35QR 协议版本,固定 0x010000(:64)常量必有
T:36源地址类型sourceAddressType必有
D:37源设备 MAC,: 替换为 -sourceDevice.address必有
AS:38Advertising SIDsourceAdvertisingSid必有
B:39broadcastIdbroadcastId必有
BN:40广播名(UTF-8→Base64 NO_WRAP)broadcastName可空才写
PM:41公共广播元数据(rawMetadata→Base64)publicBroadcastMetadata可空才写
SI:42PA sync 间隔paSyncInterval必有
C:43加密 broadcastCode(Base64)broadcastCode可空才写(有 C = 加密广播)
SG:44子组(可重复subgroups0..n
V:45厂商数据(可重复—(解析侧收进 vendorDataList 但不上 builder,:232-234 仅日志)0..n
VN:46生成端 Android 版本,固定 "U"(:63)常量必有

子组内字段(, 分隔):BS bisSync 位图(:49) / BM bisMask 位图(:50) / AC 音频内容 rawMetadata→Base64(:51)。 厂商数据内字段:VI companyId(:54) / VD vendorData Base64(:55)。 二进制字段(BN/PM/C/AC/VD)统一 Base64.NO_WRAP(不留换行)。

3. 生成侧 toQrCodeString()(:76-103)

按固定顺序攒 entries → SCHEME + join(";") + ";;"。BN(:83-86)/PM(:87-90)/C(:92-95) 判空才加;subgroups 逐个转子串(:96-97);生成完 Log.d 打完整 QR 串(:101)——调试时 logcat 搜 TAG BtLeBroadcastMetadataExt 直接看到码内容

4. 解析侧 convertToBroadcastMetadata()(:110-127)

  1. 前缀不匹配 → 打 error 日志返回 null(:111-115);
  2. strip 前缀+后缀 → parseQrCodeToMetadata(:144);
  3. 任何异常 → Log.w + null(:123-126)。调用方必须处理 null(扫码内容不是本协议时这是常态而非异常)。

parseQrCodeToMetadata 的防御性设计(值得借鉴):

  • 逐字段查重:每个单值字段 require(还是初始值) { "Duplicate xxx" },重复键直接抛(如 :171/:175/:181);
  • 忽略第三段起split(":", limit = 2) 只取前两段(:147/:168)——值里含 : 不炸;
  • 可重复键白名单:只有 SG(:217-219) 和 V(:221-223) 允许多次出现,进循环;
  • 位图子集校验:bisSync 有偏好时必须是 bisMask 子集,否则 require 抛(:339-341);bisSync=0xFFFFFFFF 视为无偏好(:320/:338);
  • 收尾建源设备BluetoothAdapter.getDefaultAdapter().getRemoteLeDevice(addr, type)(:235-237),MAC - 还原为 :(:182);
  • presentation delay 置 0 并注释原因(加入源时未知、无用,sink 同步后自知,:248-250)。

5. flavor 差异细节

  • f3dif 版 387 行 vs xcddif 362 行:SIG 格式的字段体系与前缀不同,生成/解析逻辑相应不同。本文字段表只对 xcddif 成立;f3dif 逐字段对照留待补篇。
  • 两 flavor 的 BluetoothBroadcastUtils.java 除 :46 前缀外逐字节相同(diff 仅 46c46,2026-09-06 核实)。

6. 维护注意

  1. 改协议字段 = 两 flavor 两份联动(xcddif + f3dif 的 Ext.kt;动前缀还要改各自 Utils:46)。dcd 无此文件不用管。
  2. 4 个零引用常量(TAG_FRAGMENT/ACTION/EXTRA×2)是上游同形遗物:删了无害但增加与 AOSP diff,留着无副作用;动它们前全仓 grep(含 XML 值)
  3. 解析端异常吞掉返回 null 是设计行为,排障先看有没有 Cannot parse / does not begin with 日志再怀疑调用方。

时效声明

行号 2026-09-06 于 dev 分支核对(主控逐行读 + 红队 agent 独立复核,见 _对抗核验记录-80篇.md)。引用前 grep -n 复核。