【android bluetooth 框架分析 04】【bt-framework 層詳解 7】【AdapterProperties介紹】

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

該函數的流程如下:

  1. 檢查 JNI 環境;
  2. 判斷 status 是否成功;
  3. 為屬性數據創建 Java 類型的數組;
  4. 將底層屬性轉換為 Java 格式;
  5. 回調 Java 層傳遞這些屬性。

4. 管理那些屬性?

1. AdapterProperties 和 DeviceProperties 共同使用

枚舉常量說明使用范圍數據類型訪問權限
🔁 適用于 Adapter 和 Remote Device
BT_PROPERTY_BDNAME設備名稱Adapter: 讀/寫Remote Device: 只讀bt_bdname_tGET / SET(Adapter)GET(Remote)
BT_PROPERTY_BDADDR設備地址Adapter & Remote DeviceRawAddressGET
BT_PROPERTY_UUIDS支持的服務 UUID 列表Remote Devicebluetooth::Uuid[]GET
BT_PROPERTY_CLASS_OF_DEVICE類別碼Remote Deviceuint32_tGET
BT_PROPERTY_TYPE_OF_DEVICE設備類型(BR/EDR/LE)Remote Devicebt_device_type_tGET
BT_PROPERTY_SERVICE_RECORD服務記錄Remote Devicebt_service_record_tGET

2. 僅 AdapterProperties 使用

枚舉常量說明使用范圍數據類型訪問權限
🧭 僅適用于 Adapter(本地適配器)
BT_PROPERTY_ADAPTER_SCAN_MODE掃描模式(可發現性)Adapterbt_scan_mode_tGET / SET
BT_PROPERTY_ADAPTER_BONDED_DEVICES已綁定設備地址列表AdapterRawAddress[]GET
BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT可發現超時時間Adapteruint32_tGET / SET
BT_PROPERTY_LOCAL_LE_FEATURES本地 LE 特性Adapterbt_local_le_features_tGET
BT_PROPERTY_LOCAL_IO_CAPS本地 IO 能力(經典藍牙)Adapterbt_io_cap_tGET / SET
BT_PROPERTY_LOCAL_IO_CAPS_BLE本地 IO 能力(BLE)Adapterbt_io_cap_tGET / 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_strbtif_config_get_int

1. btif_config_get_strbtif_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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/84770.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/84770.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/84770.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C盤瘦身?

突然發現回收站底部有橫幅辣眼睛&#xff01; 點擊深度清理跳轉C盤瘦身 點擊一鍵瘦身跳轉支付 回收站右鍵還有菜單 回收站右鍵可以通過設置關閉 回收站底部橫幅關不了&#xff01; 流氓沒人管了嗎&#xff1f;

用戶通知服務,輕松實現應用與用戶的多場景交互

用戶在使用應用時&#xff0c;經常想要了解應用程序在執行的操作&#xff0c;如下載完成、新郵件到達、發布即時的客服支付通知等&#xff0c;這些通知除了攜帶基本的文本圖片信息外&#xff0c;最好還可以支持文件上傳下載進度場景下的進度條通知&#xff0c;以及點擊通知欄可…

蘋果獲智能錢包專利,Find My生態版圖或再擴張:錢包會“說話”還能防丟

蘋果公司近日成功獲批一項突破性專利&#xff0c;揭示了一種支持Find My網絡的全新智能錢包設計方案。該錢包不僅能智能管理用戶的信用卡、身份證等實體卡片&#xff0c;更具備了追蹤定位和通過揚聲器發聲提醒的能力&#xff0c;有望成為蘋果“查找”&#xff08;Find My&#…

當機床開始“思考”,傳統“制造”到“智造”升級路上的法律暗礁

——首席數據官高鵬律師團隊創作&#xff0c;AI輔助 一、被時代推著走的工廠&#xff1a;從“鐵疙瘩”到“智能體”的陣痛 某汽車零部件廠的李廠長至今記得三年前的凌晨。為了趕上新能源車企的訂單&#xff0c;廠里咬牙引進了兩條智能生產線&#xff0c;可調試第三天&#xff…

概率基礎——不確定性的數學

第05篇&#xff1a;概率基礎——不確定性的數學 寫在前面&#xff1a;大家好&#xff0c;我是藍皮怪&#xff01;前幾篇我們聊了統計學的基本概念、數據類型、描述性統計和數據可視化&#xff0c;今天我們要進入統計學的另一個重要基礎——概率論。你有沒有想過&#xff0c;為什…

爬蟲遇到base64編碼(非常規版)

一.特征 從 Base64 的核心特性入手&#xff0c;比如它的編碼原理&#xff08;將二進制數據轉換為 ASCII 字符集&#xff09;和字符集的組成&#xff08;A-Z、a-z、0-9、、/ 和 &#xff09;。這是 Base64 最基礎的特點&#xff0c;幾乎每個回答都應該包括這些內容。基于 64 個…

節拍定時器是什么?

節拍定時器是什么&#xff1f; 節拍定時器&#xff08;SysTick Timer&#xff09;是嵌入式系統中用于提供精確時間基準的核心硬件組件&#xff0c;尤其在ARM Cortex-M系列處理器中廣泛應用。以下是其關鍵特性和應用的綜合說明&#xff1a; ?? 一、核心概念與工作原理 硬件基…

SDPA(Scaled Dot-Product Attention)詳解

