前面我们提到了 蓝牙协议栈中的 Properties , 这篇文章是 他的补充。
- 【android bluetooth 框架分析 04】【bt-framework 层详解 6】【Properties介绍】
在 AOSP(Android Open Source Project)中,AdapterProperties
是一个 Java 层类,存在于 Bluetooth 栈的上层(/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java
),它的作用是 封装并管理本地蓝牙适配器(Bluetooth Adapter)的属性,如名称、地址、开关状态、能见性等。
可以将 AdapterProperties
类比为一个“蓝牙身份证”:
- 它记录了本地蓝牙设备的“姓名”(Name)、“身份证号”(Address)、“工作能力”(支持的 Profile UUID)等;
- 同时,它也像一个“前台秘书”,代表本地蓝牙向系统汇报身份变更(地址、名称改了),并在用户询问时给出准确答复;
- 所有的数据,都是它通过“原始记录处”(native 层)查询来的,并自己维护一份缓存副本,提高效率。
1. AdapterProperties
的主要职责:
职责 | 说明 |
---|---|
属性缓存 | 缓存从 native 层获取的适配器属性,避免重复调用底层 |
属性更新 | 接收 native 层的事件通知(如 Adapter 属性变化),及时更新属性值 |
状态同步 | 与 AdapterService 协同工作,确保 UI 层获取一致的适配器状态 |
权限保护 | 对外暴露属性前执行权限检查,比如是否允许访问设备地址等 |
通知分发 | 当属性发生变化时,通知系统的其他组件,如广播 Intent |
对 native 层调用的封装 | 如 setAdapterPropertyNative() ,setBufferLengthMillisNative() ,通过 JNI 与 native 通信 |
职能分类 | 具体内容 |
---|---|
适配器信息管理 | 地址、名称、CoD、UUID 等 |
native 接口封装 | 封装对 HAL 层的属性访问接口 |
属性变更监听 | 接收 native 层属性变更事件并更新缓存 |
状态广播 | 属性变化时向系统广播,通知 UI 层或应用 |
权限与安全 | 控制某些敏感属性的访问,例如地址 |
2. 设计目的:为什么这样设计?
目的 | 说明 |
---|---|
解耦 | 将 Adapter 属性管理从 AdapterService 中剥离,逻辑更清晰 |
状态缓存 | native 属性变更频繁,通过缓存减少系统负载 |
多线程安全 | 封装属性处理逻辑,有助于线程同步 |
对外接口控制 | 通过 AdapterProperties 控制暴露出去的属性,利于权限和隐私控制 |
JNI 管理集中化 | 所有蓝牙本地属性相关的 native 方法都集中管理,避免 JNI 调用散乱 |
3. 接口说明
adapter property 保管的重要信息,如何在 java 层 和 native 层传递的?
1. java -> native api
关于 AdapterPropertyNative 有如下几个 jni 接口
- android/app/src/com/android/bluetooth/btservice/AdapterService.java
/*package*/native boolean setAdapterPropertyNative(int type, byte[] val);/*package*/native boolean getAdapterPropertiesNative();/*package*/native boolean getAdapterPropertyNative(int type);/*package*/native boolean setAdapterPropertyNative(int type);
- 上面几个接口 是 java -> native 主动调用的接口
2. native -> java api
- /packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static void classInitNative(JNIEnv* env, jclass clazz) {
...method_adapterPropertyChangedCallback = env->GetMethodID(jniCallbackClass, "adapterPropertyChangedCallback", "([I[[B)V");
...
}
- /packages/modules/Bluetooth/android/app/src/com/android/bluetooth/btservice/JniCallbacks.java
void adapterPropertyChangedCallback(int[] types, byte[][] val) {mAdapterProperties.adapterPropertyChangedCallback(types, val);}
- native 侧 如果要主动将 adapter property 发送给 java 侧,通过 adapterPropertyChangedCallback 回调函数来实现。
adapter_properties_callback
功能介绍:
-
是 Android 蓝牙 JNI 层(Bluetooth JNI Layer)用于处理适配器(Adapter)属性变化回调的函数。它的功能是把底层 C/C++ Bluetooth stack 传来的属性变化信息封装为 Java 对象,然后通过 JNI 调用 Java 层的回调函数。
-
定义了一个静态函数
adapter_properties_callback
,当蓝牙适配器(本地蓝牙模块)属性发生变化时被调用。 -
packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
/*
入参介绍:status: 操作状态(例如 BT_STATUS_SUCCESS 表示成功)num_properties: 属性的数量properties: 指向 bt_property_t 类型数组,包含多个属性数据*/static void adapter_properties_callback(bt_status_t status, int num_properties,bt_property_t* properties) {// 创建一个 CallbackEnv 对象,用于获取当前线程的 JNI 环境。CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return; // 如果当前线程没有有效的 JNI 环境(比如当前线程没有附加到 JVM),直接返回。ALOGV("%s: Status is: %d, Properties: %d", __func__, status, num_properties);// 如果状态不是成功,输出错误日志并返回。if (status != BT_STATUS_SUCCESS) {ALOGE("%s: Status %d is incorrect", __func__, status);return;}/*创建一个 jbyteArray 类型的 Java 字节数组,用来测试 JNI 是否可以正确分配内存(但这个变量并未后续使用,可能只是用于验证 JVM 能否正常分配数组)。ScopedLocalRef 是一个 RAII 类型的包装器,会自动释放局部引用,避免 JNI 局部引用表溢出。*/ScopedLocalRef<jbyteArray> val(sCallbackEnv.get(),(jbyteArray)sCallbackEnv->NewByteArray(num_properties));if (!val.get()) { // 如果数组分配失败,记录错误并返回。ALOGE("%s: Error allocating byteArray", __func__);return;}// 获取 val 对象的 Java 类,用于后续创建对象数组(虽然是 jbyteArray 类型,但这里取得的是其 Class 对象)。ScopedLocalRef<jclass> mclass(sCallbackEnv.get(),sCallbackEnv->GetObjectClass(val.get()));/* (BT) Initialize the jobjectArray and jintArray here itself and send theinitialized array pointers alone to get_properties */// 创建一个 jobjectArray 数组,用来存储 Java 层属性对象。数组长度为 num_properties,数组元素类型是 mclass(即 byte[])。ScopedLocalRef<jobjectArray> props(sCallbackEnv.get(),sCallbackEnv->NewObjectArray(num_properties, mclass.get(), NULL));if (!props.get()) { // 如果对象数组分配失败,记录错误并返回。ALOGE("%s: Error allocating object Array for properties", __func__);return;}// 创建一个 jintArray 数组,用于存储属性类型(每个 bt_property_t 对象的 type 字段)。ScopedLocalRef<jintArray> types(sCallbackEnv.get(), (jintArray)sCallbackEnv->NewIntArray(num_properties));if (!types.get()) { // 如果类型数组分配失败,记录错误并返回。ALOGE("%s: Error allocating int Array for values", __func__);return;}// 将 ScopedLocalRef 中的原生 JNI 对象提取出来,准备传给 C 函数。jintArray typesPtr = types.get();jobjectArray propsPtr = props.get();// 调用辅助函数 get_properties,将 C 层属性 bt_property_t* 转换为 Java 中的 jintArray(类型数组)和 jobjectArray(属性值数组)。如果转换失败,直接返回。if (get_properties(num_properties, properties, &typesPtr, &propsPtr) < 0) {return;}/*最后调用 Java 层的回调方法 adapterPropertyChangedCallback,把构建好的属性类型数组和属性值数组传给 Java。sJniCallbacksObj 是之前保存的全局 Java 回调对象method_adapterPropertyChangedCallback 是方法 ID(jmethodID),对应 Java 中定义的回调函数*/sCallbackEnv->CallVoidMethod(sJniCallbacksObj,method_adapterPropertyChangedCallback,types.get(), props.get());
}
该函数的流程如下:
- 检查 JNI 环境;
- 判断 status 是否成功;
- 为属性数据创建 Java 类型的数组;
- 将底层属性转换为 Java 格式;
- 回调 Java 层传递这些属性。
4. 管理那些属性?
1. AdapterProperties 和 DeviceProperties 共同使用
枚举常量 | 说明 | 使用范围 | 数据类型 | 访问权限 |
---|---|---|---|---|
🔁 适用于 Adapter 和 Remote Device | ||||
BT_PROPERTY_BDNAME | 设备名称 | Adapter: 读/写Remote Device: 只读 | bt_bdname_t | GET / SET(Adapter)GET(Remote) |
BT_PROPERTY_BDADDR | 设备地址 | Adapter & Remote Device | RawAddress | GET |
BT_PROPERTY_UUIDS | 支持的服务 UUID 列表 | Remote Device | bluetooth::Uuid[] | GET |
BT_PROPERTY_CLASS_OF_DEVICE | 类别码 | Remote Device | uint32_t | GET |
BT_PROPERTY_TYPE_OF_DEVICE | 设备类型(BR/EDR/LE) | Remote Device | bt_device_type_t | GET |
BT_PROPERTY_SERVICE_RECORD | 服务记录 | Remote Device | bt_service_record_t | GET |
2. 仅 AdapterProperties 使用
枚举常量 | 说明 | 使用范围 | 数据类型 | 访问权限 |
---|---|---|---|---|
🧭 仅适用于 Adapter(本地适配器) | ||||
BT_PROPERTY_ADAPTER_SCAN_MODE | 扫描模式(可发现性) | Adapter | bt_scan_mode_t | GET / SET |
BT_PROPERTY_ADAPTER_BONDED_DEVICES | 已绑定设备地址列表 | Adapter | RawAddress[] | GET |
BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT | 可发现超时时间 | Adapter | uint32_t | GET / SET |
BT_PROPERTY_LOCAL_LE_FEATURES | 本地 LE 特性 | Adapter | bt_local_le_features_t | GET |
BT_PROPERTY_LOCAL_IO_CAPS | 本地 IO 能力(经典蓝牙) | Adapter | bt_io_cap_t | GET / SET |
BT_PROPERTY_LOCAL_IO_CAPS_BLE | 本地 IO 能力(BLE) | Adapter | bt_io_cap_t | GET / SET |
BT_PROPERTY_DYNAMIC_AUDIO_BUFFER | 音频缓冲设置(动态) | Adapter | 自定义类型 | (未明确) |
- BT_PROPERTY_DYNAMIC_AUDIO_BUFFER : 应该是 a2dp source 有关, 遇到具体问题在分析, 不是本篇重点,暂时不表。
5. 真实车机日志鉴赏
这里我分享一个 真实的车机 蓝牙启动过程中, AdapterProperties 相关的日志。我们一起来欣赏一下,在启动过程中 AdapterProperties 交互。
// 蓝牙进程已经拉起
01-02 04:40:07.206918 2259 2658 I AdapterState0: OFF : entered
01-02 04:40:07.206954 2259 2658 D AdapterProperties: Setting state to OFF// 蓝牙开始启动 ble 相关的服务例如 GattService
01-02 04:40:07.348232 2259 2658 I AdapterState0: BLE_TURNING_ON : entered
01-02 04:40:07.348271 2259 2658 D AdapterProperties: Setting state to BLE_TURNING_ON
01-02 04:40:07.356894 2259 2658 I AdapterProperties: init(), maxConnectedAudioDevices, default=5, propertyOverlayed=1, finalValue=1// native->java: 更新 ble 支持那些 feature.
01-02 04:40:08.961744 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:13 len:28
01-02 04:40:08.962018 2259 2691 D AdapterProperties: BT_PROPERTY_LOCAL_LE_FEATURES: update from BT controller mNumOfAdvertisementInstancesSupported = 16 mRpaOffloadSupported = true mNumOfOffloadedIrkSupported = 32 mNumOfOffloadedScanFilterSupported = 32 mOffloadedScanResultStorageBytes= 10240 mIsActivityAndEnergyReporting = true mVersSupported = 96 mTotNumOfTrackableAdv = 32 mIsExtendedScanSupported = true mIsDebugLogSupported = false mIsLe2MPhySupported = true mIsLeCodedPhySupported = true mIsLeExtendedAdvertisingSupported = true mIsLePeriodicAdvertisingSupported = true mLeMaximumAdvertisingDataLength = 1650 mDynamicAudioBufferSizeSupportedCodecsGroup1 = 0 mDynamicAudioBufferSizeSupportedCodecsGroup2 = 0 mIsLePeriodicAdvertisingSyncTransferSenderSupported = true mIsLeConnectedIsochronousStreamCentralSupported = false mIsLeIsochronousBroadcasterSupported = false mIsLePeriodicAdvertisingSyncTransferRecipientSupported = true// native->java: 更新 配对设备列表: 此时 len:0 没有配对设备
01-02 04:40:08.988612 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:8 len:0// native->java: 更新 mac 地址
01-02 04:40:08.988664 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:2 len:6// native->java: 更新 蓝牙名字
01-02 04:40:08.992951 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:1 len:10
01-02 04:40:09.007034 2259 2691 D AdapterProperties: Name is: xxxxx// native->java: 更新 扫描模式
01-02 04:40:09.007075 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:7 len:4
01-02 04:40:09.008607 2259 2691 D AdapterProperties: Scan Mode:20// native->java: 更新 Discoverable Timeout
01-02 04:40:09.008637 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:9 len:4
01-02 04:40:09.008671 2259 2691 D AdapterProperties: Discoverable Timeout:120// native->java: 更新 配对设备列表:
01-02 04:40:09.008688 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:8 len:6
01-02 04:40:09.009478 2259 2691 D AdapterProperties: Adding bonded device:70:8F:47:91:B0:62// native->java: 更新 UUID, 此时 len: 0, 没有可更新的 uuid
01-02 04:40:09.010275 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:3 len:0// 此时 ble 相关的服务完全启动
01-02 04:40:09.013738 2259 2658 I AdapterState0: BLE_ON : entered
01-02 04:40:09.013761 2259 2658 D AdapterProperties: Setting state to BLE_ON// 开始启动 经典蓝牙, 此时会启动 a2dpsink pbap client, hfp 等 profile.
01-02 04:40:09.042387 2259 2658 I AdapterState0: TURNING_ON : entered
01-02 04:40:09.042476 2259 2658 D AdapterProperties: Setting state to TURNING_ON// native->java: 更新 各个 子 profile 的 uuid. 标识当前 profile 的 service 已经启动成功,
// 此时app 侧, 可以对单个 profile 发起 操作了。 如果对应 profile uuid 没有更新, app 侧操作也会返回失败。
01-02 04:40:09.064374 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:3 len:16
01-02 04:40:09.111270 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:3 len:32
01-02 04:40:09.198514 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:3 len:32
01-02 04:40:09.210353 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:3 len:32
01-02 04:40:09.214185 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:3 len:48// 这里 标识 所有的 profile 已经启动完毕, 经典蓝牙可以使用了
01-02 04:40:09.231319 2259 2259 D AdapterProperties: onBluetoothReady, state=TURNING_ON, ScanMode=20
01-02 04:40:09.235691 2259 2259 I AdapterProperties: getBondedDevices: length=1// native->java: 更新 扫描模式
01-02 04:40:09.236183 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:7 len:4
01-02 04:40:09.236748 2259 2691 D AdapterProperties: Scan Mode:21// native->java: 更新 Discoverable Timeout
01-02 04:40:09.236802 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:9 len:4
01-02 04:40:09.236819 2259 2691 D AdapterProperties: Discoverable Timeout:120// 此时经典蓝牙的所有 profile 已经都启动完毕
01-02 04:40:09.238341 2259 2658 I AdapterState0: ON : entered
01-02 04:40:09.238494 2259 2658 D AdapterProperties: Setting state to ON// native->java: 更新 Local IO Capability
01-02 04:40:09.240490 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:14 len:4
01-02 04:40:09.240542 2259 2691 D AdapterProperties: mLocalIOCapability set to 1// native->java: 更新 Ble Local IO Capability
01-02 04:40:09.240579 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:15 len:4
01-02 04:40:09.240616 2259 2691 D AdapterProperties: mLocalIOCapabilityBLE set to 4// native->java: 更新 DYNAMIC AUDIO BUFFER
01-02 04:40:09.240640 2259 2691 I AdapterProperties: adapterPropertyChangedCallback with type:16 len:192
6. GET 方向代码分析
我们如何获取到 当前蓝牙的 mac 地址?
1. app
在应用侧 可以之间通过调用 :BluetoothAdapter.getAddress()
// framework/java/android/bluetooth/BluetoothAdapter.javapublic String getAddress() {try {return mManagerService.getAddress(mAttributionSource); } catch (RemoteException e) {Log.e(TAG, "", e);}return null;}
mManagerService.getAddress(mAttributionSource);
:
- 会先调用到 system_server 中, 这个过程本文不表。
- 会触发调用到 bt.server 侧,从这里开始分析。
2. bt.server-java
1. AdapterService
// android/app/src/com/android/bluetooth/btservice/AdapterService.java@Overridepublic String getAddress() {if (mService == null) {return null;}return getAddressWithAttribution(Utils.getCallingAttributionSource(mService));}@Overridepublic void getAddressWithAttribution(AttributionSource source,SynchronousResultReceiver receiver) {try {receiver.send(getAddressWithAttribution(source));} catch (RuntimeException e) {receiver.propagateException(e);}}private String getAddressWithAttribution(AttributionSource attributionSource) {AdapterService service = getService();if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getAddress")|| !Utils.checkConnectPermissionForDataDelivery(service, attributionSource, "AdapterService getAddress")) {return null;}enforceLocalMacAddressPermission(service);// 最终通过 mAdapterProperties.getAddress() 获得return Utils.getAddressStringFromByte(service.mAdapterProperties.getAddress());}
- 最终通过
mAdapterProperties.getAddress()
获得
2. AdapterProperties
// android/app/src/com/android/bluetooth/btservice/AdapterProperties.javabyte[] getAddress() {return mAddress;}
是不是很简单就获取到了?
3. AdapterProperties-callback
按照我们的理解 此时不应该时 要调用到 native , 从 native 开始获取吗? 怎么到这里就直接 获得了。
- 其实不然, 在第 3. 接口说明 native->java api , 是有一个回调的。
void adapterPropertyChangedCallback(int[] types, byte[][] values) {Intent intent;int type;byte[] val;for (int i = 0; i < types.length; i++) {val = values[i];type = types[i];infoLog("adapterPropertyChangedCallback with type:" + type + " len:" + val.length);synchronized (mObject) {switch (type) {case AbstractionLayer.BT_PROPERTY_BDADDR:mAddress = val; // 赋值给 mAddress// 向外发送广播String address = Utils.getAddressStringFromByte(mAddress);intent = newIntent(BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED,BluetoothAdapterExt.ACTION_BLUETOOTH_ADDRESS_CHANGED);intent.putExtra(BluetoothAdapter.EXTRA_BLUETOOTH_ADDRESS, address);intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);mService.sendBroadcastAsUser(intent, UserHandle.ALL,BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());break;
- 在蓝牙启动过程中就会将 BT_PROPERTY_BDADDR 从 native 更新到 java 侧。
4. AdapterService 启动时
当 AdapterService 启动时,在其生命周期 onCreate 中,会触发 native 获取 mac 地址:
- /packages/modules/Bluetooth/android/app/src/com/android/bluetooth/btservice/AdapterService.java
public void onCreate() {...mAdapterProperties = new AdapterProperties(this);mAdapterStateMachine = AdapterState.make(this);...getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDADDR); // 主动获取 mac 地址, 蓝牙名字, 蓝牙类型getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME);getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE);...}
native 层 获取到 对应的mac 地址后, 会回调到 adapterPropertyChangedCallback
7. SET 方向代码分析
这里 拿用户设置 蓝牙命名举例说明这个过程。
1. app
app 侧调用 BluetoothAdapter.setName("xxx")
来设置蓝牙命名
// framework/java/android/bluetooth/BluetoothAdapter.javapublic boolean setName(String name) {Log.d(TAG, "setName() name:"+name);if (getState() != STATE_ON) {return false;}try {mServiceLock.readLock().lock();if (mService != null) {final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();mService.setName(name, mAttributionSource, recv);return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(false);}} catch (RemoteException | TimeoutException e) {Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));} finally {mServiceLock.readLock().unlock();}return false;}
2.AdapterService
// android/app/src/com/android/bluetooth/btservice/AdapterService.java@Overridepublic void setName(String name, AttributionSource source,SynchronousResultReceiver receiver) {final String packageName = source.getPackageName();Log.d(TAG, "BluetoothAdapter Binder setName name="+name + " pkg:"+packageName);try {receiver.send(setName(name, source)); // 1.} catch (RuntimeException e) {receiver.propagateException(e);}}private boolean setName(String name, AttributionSource attributionSource) {AdapterService service = getService();if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "setName")|| !Utils.checkConnectPermissionForDataDelivery(service, attributionSource, "AdapterService setName")) {return false;}return service.mAdapterProperties.setName(name); // 2. }
3. AdapterProperties
在如下场景中我们都会使用到 setAdapterPropertyNative 接口:
// android/app/src/com/android/bluetooth/btservice/AdapterProperties.java/*** Set the local adapter property - name* @param name the name to set*/boolean setName(String name) {synchronized (mObject) {return mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_BDNAME,name.getBytes());}}boolean setBluetoothClass(BluetoothClass bluetoothClass) {synchronized (mObject) {boolean result =mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE,bluetoothClass.getClassOfDeviceBytes());if (result) {mBluetoothClass = bluetoothClass;}return result;}}boolean setIoCapability(int capability) {synchronized (mObject) {boolean result = mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS, Utils.intToByteArray(capability));if (result) {mLocalIOCapability = capability;}return result;}}boolean setLeIoCapability(int capability) {synchronized (mObject) {boolean result = mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE,Utils.intToByteArray(capability));if (result) {mLocalIOCapabilityBLE = capability;}return result;}}boolean setScanMode(int scanMode) {addScanChangeLog(scanMode);synchronized (mObject) {return mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_ADAPTER_SCAN_MODE,Utils.intToByteArray(AdapterService.convertScanModeToHal(scanMode)));}}boolean setDiscoverableTimeout(int timeout) {synchronized (mObject) {return mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT,Utils.intToByteArray(timeout));}}
// android/app/src/com/android/bluetooth/btservice/AdapterService.javanative boolean setAdapterPropertyNative(int type, byte[] val);
8. GET 相关重要函数讲解
1. getAdapterPropertyNative
- 根据传入的 type : 获取 指定的 property 值, 该函数 不是同步立马拿到结果。 会将结果异步 回调到 java 侧。
// packages/modules/Bluetooth/android/app/src/com/android/bluetooth/btservice/AdapterService.java/*package*/
native boolean getAdapterPropertyNative(int type);
- /packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static jboolean getAdapterPropertyNative(JNIEnv* env, jobject obj, jint type) {ALOGV("%s", __func__);if (!sBluetoothInterface) return JNI_FALSE;int ret = sBluetoothInterface->get_adapter_property((bt_property_type_t)type);return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
- system/btif/src/bluetooth.cc
static int get_adapter_property(bt_property_type_t type) {/* Allow get_adapter_property only for BDADDR and BDNAME if BT is disabled */if (!btif_is_enabled() && (type != BT_PROPERTY_BDADDR) &&(type != BT_PROPERTY_BDNAME) && (type != BT_PROPERTY_CLASS_OF_DEVICE))return BT_STATUS_NOT_READY;do_in_main_thread(FROM_HERE, base::BindOnce(btif_get_adapter_property, type));return BT_STATUS_SUCCESS;
}
void btif_get_adapter_property(bt_property_type_t type) {BTIF_TRACE_EVENT("%s %d", __func__, type);bt_status_t status = BT_STATUS_SUCCESS;char buf[512];bt_property_t prop;prop.type = type;prop.val = (void*)buf;prop.len = sizeof(buf);if (prop.type == BT_PROPERTY_LOCAL_LE_FEATURES) {...} else if (prop.type == BT_PROPERTY_DYNAMIC_AUDIO_BUFFER) {...} else {status = btif_storage_get_adapter_property(&prop);}invoke_adapter_properties_cb(status, 1, &prop);
}
2. getAdapterPropertiesNative
java 侧可以通过该 函数触发 所有 propert 的获取, 同样也不是 同步调用。
// packages/modules/Bluetooth/android/app/src/com/android/bluetooth/btservice/AdapterService.java/*package*/native boolean getAdapterPropertiesNative();
// packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static jboolean getAdapterPropertiesNative(JNIEnv* env, jobject obj) {ALOGV("%s", __func__);if (!sBluetoothInterface) return JNI_FALSE;int ret = sBluetoothInterface->get_adapter_properties();return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}// packages/modules/Bluetooth/system/btif/src/bluetooth.ccEXPORT_SYMBOL bt_interface_t bluetoothInterface = {sizeof(bluetoothInterface),init,enable,disable,cleanup,get_adapter_properties, // get_adapter_properties...};static int get_adapter_properties(void) {if (!btif_is_enabled()) return BT_STATUS_NOT_READY;do_in_main_thread(FROM_HERE, base::BindOnce(btif_get_adapter_properties));return BT_STATUS_SUCCESS;
}
// packages/modules/Bluetooth/system/btif/src/btif_core.ccvoid btif_get_adapter_properties(void) {BTIF_TRACE_EVENT("%s", __func__);btif_in_get_adapter_properties();
}static bt_status_t btif_in_get_adapter_properties(void) {const static uint32_t NUM_ADAPTER_PROPERTIES = 8;bt_property_t properties[NUM_ADAPTER_PROPERTIES];uint32_t num_props = 0;RawAddress addr;bt_bdname_t name;bt_scan_mode_t mode;uint32_t disc_timeout;RawAddress bonded_devices[BTM_SEC_MAX_DEVICE_RECORDS];Uuid local_uuids[BT_MAX_NUM_UUIDS];bt_status_t status;bt_io_cap_t local_bt_io_cap;bt_io_cap_t local_bt_io_cap_ble;/* RawAddress */BTIF_STORAGE_FILL_PROPERTY(&properties[num_props], BT_PROPERTY_BDADDR,sizeof(addr), &addr);status = btif_storage_get_adapter_property(&properties[num_props]);// Add BT_PROPERTY_BDADDR property into list only when successful.// Otherwise, skip this property entry.if (status == BT_STATUS_SUCCESS) {num_props++;}/* BD_NAME */BTIF_STORAGE_FILL_PROPERTY(&properties[num_props], BT_PROPERTY_BDNAME,sizeof(name), &name);btif_storage_get_adapter_property(&properties[num_props]);num_props++;/* SCAN_MODE */BTIF_STORAGE_FILL_PROPERTY(&properties[num_props],BT_PROPERTY_ADAPTER_SCAN_MODE, sizeof(mode),&mode);btif_storage_get_adapter_property(&properties[num_props]);num_props++;/* DISC_TIMEOUT */BTIF_STORAGE_FILL_PROPERTY(&properties[num_props],BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT,sizeof(disc_timeout), &disc_timeout);btif_storage_get_adapter_property(&properties[num_props]);num_props++;/* BONDED_DEVICES */BTIF_STORAGE_FILL_PROPERTY(&properties[num_props],BT_PROPERTY_ADAPTER_BONDED_DEVICES,sizeof(bonded_devices), bonded_devices);btif_storage_get_adapter_property(&properties[num_props]);num_props++;/* LOCAL UUIDs */BTIF_STORAGE_FILL_PROPERTY(&properties[num_props], BT_PROPERTY_UUIDS,sizeof(local_uuids), local_uuids);btif_storage_get_adapter_property(&properties[num_props]);num_props++;/* LOCAL IO Capabilities */BTIF_STORAGE_FILL_PROPERTY(&properties[num_props], BT_PROPERTY_LOCAL_IO_CAPS,sizeof(bt_io_cap_t), &local_bt_io_cap);btif_storage_get_adapter_property(&properties[num_props]);num_props++;BTIF_STORAGE_FILL_PROPERTY(&properties[num_props],BT_PROPERTY_LOCAL_IO_CAPS_BLE, sizeof(bt_io_cap_t),&local_bt_io_cap_ble);btif_storage_get_adapter_property(&properties[num_props]);num_props++;invoke_adapter_properties_cb(BT_STATUS_SUCCESS, num_props, properties);return BT_STATUS_SUCCESS;
}
3. btif_storage_get_adapter_property
getAdapterPropertyNative 和 getAdapterPropertiesNative 最终都会调用 btif_storage_get_adapter_property
作用:根据 property->type
类型,获取本地 Bluetooth 适配器的某项属性(如地址、已绑定设备、UUID 支持列表等),并将其填入传入的 property
结构中。
- system/btif/src/btif_storage.cc
/********************************************************************************* Function btif_storage_get_adapter_property** Description BTIF storage API - Fetches the adapter property->type* from NVRAM and fills property->val.* Caller should provide memory for property->val and* set the property->val** Returns BT_STATUS_SUCCESS if the fetch was successful,* BT_STATUS_FAIL otherwise*******************************************************************************//*参数:property: 指向 bt_property_t 的指针,结构内包含类型、长度和值的指针。
*/
bt_status_t btif_storage_get_adapter_property(bt_property_t* property) {/* Special handling for adapter address and BONDED_DEVICES *//*判断当前请求的属性是否是获取本地蓝牙地址。强制转换为 RawAddress* 类型以便赋值。*/if (property->type == BT_PROPERTY_BDADDR) {RawAddress* bd_addr = (RawAddress*)property->val;/* Fetch the local BD ADDR *//*获取底层 Bluetooth Controller 的接口(比如 HCI 层接口)指针*/const controller_t* controller = controller_get_interface();if (!controller->get_is_ready()) { // 检查 Controller 是否已经初始化完成。LOG_ERROR("%s: Controller not ready! Unable to return Bluetooth Address",__func__);*bd_addr = RawAddress::kEmpty; // 如果未就绪,返回一个空地址并失败退出return BT_STATUS_FAIL;} else {LOG_ERROR("%s: Controller ready!", __func__);*bd_addr = *controller->get_address(); // 否则,将 Controller 返回的地址写入 property->val}property->len = RawAddress::kLength; // 设置属性长度并返回成功return BT_STATUS_SUCCESS;} else if (property->type == BT_PROPERTY_ADAPTER_BONDED_DEVICES) { // 获取已绑定设备地址列表(BONDED_DEVICES)btif_bonded_devices_t bonded_devices;btif_in_fetch_bonded_devices(&bonded_devices, 0); // 获取绑定设备地址列表,填充到 bonded_devices 结构中BTIF_TRACE_DEBUG("%s: Number of bonded devices: %d ""Property:BT_PROPERTY_ADAPTER_BONDED_DEVICES",__func__, bonded_devices.num_devices);// 设置返回值长度并拷贝设备地址数组到 property->valproperty->len = bonded_devices.num_devices * RawAddress::kLength;memcpy(property->val, bonded_devices.devices, property->len);/* if there are no bonded_devices, then length shall be 0 */return BT_STATUS_SUCCESS;} else if (property->type == BT_PROPERTY_UUIDS) { // 获取支持的 UUID 服务列表/* publish list of local supported services */Uuid* p_uuid = reinterpret_cast<Uuid*>(property->val);uint32_t num_uuids = 0;uint32_t i;// 获取已启用的 Profile 服务掩码(bitmask)tBTA_SERVICE_MASK service_mask = btif_get_enabled_services_mask();LOG_INFO("%s service_mask:0x%x", __func__, service_mask);// 遍历所有服务 ID,如果对应服务启用,则进入处理。for (i = 0; i < BTA_MAX_SERVICE_ID; i++) {/* This should eventually become a function when more services are enabled*/if (service_mask & (tBTA_SERVICE_MASK)(1 << i)) {switch (i) {case BTA_HFP_SERVICE_ID: { // 如果启用了 HFP,将其 UUID 填入数组。*(p_uuid + num_uuids) =Uuid::From16Bit(UUID_SERVCLASS_AG_HANDSFREE);num_uuids++;}FALLTHROUGH_INTENDED; /* FALLTHROUGH *//* intentional fall through: Send both BFP & HSP UUIDs if HFP is* enabled */// HFP 时,顺带把 HSP 也加上(经典 Bluetooth 的设计)case BTA_HSP_SERVICE_ID: {*(p_uuid + num_uuids) =Uuid::From16Bit(UUID_SERVCLASS_HEADSET_AUDIO_GATEWAY);num_uuids++;} break;case BTA_A2DP_SOURCE_SERVICE_ID: {*(p_uuid + num_uuids) =Uuid::From16Bit(UUID_SERVCLASS_AUDIO_SOURCE);num_uuids++;} break;case BTA_A2DP_SINK_SERVICE_ID: {*(p_uuid + num_uuids) = Uuid::From16Bit(UUID_SERVCLASS_AUDIO_SINK);num_uuids++;} break;case BTA_PBAP_SERVICE_ID: {*(p_uuid + num_uuids) = Uuid::From16Bit(UUID_SERVCLASS_PBAP_PSE);num_uuids++;} break;case BTA_HFP_HS_SERVICE_ID: {*(p_uuid + num_uuids) =Uuid::From16Bit(UUID_SERVCLASS_HF_HANDSFREE);num_uuids++;} break;case BTA_MAP_SERVICE_ID: {*(p_uuid + num_uuids) = Uuid::From16Bit(UUID_SERVCLASS_MESSAGE_ACCESS);num_uuids++;} break;case BTA_MN_SERVICE_ID: {*(p_uuid + num_uuids) = Uuid::From16Bit(UUID_SERVCLASS_MESSAGE_NOTIFICATION);num_uuids++;} break;case BTA_PCE_SERVICE_ID: {*(p_uuid + num_uuids) = Uuid::From16Bit(UUID_SERVCLASS_PBAP_PCE);num_uuids++;} break;}}}property->len = (num_uuids) * sizeof(Uuid); // 设置最终的 UUID 数组长度,返回成功。return BT_STATUS_SUCCESS;}/* fall through for other properties */if (!cfg2prop(NULL, property)) { // 其他属性统一处理// 如果不属于以上特殊处理的属性类型,尝试从配置文件中读取(如 cfg2prop 从存储获取属性值)。return btif_dm_get_adapter_property(property); // 若仍未命中,则调用 btif_dm_get_adapter_property 去处理默认属性}return BT_STATUS_SUCCESS;
}
属性类型宏 | 说明 | 处理方式 |
---|---|---|
BT_PROPERTY_BDADDR | 获取本地蓝牙地址 | 读取 controller 地址 |
BT_PROPERTY_ADAPTER_BONDED_DEVICES | 获取绑定设备地址列表 | 从绑定设备结构中复制 |
BT_PROPERTY_UUIDS | 获取支持的服务 UUID 列表 | 遍历启用服务掩码生成 UUID |
其它 | 如名字、状态等 | 通过配置或默认接口读取 |
1.cfg2prop
功能:
-
该函数用于从配置文件中读取蓝牙属性值,写入到 prop 指向的结构体中。属性来源可能是本地适配器(如 Adapter 名称、扫描模式等)或远程设备(如远程设备名称、UUID 列表、版本信息等)。
-
system/btif/src/btif_storage.cc
/*
remote_bd_addr:远程设备的蓝牙地址,如果为 NULL,则表示读取本地适配器属性。prop:目标属性结构体指针,类型为 bt_property_t,包括 type、val(指向数据缓存)和 len(数据长度)。
*/static int cfg2prop(const RawAddress* remote_bd_addr, bt_property_t* prop) {std::string bdstr;if (remote_bd_addr) {/*如果传入了 remote_bd_addr,就将其转为字符串(形如 "11:22:33:44:55:66")用于后续配置文件查询 key。否则是读取本地适配器相关的属性。*/bdstr = remote_bd_addr->ToString();}if (prop->len <= 0) {// 属性长度必须是正数,否则认为是非法请求LOG_WARN("Invalid property read from configuration file type:%d, len:%d",prop->type, prop->len);return false;}int ret = false;switch (prop->type) {case BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP:if (prop->len >= (int)sizeof(int))// 从配置中读取设备的时间戳ret = btif_config_get_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVTIME,(int*)prop->val);break;case BT_PROPERTY_BDNAME: { // 本地或远程名称int len = prop->len;if (remote_bd_addr)ret = btif_config_get_str(bdstr, BTIF_STORAGE_PATH_REMOTE_NAME,(char*)prop->val, &len); // 如果是远程设备,则从其对应的配置条目中读取名字elseret = btif_config_get_str("Adapter", BTIF_STORAGE_KEY_ADAPTER_NAME,(char*)prop->val, &len); // 如果是本地设备,key 是 "Adapter",配置项名为 "Name"。if (ret && len && len <= prop->len) // 成功读取后更新 prop->len 为实际有效长度(去掉 null terminator)。prop->len = len - 1;else {prop->len = 0; // 如果读取失败则置为 0 并标记失败。ret = false;}break;}case BT_PROPERTY_REMOTE_FRIENDLY_NAME: { // 与 BDNAME 类似,从配置中获取远程设备别名 RemoteAlias。int len = prop->len;ret = btif_config_get_str(bdstr, BTIF_STORAGE_PATH_REMOTE_ALIASE,(char*)prop->val, &len);if (ret && len && len <= prop->len)prop->len = len - 1;else {prop->len = 0;ret = false;}break;}case BT_PROPERTY_ADAPTER_SCAN_MODE: // 读取适配器的可见性扫描模式(整型)。if (prop->len >= (int)sizeof(int))ret = btif_config_get_int("Adapter", BTIF_STORAGE_KEY_ADAPTER_SCANMODE,(int*)prop->val);break;// 读取本地 IO 能力(常用于配对流程),分为 BR/EDR 和 BLE。case BT_PROPERTY_LOCAL_IO_CAPS:if (prop->len >= (int)sizeof(int))ret = btif_config_get_int("Adapter", BTIF_STORAGE_KEY_LOCAL_IO_CAPS,(int*)prop->val);break;case BT_PROPERTY_LOCAL_IO_CAPS_BLE:if (prop->len >= (int)sizeof(int))ret = btif_config_get_int("Adapter", BTIF_STORAGE_KEY_LOCAL_IO_CAPS_BLE,(int*)prop->val);break;// 获取适配器的可发现超时时间。case BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT:if (prop->len >= (int)sizeof(int))ret = btif_config_get_int("Adapter", BTIF_STORAGE_KEY_ADAPTER_DISC_TIMEOUT, (int*)prop->val);break;// 获取远程设备的设备类别(整型编码)。case BT_PROPERTY_CLASS_OF_DEVICE:if (prop->len >= (int)sizeof(int))ret = btif_config_get_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVCLASS,(int*)prop->val);break;// 获取设备类型(如 phone、audio 等)。case BT_PROPERTY_TYPE_OF_DEVICE:if (prop->len >= (int)sizeof(int))ret = btif_config_get_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVTYPE,(int*)prop->val);break;// 从配置读取远程设备支持的 UUID 列表(逗号分隔的字符串)case BT_PROPERTY_UUIDS: {char value[1280];int size = sizeof(value);if (btif_config_get_str(bdstr, BTIF_STORAGE_PATH_REMOTE_SERVICE, value,&size)) {Uuid* p_uuid = reinterpret_cast<Uuid*>(prop->val);// 调用 btif_split_uuids_string 函数分割字符串并转换为 UUID 对象数组size_t num_uuids =btif_split_uuids_string(value, p_uuid, BT_MAX_NUM_UUIDS);prop->len = num_uuids * sizeof(Uuid);ret = true;} else {prop->val = NULL;prop->len = 0;}} break;// 读取远程设备的版本信息(厂商、版本号、子版本号)case BT_PROPERTY_REMOTE_VERSION_INFO: {bt_remote_version_t* info = (bt_remote_version_t*)prop->val;if (prop->len >= (int)sizeof(bt_remote_version_t)) {ret = btif_config_get_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_MFCT,&info->manufacturer);if (ret)ret = btif_config_get_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_VER,&info->version);if (ret)ret = btif_config_get_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_SUBVER,&info->sub_ver);}} break;default: // 未支持的 type 直接报错返回BTIF_TRACE_ERROR("Unknow prop type:%d", prop->type);return false;}return ret;
}#define BTIF_STORAGE_PATH_BLUEDROID "/data/misc/bluedroid"//#define BTIF_STORAGE_PATH_ADAPTER_INFO "adapter_info"
//#define BTIF_STORAGE_PATH_REMOTE_DEVICES "remote_devices"
#define BTIF_STORAGE_PATH_REMOTE_DEVTIME "Timestamp"
#define BTIF_STORAGE_PATH_REMOTE_DEVCLASS "DevClass"
#define BTIF_STORAGE_PATH_REMOTE_DEVTYPE "DevType"
#define BTIF_STORAGE_PATH_REMOTE_NAME "Name"//#define BTIF_STORAGE_PATH_REMOTE_LINKKEYS "remote_linkkeys"
#define BTIF_STORAGE_PATH_REMOTE_ALIASE "Aliase"
#define BTIF_STORAGE_PATH_REMOTE_SERVICE "Service"
#define BTIF_STORAGE_PATH_REMOTE_HIDINFO "HidInfo"
#define BTIF_STORAGE_KEY_ADAPTER_NAME "Name"
#define BTIF_STORAGE_KEY_ADAPTER_SCANMODE "ScanMode"
#define BTIF_STORAGE_KEY_LOCAL_IO_CAPS "LocalIOCaps"
#define BTIF_STORAGE_KEY_LOCAL_IO_CAPS_BLE "LocalIOCapsBLE"
#define BTIF_STORAGE_KEY_ADAPTER_DISC_TIMEOUT "DiscoveryTimeout"
#define BTIF_STORAGE_KEY_GATT_CLIENT_SUPPORTED "GattClientSupportedFeatures"
#define BTIF_STORAGE_KEY_GATT_CLIENT_DB_HASH "GattClientDatabaseHash"
#define BTIF_STORAGE_KEY_GATT_SERVER_SUPPORTED "GattServerSupportedFeatures"
#define BTIF_STORAGE_DEVICE_GROUP_BIN "DeviceGroupBin"
#define BTIF_STORAGE_CSIS_AUTOCONNECT "CsisAutoconnect"
#define BTIF_STORAGE_CSIS_SET_INFO_BIN "CsisSetInfoBin"
#define BTIF_STORAGE_LEAUDIO_AUTOCONNECT "LeAudioAutoconnect"
#define BTIF_STORAGE_LEAUDIO_HANDLES_BIN "LeAudioHandlesBin"
#define BTIF_STORAGE_LEAUDIO_SINK_PACS_BIN "SinkPacsBin"
#define BTIF_STORAGE_LEAUDIO_SOURCE_PACS_BIN "SourcePacsBin"
#define BTIF_STORAGE_LEAUDIO_ASES_BIN "AsesBin"
#define BTIF_STORAGE_LEAUDIO_SINK_AUDIOLOCATION "SinkAudioLocation"
#define BTIF_STORAGE_LEAUDIO_SOURCE_AUDIOLOCATION "SourceAudioLocation"
#define BTIF_STORAGE_LEAUDIO_SINK_SUPPORTED_CONTEXT_TYPE \"SinkSupportedContextType"
#define BTIF_STORAGE_LEAUDIO_SOURCE_SUPPORTED_CONTEXT_TYPE \"SourceSupportedContextType"
我们接下来看一下 他俩是如何实现的。
btif_config_get_str
和 btif_config_get_int
1. btif_config_get_str
和 btif_config_get_int
- system/btif/src/btif_config.cc
bool btif_config_get_int(const std::string& section, const std::string& key,int* value) {CHECK(bluetooth::shim::is_gd_stack_started_up()); // 这里已经启用了 gd 协议栈return bluetooth::shim::BtifConfigInterface::GetInt(section, key, value);
}bool btif_config_get_str(const std::string& section, const std::string& key,char* value, int* size_bytes) {CHECK(bluetooth::shim::is_gd_stack_started_up());return bluetooth::shim::BtifConfigInterface::GetStr(section, key, value,size_bytes);
}
- system/main/shim/config.cc
bool BtifConfigInterface::GetStr(const std::string& section,const std::string& property, char* value,int* size_bytes) {...// 这里会最终从 /data/misc/bluedroid/bt_config.conf 获取auto str = GetStorage()->GetConfigCache()->GetProperty(section, property);...*size_bytes = str->copy(value, (*size_bytes - 1));value[*size_bytes] = '\0';*size_bytes += 1;return true;
}
// system/main/shim/entry.cc
storage::StorageModule* GetStorage() {return Stack::GetInstance()->GetStackManager()->GetInstance<storage::StorageModule>();
}// system/gd/storage/storage_module.cc
ConfigCache* StorageModule::GetConfigCache() {std::lock_guard<std::recursive_mutex> lock(mutex_);return &pimpl_->cache_; // 这里的 cache_ 就是从 /data/misc/bluedroid/bt_config.conf 读到 内存的缓存
}---void StorageModule::Start() {...auto config = LegacyConfigFile::FromPath(config_file_path_).Read(temp_devices_capacity_);...pimpl_ = std::make_unique<impl>(GetHandler(), std::move(config.value()), temp_devices_capacity_);}struct StorageModule::impl {explicit impl(Handler* handler, ConfigCache cache, size_t in_memory_cache_size_limit): config_save_alarm_(handler), cache_(std::move(cache)), memory_only_cache_(in_memory_cache_size_limit, {}) {}
};
想具体了解 StorageModule 请 参看 【android bluetooth 框架分析 02】【Module详解 6】【StorageModule 模块介绍】
2. ConfigCache::GetProperty
函数作用是:根据指定的 section 和 property 键名,查询配置缓存中是否存在对应的值,并返回其值(可能是明文或解密后的密文)。
- system/gd/storage/config_cache.cc
/*
返回值是 std::optional<std::string>:说明返回结果可能存在,也可能不存在。参数 section:配置项所在的“区块”(类似 INI 文件里的 [section])。参数 property:具体的键名。*/std::optional<std::string> ConfigCache::GetProperty(const std::string& section, const std::string& property) const {/*加锁保护多线程访问 information_sections_、persistent_devices_、temporary_devices_ 等成员变量。recursive_mutex 表示允许同一线程多次加锁(避免死锁)。*/std::lock_guard<std::recursive_mutex> lock(mutex_);/*优先查找 information_sections_,这是“运行时内存中保存的系统信息类配置”。如果 section 存在,再查找其中是否有目标属性(property)。如果找到了,直接返回该值(明文)。*/auto section_iter = information_sections_.find(section);if (section_iter != information_sections_.end()) {auto property_iter = section_iter->second.find(property);if (property_iter != section_iter->second.end()) {return property_iter->second;}}/*如果上一步没找到,接着在 persistent_devices_ 中查找,这是“持久化保存的设备相关信息”,可能来源于配置文件。若找到对应 section 和 property,执行下一步检查:*/section_iter = persistent_devices_.find(section);if (section_iter != persistent_devices_.end()) {auto property_iter = section_iter->second.find(property);if (property_iter != section_iter->second.end()) {std::string value = property_iter->second;/*如果值是一个特殊标记(如 "Encrypted"),说明真正的数据加密存储在 keystore 中:使用 section + "-" + property 拼成 key 向 BtKeystoreInterface 查询解密后的值。否则直接返回明文值。*/if (os::ParameterProvider::GetBtKeystoreInterface() != nullptr && value == kEncryptedStr) {return os::ParameterProvider::GetBtKeystoreInterface()->get_key(section + "-" + property);}return value;}}/*如果还没找到,就在 temporary_devices_ 中查找,这可能是“临时设备信息”,生命周期较短。查找逻辑同上。*/section_iter = temporary_devices_.find(section);if (section_iter != temporary_devices_.end()) {auto property_iter = section_iter->second.find(property);if (property_iter != section_iter->second.end()) {return property_iter->second;}}return std::nullopt; // 如果三处都没找到,返回空值.
}
查找顺序 | 说明 | 是否解密 |
---|---|---|
information_sections_ | 内存中的系统信息 | 否 |
persistent_devices_ | 持久化设备信息,可能被加密 | 是 |
temporary_devices_ | 临时设备信息,仅内存存在 | 否 |
该函数逻辑清晰地体现了一个设备配置信息获取的通用策略:优先本地缓存,其次查文件(可能加密),最后查临时存储。
2. btif_dm_get_adapter_property
该函数是 Bluetooth Adapter 属性查询的入口之一,由上层调用,例如 Settings 或 Bluetooth 服务层获取蓝牙本地信息时使用:
-
此函数属于 BTIF(Bluetooth Interface)层,是 Java/Binder 与 BTA/Bluetooth stack 之间的桥梁。
-
功能:根据传入的 prop->type,填充 prop->val 值。
-
返回值为 bt_status_t,表示操作是否成功(例如 BT_STATUS_SUCCESS 或 BT_STATUS_FAIL)。
-
system/btif/src/btif_dm.cc
/********************************************************************************* Function btif_dm_get_adapter_property** Description Queries the BTA for the adapter property** Returns bt_status_t*******************************************************************************/
bt_status_t btif_dm_get_adapter_property(bt_property_t* prop) {BTIF_TRACE_EVENT("%s: type=0x%x", __func__, prop->type);switch (prop->type) {// 获取蓝牙本地名称case BT_PROPERTY_BDNAME: {bt_bdname_t* bd_name = (bt_bdname_t*)prop->val;strncpy((char*)bd_name->name, (char*)btif_get_default_local_name(),sizeof(bd_name->name) - 1); // 获取本地设备名(系统默认名,可能来源于 ro.product.*)bd_name->name[sizeof(bd_name->name) - 1] = 0; // 拷贝到 bd_name->name,确保结尾有 \0prop->len = strlen((char*)bd_name->name);} break;case BT_PROPERTY_ADAPTER_SCAN_MODE: { // 获取扫描模式/* if the storage does not have it. Most likely app never set it. Default* is NONE */bt_scan_mode_t* mode = (bt_scan_mode_t*)prop->val;*mode = BT_SCAN_MODE_NONE; // 默认设置为不可被扫描(BT_SCAN_MODE_NONE),除非应用层设置了别的值。prop->len = sizeof(bt_scan_mode_t); // 这里不从系统读出设置值,只是返回默认值。} break;// 如果设备开启“被发现模式”,这个字段指定超时时间。case BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT: { // 获取可被发现超时时间uint32_t* tmt = (uint32_t*)prop->val;*tmt = 120; /* default to 120s, if not found in NV */ // 默认 120 秒prop->len = sizeof(uint32_t);} break;// 获取设备的类信息case BT_PROPERTY_CLASS_OF_DEVICE: {DEV_CLASS dev_class; // DEV_CLASS 是一个 3 字节的数组(如 [0x5A, 0x02, 0x0C]),表示设备类型,如手机/耳机/音箱等。btif_dm_get_local_class_of_device(dev_class);memcpy(prop->val, dev_class, sizeof(DEV_CLASS)); // 获取当前本地设备类prop->len = sizeof(DEV_CLASS);} break;// While fetching IO_CAP* values for the local device, we maintain backward// compatibility by using the value from #define macros BTM_LOCAL_IO_CAPS,// BTM_LOCAL_IO_CAPS_BLE if the values have never been explicitly set.case BT_PROPERTY_LOCAL_IO_CAPS: { // 获取本地 IO 能力(Classic 模式)*(bt_io_cap_t*)prop->val = (bt_io_cap_t)BTM_LOCAL_IO_CAPS; // 返回本地设备的经典蓝牙 IO 能力,如显示/键盘/无输入输出。prop->len = sizeof(bt_io_cap_t);} break;case BT_PROPERTY_LOCAL_IO_CAPS_BLE: { // 获取 BLE IO 能力(低功耗蓝牙)*(bt_io_cap_t*)prop->val = (bt_io_cap_t)BTM_LOCAL_IO_CAPS_BLE; // 和上面类似,只不过是 BLE 模式的 IO 能力prop->len = sizeof(bt_io_cap_t);} break;default:prop->len = 0;return BT_STATUS_FAIL; // 如果传入的类型不是已知的几种,设置长度为 0,并返回失败。}return BT_STATUS_SUCCESS;
}
enum : uint8_t {BTM_IO_CAP_OUT = 0, /* DisplayOnly */BTM_IO_CAP_IO = 1, /* DisplayYesNo */BTM_IO_CAP_IN = 2, /* KeyboardOnly */BTM_IO_CAP_NONE = 3, /* NoInputNoOutput */BTM_IO_CAP_KBDISP = 4, /* Keyboard display */BTM_IO_CAP_MAX = 5,BTM_IO_CAP_UNKNOWN = 0xFF /* Unknown value */
};#ifndef BTM_LOCAL_IO_CAPS
#define BTM_LOCAL_IO_CAPS BTM_IO_CAP_IO
#endif#ifndef BTM_LOCAL_IO_CAPS_BLE
#define BTM_LOCAL_IO_CAPS_BLE BTM_IO_CAP_KBDISP
#endif
prop->type 宏名 | 内容 | 默认值/说明 |
---|---|---|
BT_PROPERTY_BDNAME | 本地设备名称 | btif_get_default_local_name() |
BT_PROPERTY_ADAPTER_SCAN_MODE | 当前扫描模式 | BT_SCAN_MODE_NONE |
BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT | 可发现超时时间 | 120 秒 |
BT_PROPERTY_CLASS_OF_DEVICE | 蓝牙设备类别(COD) | 设备类宏定义 |
BT_PROPERTY_LOCAL_IO_CAPS | 本地 I/O 能力(经典) | BTM_LOCAL_IO_CAPS |
BT_PROPERTY_LOCAL_IO_CAPS_BLE | 本地 I/O 能力(BLE) | BTM_LOCAL_IO_CAPS_BLE |
4. invoke_adapter_properties_cb
- system/btif/src/bluetooth.cc
getAdapterPropertyNative 和 getAdapterPropertiesNative 最终都会调用invoke_adapter_properties_cb
void invoke_adapter_properties_cb(bt_status_t status, int num_properties,bt_property_t* properties) {do_in_jni_thread(FROM_HERE,base::BindOnce([](bt_status_t status, int num_properties,bt_property_t* properties) {HAL_CBACK(bt_hal_cbacks, adapter_properties_cb, status,num_properties, properties);if (properties) {osi_free(properties);}},status, num_properties,property_deep_copy_array(num_properties, properties)));
}// android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static bt_callbacks_t sBluetoothCallbacks = {sizeof(sBluetoothCallbacks),adapter_state_change_callback,adapter_properties_callback,...};// 在 【3.接口说明】【2.native -> java api】 中有详细讲解
static void adapter_properties_callback(bt_status_t status, int num_properties,bt_property_t* properties) {}
9. SET 相关重要函数讲解
1. setAdapterPropertyNative
- android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static jboolean setAdapterPropertyNative(JNIEnv* env, jobject obj, jint type,jbyteArray value) {ALOGV("%s", __func__);if (!sBluetoothInterface) return JNI_FALSE;jbyte* val = env->GetByteArrayElements(value, NULL);bt_property_t prop;prop.type = (bt_property_type_t)type;prop.len = env->GetArrayLength(value);prop.val = val;int ret = sBluetoothInterface->set_adapter_property(&prop); // 1. env->ReleaseByteArrayElements(value, val, 0);return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
// system/btif/src/bluetooth.cc
static int set_adapter_property(const bt_property_t* property) {if (!btif_is_enabled()) return BT_STATUS_NOT_READY;switch (property->type) {// 只有下面的 属性,支持 Set , 其他属性 之间返回失败。不支持写case BT_PROPERTY_BDNAME:case BT_PROPERTY_ADAPTER_SCAN_MODE:case BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT:case BT_PROPERTY_CLASS_OF_DEVICE:case BT_PROPERTY_LOCAL_IO_CAPS:case BT_PROPERTY_LOCAL_IO_CAPS_BLE:break;default:return BT_STATUS_FAIL;}do_in_main_thread(FROM_HERE, base::BindOnce([](bt_property_t* property) {btif_set_adapter_property(property); // 1.osi_free(property);},property_deep_copy(property)));return BT_STATUS_SUCCESS;
}
2. btif_set_adapter_property
该函数用于设置蓝牙适配器的属性(例如名称、扫描模式、类信息等),并更新到底层 Bluetooth Stack,同时缓存到本地存储(如配置文件、nvram 等)中。
-
功能:将上层传入的新属性值更新到 Core Bluetooth Stack,同时缓存到设备存储中。
-
应用于设置名称、设备类型、扫描模式等配置。
-
注意:函数为 void 类型,不返回 bt_status_t(但可以通过其他回调机制通知上层成功/失败)。
-
system/btif/src/btif_core.cc
/********************************************************************************* Function btif_set_adapter_property** Description Updates core stack with property value and stores it in* local cache** Returns bt_status_t*******************************************************************************/void btif_set_adapter_property(bt_property_t* property) {BTIF_TRACE_EVENT("btif_set_adapter_property type: %d, len %d, 0x%x",property->type, property->len, property->val);switch (property->type) {case BT_PROPERTY_BDNAME: { // 设置蓝牙本地名称char bd_name[BTM_MAX_LOC_BD_NAME_LEN + 1];uint16_t name_len = property->len > BTM_MAX_LOC_BD_NAME_LEN? BTM_MAX_LOC_BD_NAME_LEN: property->len;memcpy(bd_name, property->val, name_len);bd_name[name_len] = '\0'; // 将新名称从 property->val 拷贝出来,并保证最后一位为 '\0'BTIF_TRACE_EVENT("set property name : %s", (char*)bd_name);BTA_DmSetDeviceName((const char*)bd_name); // 调用 BTA_DmSetDeviceName() 向 BTA 层设置设备名(通知 controller 层及远端设备)。btif_core_storage_adapter_write(property); // 保存该配置,例如写入 bt_config.conf 文件} break;case BT_PROPERTY_ADAPTER_SCAN_MODE: { // 设置扫描模式bt_scan_mode_t mode = *(bt_scan_mode_t*)property->val;BTIF_TRACE_EVENT("set property scan mode : %x", mode);if (BTA_DmSetVisibility(mode)) { // 调用 BTA_DmSetVisibility(mode) 通知 BTA 更新设备可见性。btif_core_storage_adapter_write(property); // 如果设置成功,就把它存到配置文件中}} break;case BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT: { // 设置可被发现的超时时间 /* Nothing to do beside store the value in NV. Javawill change the SCAN_MODE property after setting timeout,if required */btif_core_storage_adapter_write(property); // 只做持久化存储,不会立刻改变 controller 的状态。 java 层会在设置完 timeout 后自动设置 scan mode} break;case BT_PROPERTY_CLASS_OF_DEVICE: { // 设置设备类型 CODDEV_CLASS dev_class;memcpy(dev_class, property->val, DEV_CLASS_LEN);BTIF_TRACE_EVENT("set property dev_class : 0x%02x%02x%02x", dev_class[0],dev_class[1], dev_class[2]);BTM_SetDeviceClass(dev_class); // 使用 BTM_SetDeviceClass() 设置底层 controller 的设备类别。btif_core_storage_adapter_notify_empty_success(); // 通知设置成功(用于反馈)} break;case BT_PROPERTY_LOCAL_IO_CAPS: // 设置 IO 能力(用于配对认证)case BT_PROPERTY_LOCAL_IO_CAPS_BLE: {// Changing IO Capability of stack at run-time is not currently supported.// This call changes the stored value which will affect the stack next// time it starts up.btif_core_storage_adapter_write(property); // 虽然运行时不能动态更改 IO 能力(比如从无输入输出变为键盘显示),但会保存为下次启动生效。} break;default:break;}
}
类型宏名 | 设置行为 |
---|---|
BT_PROPERTY_BDNAME | 设置设备名,通知 Stack,写入本地配置 |
BT_PROPERTY_ADAPTER_SCAN_MODE | 设置可见性,写入本地配置(成功时) |
BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT | 仅存储,实际生效由 scan mode 决定 |
BT_PROPERTY_CLASS_OF_DEVICE | 设置设备类型(COD),立即生效,通知上层成功 |
BT_PROPERTY_LOCAL_IO_CAPS/BLE | 设置 IO 能力,仅写配置,下次启动后生效 |
- BTA_DmSetDeviceName 、 BTA_DmSetVisibility、BTM_SetDeviceClass 不再本文讨论范围内。暂时不表。有机会会单独出篇章讲解。
这里我们重点看一下 btif_core_storage_adapter_write
3. btif_core_storage_adapter_write
- system/btif/src/btif_core.cc
static void btif_core_storage_adapter_write(bt_property_t* prop) {BTIF_TRACE_EVENT("type: %d, len %d, 0x%x", prop->type, prop->len, prop->val);bt_status_t status = btif_storage_set_adapter_property(prop); // 1.invoke_adapter_properties_cb(status, 1, prop); // 这里同样会把 属性的结构回调到 java 侧
}
4. btif_storage_set_adapter_property
- system/btif/src/btif_storage.cc
bt_status_t btif_storage_set_adapter_property(bt_property_t* property) {return prop2cfg(NULL, property) ? BT_STATUS_SUCCESS : BT_STATUS_FAIL;
}
static int prop2cfg(const RawAddress* remote_bd_addr, bt_property_t* prop) {std::string bdstr;if (remote_bd_addr) {bdstr = remote_bd_addr->ToString();}char value[1024];if (prop->len <= 0 || prop->len > (int)sizeof(value) - 1) {LOG_WARN("Unable to save property to configuration file type:%d, "" len:%d is invalid",prop->type, prop->len);return false;}switch (prop->type) {case BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVTIME,(int)time(NULL));break;case BT_PROPERTY_BDNAME: {int name_length = prop->len > BTM_MAX_LOC_BD_NAME_LEN? BTM_MAX_LOC_BD_NAME_LEN: prop->len;strncpy(value, (char*)prop->val, name_length);value[name_length] = '\0';if (remote_bd_addr) {btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_NAME, value);} else {btif_config_set_str("Adapter", BTIF_STORAGE_KEY_ADAPTER_NAME, value);btif_config_flush();}break;}case BT_PROPERTY_REMOTE_FRIENDLY_NAME:strncpy(value, (char*)prop->val, prop->len);value[prop->len] = '\0';btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_ALIASE, value);break;case BT_PROPERTY_ADAPTER_SCAN_MODE:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_ADAPTER_SCANMODE,*(int*)prop->val);break;case BT_PROPERTY_LOCAL_IO_CAPS:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_LOCAL_IO_CAPS,*(int*)prop->val);break;case BT_PROPERTY_LOCAL_IO_CAPS_BLE:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_LOCAL_IO_CAPS_BLE,*(int*)prop->val);break;case BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT:btif_config_set_int("Adapter", BTIF_STORAGE_KEY_ADAPTER_DISC_TIMEOUT,*(int*)prop->val);break;case BT_PROPERTY_CLASS_OF_DEVICE:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVCLASS,*(int*)prop->val);break;case BT_PROPERTY_TYPE_OF_DEVICE:btif_config_set_int(bdstr, BTIF_STORAGE_PATH_REMOTE_DEVTYPE,*(int*)prop->val);break;case BT_PROPERTY_UUIDS: {std::string val;size_t cnt = (prop->len) / sizeof(Uuid);for (size_t i = 0; i < cnt; i++) {val += (reinterpret_cast<Uuid*>(prop->val) + i)->ToString() + " ";}btif_config_set_str(bdstr, BTIF_STORAGE_PATH_REMOTE_SERVICE, val);break;}case BT_PROPERTY_REMOTE_VERSION_INFO: {bt_remote_version_t* info = (bt_remote_version_t*)prop->val;if (!info) return false;btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_MFCT,info->manufacturer);btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_VER, info->version);btif_config_set_int(bdstr, BT_CONFIG_KEY_REMOTE_VER_SUBVER,info->sub_ver);} break;default:BTIF_TRACE_ERROR("Unknown prop type:%d", prop->type);return false;}/* No need to look for bonded device with address of NULL */if (remote_bd_addr &&btif_in_fetch_bonded_device(bdstr) == BT_STATUS_SUCCESS) {/* save changes if the device was bonded */btif_config_flush();}return true;
}
1. btif_config_set_str 和 btif_config_set_int
// system/btif/src/btif_config.cc
bool btif_config_set_int(const std::string& section, const std::string& key,int value) {CHECK(bluetooth::shim::is_gd_stack_started_up());return bluetooth::shim::BtifConfigInterface::SetInt(section, key, value);
}// system/main/shim/config.cc
bool BtifConfigInterface::SetInt(const std::string& section,const std::string& property, int value) {ConfigCacheHelper::FromConfigCache(*GetStorage()->GetConfigCache()).SetInt(section, property, value);return true;
}// system/gd/storage/config_cache_helper.cc
void ConfigCacheHelper::SetInt(const std::string& section, const std::string& property, int value) {config_cache_.SetProperty(section, property, std::to_string(value));
}
2. ConfigCache::SetProperty
功能:
- 设置一个配置项(如蓝牙设备属性或通用配置),并根据是否为设备属性决定是否进入持久化配置或临时配置缓存中。
// system/gd/storage/config_cache.ccvoid ConfigCache::SetProperty(std::string section, std::string property, std::string value) {/*使用递归互斥锁保护对 information_sections_、persistent_devices_、temporary_devices_ 等共享数据结构的并发访问。防止多线程同时读写 config。*/std::lock_guard<std::recursive_mutex> lock(mutex_);// 移除传入字符串中可能存在的 \n 或 \r,防止注入或破坏配置格式。TrimAfterNewLine(section);TrimAfterNewLine(property);TrimAfterNewLine(value);// section 和 property 名不能为空,否则断言失败(开发期调试用)ASSERT_LOG(!section.empty(), "Empty section name not allowed");ASSERT_LOG(!property.empty(), "Empty property name not allowed");/* 判断是否为“设备节一般如 [Adapter], [Metrics], [Global] 属于非设备配置节;而类似 [Device_XX:XX:XX:XX:XX:XX] 才是设备配置节。*/if (!IsDeviceSection(section)) {/*存入 information_sections_(非设备类配置)如果该节尚不存在,则创建;将 property -> value 插入;通知配置已更改。适用于如 [Adapter] 节下的 Name、ScanMode 等普通配置项。*/auto section_iter = information_sections_.find(section);if (section_iter == information_sections_.end()) {section_iter = information_sections_.try_emplace_back(section, common::ListMap<std::string, std::string>{}).first;}section_iter->second.insert_or_assign(property, std::move(value));PersistentConfigChangedCallback();return;}/*如果是设备配置节(Device_...),检查是否可持久化保存如果该设备还不在 persistent_devices_ 中,且当前 property 是可持久化的(如 LinkKey),尝试从临时设备中迁移。*/auto section_iter = persistent_devices_.find(section);if (section_iter == persistent_devices_.end() && IsPersistentProperty(property)) {// move paired devices or create new paired device when a link key is set/*从 temporary_devices_ 迁移或新建一项若该设备的属性存在于临时设备列表中,则将其“转正”迁入 persistent;否则新建。场景举例:连接配对时第一次保存 link key,从临时状态迁移为持久配对状态。*/auto section_properties = temporary_devices_.extract(section);if (section_properties) {section_iter = persistent_devices_.try_emplace_back(section, std::move(section_properties->second)).first;} else {section_iter = persistent_devices_.try_emplace_back(section, common::ListMap<std::string, std::string>{}).first;}}/*安全模式下加密敏感属性值如果开启了安全模式(如 CC Mode)并且属性为敏感字段(如 LinkKey、LE_KEY_PENC 等):通过 KeyStore 接口尝试加密存储;若成功,则设置为标记字符串 value = "$encrypted" 表示已加密。
*/if (section_iter != persistent_devices_.end()) {bool is_encrypted = value == kEncryptedStr;if ((!value.empty()) && os::ParameterProvider::GetBtKeystoreInterface() != nullptr &&os::ParameterProvider::IsCommonCriteriaMode() && InEncryptKeyNameList(property) && !is_encrypted) {if (os::ParameterProvider::GetBtKeystoreInterface()->set_encrypt_key_or_remove_key(section + "-" + property, value)) {value = kEncryptedStr;}}/*插入持久化设备属性 + 通知更改将处理后的值插入设备节中;通知持久化更改(通常触发异步写入 config 文件)。
*/section_iter->second.insert_or_assign(property, std::move(value));PersistentConfigChangedCallback();return;}/*如果该设备节仍不存在,写入 temporary_devices_表示当前属性不需要持久化;保存到 temporary_devices_ 中(通常用于会话属性、未配对设备等);*/section_iter = temporary_devices_.find(section);if (section_iter == temporary_devices_.end()) {auto triple = temporary_devices_.try_emplace(section, common::ListMap<std::string, std::string>{});section_iter = std::get<0>(triple);}section_iter->second.insert_or_assign(property, std::move(value));
}
SetProperty(section, key, value)
│
├─> Trim strings & check valid
│
├─> if (section is not device) ───> write to information_sections_
│
├─> if (section is device)
│ ├─> if property is persistent & section not in persistent_devices_
│ │ ├─ try extract from temporary_devices_
│ │ └─ or create new persistent section
│ │
│ └─> if (CC mode + key needs encryption) ──> encrypt value
│
│ └─> write to persistent_devices_
│
└─> else write to temporary_devices_
-
首次连接设备,存入
temporary_devices_
; -
成功配对后写入
LinkKey
,触发迁移到persistent_devices_
; -
设置 Adapter 名称,写入
information_sections_["Adapter"]["Name"]
; -
CC 模式,存储加密后的密钥值至 keystore,config 文件只存
$encrypted
。