前面我們提到了 藍牙協議棧中的 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
。