SDPA&#xff08;Scaled Dot-Product Attention&#xff09;詳解 SDPA&#xff08;Scaled Dot-Product Attention&#xff0c;縮放點積注意力&#xff09;是 Transformer 模型的核心計算單元&#xff0c;最早由 Vaswani 等人在 2017 年的論文《Attention Is All You Need》提出…

java通過hutool工具生成二維碼實現掃碼跳轉功能

實現&#xff1a; 首先引入zxing和hutool工具依賴 <dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.5.2</version></dependency><dependency><groupId>com.google.zxi…

數據庫數據導出到Excel表格

1.后端代碼 第一步&#xff1a;UserMapper定義根據ID列表批量查詢用戶方法 // 批量查詢用戶信息List<User> selectUserByIds(List<Integer> ids); 第二步&#xff1a;UserMapper.xml寫動態SQL&#xff0c;實現批量查詢用戶 <!--根據Ids批量查詢用戶-->&l…

Altera系列FPGA基于ADV7180解碼PAL視頻,純verilog去隔行,提供2套Quartus工程源碼和技術支持

目錄 1、前言工程概述免責聲明 2、相關方案推薦我已有的所有工程源碼總目錄----方便你快速找到自己喜歡的項目Altera系列FPGA相關方案推薦我這里已有的PAL視頻解碼方案 3、設計思路框架工程設計原理框圖輸入PAL相機ADV7180芯片解讀BT656視頻解碼模塊圖像緩存架構輸出視頻格式轉…

【教程】Windows安全中心掃描設置排除文件

轉載請注明出處&#xff1a;小鋒學長生活大爆炸[xfxuezhagn.cn] 如果本文幫助到了你&#xff0c;歡迎[點贊、收藏、關注]哦~ 目錄 背景說明 解決方法 背景說明 即使已經把實時防護等設置全都關了&#xff0c;但Windows還是會不定時給你掃描&#xff0c;然后把風險軟件給刪了…

OPenCV CUDA模塊立體匹配------對立體匹配生成的視差圖進行雙邊濾波處理類cv::cuda::DisparityBilateralFilter

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 cv::cuda::DisparityBilateralFilter 是 OpenCV CUDA 模塊中的一個類&#xff0c;用于對立體匹配生成的視差圖進行雙邊濾波處理。這種濾波方法可…

自然語言處理期末復習

自然語言處理期末復習 一單元 自然語言處理基礎 兩個核心任務&#xff1a; 自然語言理解&#xff08;NLU, Natural Language Understanding&#xff09; 讓計算機“讀懂”人類語言&#xff0c;理解文本的語義、結構和意圖。 典型子任務包括&#xff1a;分詞、詞性標注、句法分…

黃仁勛在2025年巴黎VivaTech大會上的GTC演講:AI工廠驅動的工業革命(上)

引言 2025年6月12日,在巴黎VivaTech大會上,英偉達創始人兼CEO黃仁勛發表了題為"AI工廠驅動的工業革命"的GTC主題演講。這場持續約1小時35分鐘的演講不僅詳細闡述了英偉達在AI基礎設施、智能體技術、量子計算及機器人領域的最新突破,更系統性地勾勒出了人工智能如…

DMC-E 系列總線控制卡----雷賽板卡介紹(六)

應用軟件開發方法 DMC-E 系列總線運動控制卡的應用軟件可以在 Visual Basic 、 Visual C++ 、 C# 等高級語言 環境下開發。應用軟件開發之前,需保證 DMC-E 系列總線運動控制卡連接好從站,通過控制 卡 Motion 的 EtherCAT 總線配置界面掃描從站、設置總線通信周期…

題目類型——左右逢源

1、針對的題目&#xff1a;&#xff08;不一定正確或完整&#xff09; 數據結構為數組之類的線性結構&#xff08;也許可以拓展&#xff09;&#xff0c;于是數組中每個元素和其他元素的相對關系為左右或前后需要對數組中每個元素求解或者說最終解要根據每個元素的解得出每個元…

RAG檢索前處理

1. 查詢構建&#xff08;包括Text2SQL&#xff09; 查詢構建的相關技術棧&#xff1a; Text-to-SQLText-to-Cypher 從查詢中提取元數據&#xff08;Self-query Retriever&#xff09; 1.1 Text-to-SQL&#xff08;關系數據庫&#xff09; 1.1.1 大語言模型方法Text-to-SQL樣…

OmoFun動漫官網,動漫共和國最新入口|網頁版

OmoFun 動漫&#xff0c;又叫動漫共和國&#xff0c;是一個專注于提供豐富動漫資源的在線平臺&#xff0c;深受廣大動漫愛好者的喜愛。它匯集了海量的動漫資源&#xff0c;涵蓋日本動漫、國產動漫、歐美動漫等多種類型&#xff0c;無論是最新上映的熱門番劇還是經典老番&#x…

ue5的blender4.1groom毛發插件v012安裝和使用方法(排除了沖突錯誤)

關鍵出錯不出錯是看這個文件pyalembic-1.8.8-cp311-cp311-win_amd64.whl&#xff0c;解決和Alembic SQL工具&#xff09;的加載沖突&#xff01; 其他blender版本根據其內部的python版本選擇對應的文件解壓安裝。 1、安裝插件&#xff01;把GroomExporter_v012_Blender4.1.1(原…