Android源码第十三章:Bluetooth——沿一次发现走到控制器

0 阅读29分钟

做一个蓝牙设备搜索页,点下「开始发现」,startDiscovery() 已经返回 true,列表却还是空的。这个返回值告诉 App,服务接受了调用。页面上的设备,要等后续 ACTION_FOUND 广播带回来。在 Logcat 分开看 REQUEST、STARTED、FOUND 和 FINISHED,就能知道 App 在观察窗口中收到了哪些阶段的信号。

场景插画:App 提交了发现,正在等待设备结果

13.1 用 BluetoothAdapter 发现设备

BluetoothManager.getAdapter() 返回本机蓝牙的公开 SDK 入口。远端设备会由广播中的 BluetoothDevice 表示。这个示例在前台运行,要求 Android 13(API 33)及以上。先在系统设置开启蓝牙,并确认 Adapter 可用。Manifest 声明 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT,尚未授权时由 App 请求「附近设备」权限。

先登记接收器,再发起发现。下方只摘出调用顺序,附录包含空值检查、权限、结果读取和清理。

BluetoothManager manager = getSystemService(BluetoothManager.class);
BluetoothAdapter adapter = manager.getAdapter();

IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);

boolean accepted = adapter.startDiscovery();

startDiscovery() 返回本次调用是否被接受。 ACTION_FOUND 的 EXTRA_DEVICE 带来设备对象,EXTRA_RSSI 可选。收到 ACTION_DISCOVERY_FINISHED 时关闭本地观察窗口。主动结束或离开页面时,示例只对已返回 true 的观察轮次尝试 cancelDiscovery(),然后注销 Receiver。发现是共享操作,系统是否会继续扫描要看 13.8 的条件。

接收器用 RECEIVER_EXPORTED 接收 Bluetooth 进程发送的受保护系统广播,只处理以上三个 action,并检查设备 extra。示例用 neverForLocation 声明不通过蓝牙结果推导物理位置,部分 BLE 信标可能被过滤。

发现、配对与连接

经典蓝牙 BR/EDR 用 Inquiry 寻找处于可发现模式的设备。低功耗蓝牙 LE 扫描接收广播,独立入口是 BluetoothLeScanner。本章跟踪的 startDiscovery() 在固定版本中还会在控制器支持 BLE 时请求 LE 扫描,所以 App 的 ACTION_FOUND 也可能来自 LE。要认定某条结果来自经典 Inquiry,还需 HCI 事件。

配对(pairing)协商认证与密钥。绑定(bonding)把配对关系保存下来。ACL 是承载上层协议的控制器链路。Profile 规定某类业务怎样互通,例如 A2DP 音频、HID 输入或 PAN 网络。GATT 通过服务、特征值和 ATT 组织数据。看到设备、绑定完成、ACL 建立、A2DP 连接和听见声音,各有各的证据,13.7 给出了查阅入口。

13.2 在 Android Studio 中观察一次发现

  1. 在 Android Studio 新建 Empty Views Activity,选 Java、包名 com.example.btstudy、Minimum SDK 33。保留模板的 Gradle 文件,确保安装了 app 模块 compileSdk 对应的平台,且 compileSdk 至少为 33。按附录 A替换 Manifest 和 MainActivity,再同步 Gradle。
  2. 在 Device Manager 创建一台 Phone AVD,例如 Pixel 设备配置和 Android 14(API 34)Google APIs 镜像,选择适合电脑架构的系统镜像。启动后在 Android 设置检查蓝牙。选中该 AVD 并点击 Run,由 Android Studio 构建、安装和启动 App。
  3. 打开同一台 AVD 的 Logcat,用 package:com.example.btstudy tag:BtStudy 过滤。第一次点「开始一轮发现」时,如果系统询问,允许访问附近设备,再点一次发起发现。已经授权时点一次即可。保持 App 在前台,看界面或 Logcat。完成一轮后,可以再发起一次,并在观察窗口内点「结束本地观察并请求取消」。
看到什么可以判断什么
NO_ADAPTER、BLUETOOTH_OFF 或权限提示先检查镜像能力、系统蓝牙开关或附近设备授权。前置条件不满足时,本例尚未发起发现。
REQUEST accepted=trueAPI 接受调用,后续可能启动新发现,也可能共享已有发现。
REQUEST accepted=false 或 REQUEST SecurityException调用未返回受理,本例结束本地观察。权限、Adapter 状态和服务条件见 13.4。
STARTED、FOUND address=…、FINISHED分别表示观察到开始、设备结果或发现结束广播。广播没有 App 专属请求 ID。结果可能来自其他应用也在使用的发现,结束广播也未说明结束原因。
LOCAL_END reason=…本 App 已停止观察。若约 25 秒后仍未观察到结束广播,计时任务会尝试取消并退出。执行时间取决于主线程调度。

可以先关闭蓝牙,确认按钮显示 BLUETOOTH_OFF。再打开蓝牙,保持其他条件不变,观察是否走到 REQUEST。普通 AVD 用来观察 App 启动、授权、API 返回和实际收到的广播。当前镜像无可发现对端时,可能收到 FINISHED 而没有任何 FOUND。如需稳定复现经典发现结果,在一台 Android 13 及以上真机上运行同一个 App,准备一台由自己控制、已进入经典蓝牙可发现模式的设备。实验中的地址和设备名只用于本地对照。要证明发现结果来自经典 Inquiry,按附录 B 将 HCI 事件中的地址与该轮日志对上。

从 App 看向控制器

HCI(Host Controller Interface)定义 Host 协议栈与控制器之间的命令、事件和数据。JNI 在 Bluetooth 进程中把 Java 接到 Native 代码。默认 AIDL HAL 形成另一次 Binder 边界,负责把 HCI 包交给所选传输后端。

先看架构图的 Android 分层和运行位置。应用用 BluetoothAdapter 访问 Bluetooth 进程,system_server 管理服务与代理的生命周期。默认 HAL 可经 Linux HCI user channel 接驱动,也可回退到直接 fd。虚拟控制器是需要另行核对的端点。

Android Bluetooth 整体架构与默认 HAL 传输分支

流程图标出三个停点。一次调用可以被拒绝,可以复用既有发现,也可以生成新的 Inquiry。发现结果和结束广播随后进入应用观察窗口。

一次 Bluetooth 发现的请求、复用、结果与收尾流程

总时序选择一次空闲时发起、成功收到设备并自然结束的经典发现。图中沿同一组生命线展示本地投递、HCI 事件和应用主线程消费。控制器可能在 App 收到 FOUND 前就发出 Inquiry Complete,图示只是其中一种交错。提前取消在同图下半部分单独展开。

从 App 提交发现到 HCI 结果、完成与取消的完整时序

13.3 回看 Adapter、代理和 Native 回调怎样建立

回到图中的应用进程,先找 App 调用时已经持有的代理。应用取得的是 Context 创建的 BluetoothManager。其构造调用 BluetoothAdapter.createAdapter(mContext);getAdapter() 返回该字段。createAdapter 从 BluetoothFrameworkInitializer 的 service manager 取得 IBluetoothManager,再构造 Adapter。Adapter 构造登记进程级 manager callback,registerAdapter 返回的 Binder 被包装成 IBluetooth;服务上线/下线回调更新业务代理。管理者在 system_server,真正 startDiscovery 的接收者在 Bluetooth 进程,不是 BluetoothManagerService 代替协议栈搜索。

来源:packages/modules/Bluetooth/framework/java/android/bluetooth/BluetoothAdapter.java,行 1026~1032。BluetoothAdapter.java 固定源码。

        mServiceLock.writeLock().lock();
        try {
            mService = registerBluetoothManagerCallback(mManagerCallback);
        } finally {
            mServiceLock.writeLock().unlock();
        }
    }

mService 保存当前服务代理;AttributionSource 保存调用归因信息。 远端设备不是从这个 Binder 参数里创建的;它由发现事件中的地址与 RemoteDevices 缓存建立。

Bluetooth 进程内,AdapterService 默认构造 new AdapterNativeInterface(),保存为 final 字段。onCreate 的初始化加载 bluetooth_jni 并调用 mNativeInterface.init(...);init 创建 AdapterNativeCallback,后续把 BondStateMachine 和 RemoteDevices 关联进去。JNI 注册时保存 &bluetoothInterface 函数表,initNative 对 NativeInterface 和它的 callback 做 GlobalRef。这些引用让 Native 可以在异步结果到来时回调同一组 Java 对象;并非把 Java 对象内存搬进控制器。 cleanupNative 在栈清理后删除全局引用,callback.cleanup 清空其业务关联,防止继续使用已结束服务。

来源:packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp,行 919~929。com_android_bluetooth_btservice_AdapterService.cpp 固定源码。


  android_bluetooth_UidTraffic.clazz =
          (jclass)env->NewGlobalRef(env->FindClass("android/bluetooth/UidTraffic"));

  sJniAdapterServiceObj = env->NewGlobalRef(obj);
  sJniCallbacksObj = env->NewGlobalRef(env->GetObjectField(obj, sJniCallbacksField));

  if (!sBluetoothInterface) {
    return JNI_FALSE;
  }

JNI 的 obj 实际是 AdapterNativeInterface;历史变量名 sJniAdapterServiceObj 不应当作对象类型证据。属性回调写入 RemoteDevices 后,deviceFoundCallback 才用地址查出 BluetoothDevice 与 DeviceProperties。DeviceProperties 保存名称、RSSI、设备类型等信息,随后组装 Intent;应用收到的是 Parcelable 形式的设备身份和 extras,不是 Native property 指针。

13.4 发现请求从 Binder 进入搜索状态机

BluetoothAdapter.startDiscovery 检查 getState() == STATE_ON,通过 callServiceIfEnabled(s -> s.startDiscovery(mAttributionSource), false) 调用 AIDL;空代理和未开启返回 false。AdapterServiceBinder 的 startDiscovery 先用 getServiceAndEnforceCallerUserAndScan 检查可用服务、用户和扫描权限,再转到 AdapterService.startDiscovery。

服务还检查包名与 Binder UID 的归属、状态、位置相关条件,并把包名放进 mDiscoveringPackages。这是后面按包分发结果的依据;不是单纯调用 Native。若 ignoreRedundantDiscoveryIfSameState 生效且发现已在进行,重复同包可返回 false,新包可直接收到 STARTED 并共享既有发现;sendDiscoveredDevToNewPkgs 还控制缓存结果补发。即使返回 true,也可能复用已有发现,不能保证产生一条新的 Inquiry。 本文的底层主线针对空闲时的首次请求。

NativeInterface.startDiscovery 进入 startDiscoveryNative:

来源:packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp,行 983~992。com_android_bluetooth_btservice_AdapterService.cpp 固定源码。

static jboolean startDiscoveryNative(JNIEnv* /* env */, jobject /* obj */) {
  log::verbose("");

  if (!sBluetoothInterface) {
    return JNI_FALSE;
  }

  int ret = sBluetoothInterface->start_discovery();
  return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

BT_STATUS_SUCCESS 被转换成 Java true。 接着看函数表指向的 Native 实现,才能知道“成功”承诺了什么。

这张函数表也有明确身份。JNI 注册函数 register_com_android_bluetooth_btservice_AdapterService 保存导出表地址:

来源:packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp,行 1998~1998。com_android_bluetooth_btservice_AdapterService.cpp 固定源码

  sBluetoothInterface = &bluetoothInterface;

system/btif/src/bluetooth.cc 定义导出的 bt_interface_t bluetoothInterface,其中发现与取消字段分别绑定同文件的静态实现:

来源:packages/modules/Bluetooth/system/btif/src/bluetooth.cc,行 1235~1236。bluetooth.cc 固定源码

        .start_discovery = start_discovery,
        .cancel_discovery = cancel_discovery,

因此 sBluetoothInterface->start_discovery() 调到下面的 start_discovery,不是根据同名函数猜出的路径。 指针选定本进程实现,真正异步调度要到它内部的 do_in_main_thread 才发生。

来源:packages/modules/Bluetooth/system/btif/src/bluetooth.cc,行 603~610。bluetooth.cc 固定源码。

static int start_discovery(void) {
  if (!interface_ready()) {
    return BT_STATUS_NOT_READY;
  }

  do_in_main_thread(base::BindOnce(btif_dm_start_discovery));
  return BT_STATUS_SUCCESS;
}

这里先确认接口 ready,再向 Native main thread 投递 btif_dm_start_discovery。JNI 调用仍在进入它的线程上,真正的线程交接发生在 do_in_main_thread。Native 工作和 Binder 返回可以交错。示例在 App 主线程调用 SDK,并在同一主线程消费广播,因此本次调用的 REQUEST 日志会在本地接收器执行前写出。

如果把 API 改成同步等到所有发现结果,再一次性返回列表,调用方需要等完整扫描,还要处理超时、取消和其他应用共享发现的情况。当前入口无需等待发现完成,后续设备逐个通过广播到达。App 得以提前更新列表,同时要自己管理观察窗口,并接受广播没有请求 ID 的限制。

btif_dm_start_discovery 先检查已有排队搜索,初始化 inquiry 标记,再调用 BTA_DmSearch(btif_dm_search_devices_evt)。BTA_DmSearch 委托 bta_dm_disc_start_device_discovery,向搜索状态机送 BTA_DM_API_SEARCH_EVT;状态机的启动动作保存 callback,调用 BTM_StartInquiry。若 BTM 返回非 BTM_CMD_STARTED,BTA 走完成处理而非假装已开始。BTM 检查设备 up 与 inquiry 忙状态,登记 Inquiry 结果/完成事件 handler,保存结果和完成 callback,再构造命令。

来源:packages/modules/Bluetooth/system/stack/btm/btm_inq.cc,行 727~738。btm_inq.cc 固定源码。

  // TODO: Register for the inquiry interface and use that
  bluetooth::shim::GetHciLayer()->EnqueueCommand(
          InquiryBuilder::Create(lap, btm_cb.btm_inq_vars.inqparms.duration, 0),
          get_main_thread()->BindOnce([](CommandStatusView status_view) {
            log::assert_that(status_view.IsValid(), "assert failed: status_view.IsValid()");
            auto status = status_view.GetStatus();
            if (status == ErrorCode::SUCCESS) {
              BTIF_dm_report_inquiry_status_change(tBTM_INQUIRY_STATE::BTM_INQUIRY_STARTED);
            } else {
              log::info("Inquiry failed to start status: {}", ErrorCodeText(status));
            }
          }));

InquiryBuilder::Create 携带 LAP、持续单位和响应数限制;0 表示不按响应个数提前结束。 HCI Inquiry 的时长单位为 1.28 秒,不能把字段直接读成毫秒。Command Status 成功后报告 inquiry started;它只说明控制器接受命令,后续结果事件与 Inquiry Complete 独立到来。本函数也调用 BTM_StartLeScan。所选 Android 实现会在控制器支持 BLE 时请求 LE 扫描,独立扫描器的过滤与回调另有一条链路。

看动图里 App 的返回值和控制器的命令状态怎样分别写入两个对象。

应用获得受理结果后,HCI 命令仍需控制器确认

13.5 HCI 命令通过 HAL 到达控制器

HciLayer 和 HAL 之间传什么

HciLayer.EnqueueCommand 的 CommandStatusView 重载用 handler.CallOn 入队;send_next_command 在有 command credit、没有待回应命令且队列非空时,把 builder 序列化进 shared_ptr<vector<uint8_t>>,调用 hal_->sendHciCommand。命令队列保存对应 opcode 与 callback。其 credit 管理避免把任意数量命令直接塞给控制器;HAL 传输返回不等于收到 Command Status。

来源:packages/modules/Bluetooth/system/gd/hci/hci_layer.cc,行 365~372。hci_layer.cc 固定源码。

      return;
    }
    std::shared_ptr<std::vector<uint8_t>> bytes = std::make_shared<std::vector<uint8_t>>();
    BitInserter bi(*bytes);
    command_queue_.front().command->Serialize(bi);
    hal_->sendHciCommand(*bytes);

    auto cmd_view = CommandView::Create(PacketView<kLittleEndian>(bytes));

HciHalImpl.sendHciCommand 先将发送字节交给 snoop logger,再调用 backend。HciHalImpl 构造先尝试 HciBackend.CreateAidl(instance),未声明 AIDL 时才 fallback HIDL。AIDL 分支使用服务名 android.hardware.bluetooth.IBluetoothHci/default(实例参数为空时),waitForService / fromBinder 得到代理,创建 AidlHciCallbacks 反向回调实体,再 initialize。AIDL/HIDL 是本版本选择分支,不应画成前后两层。 本文细追默认 AIDL 实现。

本版 hardware/interfaces 的默认服务在 service.cpp 创建 new BluetoothHci(),通过 Bluetooth offload 的 hal/ffi.h 转入 lib.rs::__add_bluetooth_hci_service 注册 Binder 服务。AIDL sendHciCommand 经 Rust HciProxy 的模块代理链、SinkModule 和 FFI 调用实现的 BluetoothHci::sendHciCommand;不能把新目录 bluetooth_hal 下另一个 HciProxyAidl 实现接到默认服务上。默认实现的 Send 给每包附上 H4 类型字节,经 writev 写 fd;事件由 AsyncFdWatcher 读取、H4 拆包,交给 hciEventReceived callback。

默认服务的跨语言桥梁

这条桥梁要同时跟住“哪个实现对象”与“哪些 HCI 字节”。先看服务创建入口,hci 指针来自刚创建的默认 BluetoothHci:

来源:hardware/interfaces/bluetooth/aidl/default/service.cpp,行 38~39。service.cpp 固定源码。

    IBluetoothHci_addService(new BluetoothHci());
    ABinderProcess_joinThreadPool();

C++ 包装把实例指针存入 CInterface.handle,生命周期仍属于 HAL 服务;send_command 是回调函数指针,其 instance 就是这个 handle:

来源:packages/modules/Bluetooth/offload/hal/include/hal/ffi.h,行 119~121。ffi.h 固定源码。

static inline void IBluetoothHci_addService(IBluetoothHci *hci) {
  __add_bluetooth_hci_service((CInterface){
          .handle = hci,

来源:packages/modules/Bluetooth/offload/hal/include/hal/ffi.h,行 128~132。ffi.h 固定源码。

          .send_command =
                  [](void *instance, const uint8_t *data, size_t len) {
                    static_cast<IBluetoothHci *>(instance)->sendHciCommand(
                            std::vector<uint8_t>(data, data + len));
                  },

handle 确定调用哪个实现,data/len 确定交哪些字节;二者不是同一个地址。 Rust 注册函数将整个 CInterface 放进 Ffi,再把 HciProxy 包装成 Binder 服务:

来源:packages/modules/Bluetooth/offload/hal/lib.rs,行 68~76。lib.rs 固定源码。

    binder::add_service(
        &format!("{}/default", BpBluetoothHci::get_descriptor()),
        BnBluetoothHci::new_binder(
            HciProxy::new(vec![Box::new(LeAudioModuleBuilder {})], Ffi::new(cintf)),
            binder::BinderFeatures::default(),
        )
        .as_binder(),
    )
    .expect("Failed to register service");

HciProxy.initialize 构造 SinkModule 与模块代理链,登记 callbacks 的死亡监听后标记 Opened。AIDL sendHciCommand 要求处于 Opened,并调用 proxy.out_cmd;对普通 Inquiry,LeAudioModule 的默认命令分支透传给下一模块,不会把发现变成音频业务。

来源:packages/modules/Bluetooth/offload/hal/service.rs,行 114~121。service.rs 固定源码。

    fn sendHciCommand(&self, data: &[u8]) -> BinderResult<()> {
        let State::Opened { ref proxy, .. } = *self.state.read().unwrap() else {
            return Err(ExceptionCode::ILLEGAL_STATE.into());
        };

        proxy.out_cmd(data);
        Ok(())
    }

来源:packages/modules/Bluetooth/offload/hal/service.rs,行 162~164。service.rs 固定源码。

    fn out_cmd(&self, data: &[u8]) {
        self.ffi.send_command(data);
    }

末端 SinkModule 将同一片字节交给 Ffi;它持有 CInterface,在 Mutex 内用保存的 handle 和函数指针调回 C++:

来源:packages/modules/Bluetooth/offload/hal/ffi.rs,行 106~114。ffi.rs 固定源码。

    fn send_command(&self, data: &[u8]) {
        let intf = self.intf.lock().unwrap();

        // SAFETY: The C Code has initialized the `CInterface` with a valid
        //         function pointer and an initialized `handle`.
        unsafe {
            (intf.send_command)(intf.handle, data.as_ptr(), data.len());
        }
    }

调用关系为 AIDL → HciProxy → 模块代理链 → SinkModule → Ffi → CInterface.send_command(handle, data, len) → 原 BluetoothHci 实例。 这是函数委托与对象持有链,不凭跨语言边界假定新增线程。返回方向的 callback 也经包装回 AIDL;观察 H4 writev 与 fd watcher 时,调用对象已经明确。

分支一:Linux HCI user channel 与 USB

默认 initialize 先创建 NetBluetoothMgmt,尝试 openHci。该函数用 management 通道等待 HCI 设备,处理 rfkill,再创建 AF_BLUETOOTH/SOCK_RAW/BTPROTO_HCI socket,并绑定 HCI_CHANNEL_USER。这不是 HCI_CHANNEL_RAW:user channel 对设备有独占语义,绑定还受 CAP_NET_ADMIN、设备是否忙等条件限制。

来源:hardware/interfaces/bluetooth/aidl/default/net_bluetooth_mgmt.cpp,行 267~281。net_bluetooth_mgmt.cpp 固定源码。

    // Open the raw HCI socket.
    int fd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
    if (fd < 0) {
        ALOGE("unable to open raw bluetooth socket: %s", strerror(errno));
        return -1;
    }

    struct sockaddr_hci hci_addr = {
            .hci_family = AF_BLUETOOTH,
            .hci_dev = static_cast<uint16_t>(hci),
            .hci_channel = HCI_CHANNEL_USER,
    };

    // Bind the socket to the selected interface.
    if (bind(fd, (struct sockaddr*)&hci_addr, sizeof(hci_addr)) < 0) {

内核段以 kernel/common 提交 ccafb60de224d35a8bc14de33e1cd47caaaa786b 为例,覆盖 hci_sock.c、hci_core.c、btusb.c 和 hci_ldisc.c。正在运行的镜像和控制器需要各自的证据。

hci_sock_sendmsg 把首字节解释成 HCI 包类型,剥去类型字节,检查 user channel 可用包型后放入 hdev->raw_q,并 queue_work(tx_work)。hci_tx_work 取 raw_q,调用 hci_send_frame;后者调用注册在 hdev->send 的 Driver 回调。若设备由 btusb 驱动,其普通发送分支将 Command 包交给 control URB、ACL 交给 bulk URB;URB 是 USB 异步传输请求,不是蓝牙业务完成。

来源:本章取得的 Kernel 固定提交文件 btusb.c,行 2146~2155。

	switch (hci_skb_pkt_type(skb)) {
	case HCI_COMMAND_PKT:
		urb = alloc_ctrl_urb(hdev, skb);
		if (IS_ERR(urb))
			return PTR_ERR(urb);

		hdev->stat.cmd_tx++;
		return submit_or_queue_tx_urb(hdev, urb);

	case HCI_ACLDATA_PKT:

返回方向是 USB interrupt URB 完成→btusb_recv_intr 重组事件→hci_recv_frame→rx_q/rx_work→hci_send_to_sock→user socket→HAL fd watcher。hci_rx_work 在 HCI_USER_CHANNEL 且非 HCI_INIT 时,把数据送 socket 后释放 skb 并跳过 Linux 普通事件/ACL 协议处理。Android 在用户态消费 HCI 事件;Linux 在这条路径提供传输与设备管理,而没有接管 Android 的 Profile。 USB 固件下载、厂商 setup 和专用 send 回调可能覆盖普通 btusb 分支,具体硬件须继续核对 VID/PID、模块、固件与绑定。

UART 也可能由 Kernel hci_uart 提供同类 HCI 设备:hdev->send 注册为 hci_uart_send_frame,协议 enqueue 后唤醒发送,TTY receive_buf 交给协议解析。这与下面 HAL 直接打开字符设备是两种安排,不应把同一包强制画成“直接 UART→Linux L2CAP→btusb”。

分支二:直接路径与虚拟控制器

openHci 失败后,默认 HAL 使用 vendor.ser.bt-uart 或默认 /dev/hvc5,open(O_RDWR),设置 raw terminal,并以 H4Protocol 收发。路径叫 hvc 并不能证明它是物理 UART;虚拟串口可承载相同 H4 数据。缺失节点、SELinux、服务实例与 Host 端点不匹配都可能使初始化失败。本版该初始化代码在错误 callback 后还有后续语句;不要据一条错误日志推断完善的短路收尾。

在可读的 netsim 分支,HciPacketTransport.Request 把包交给 AsyncManager,同步调度后调用 RootCanal 登记的 packet callback;响应经 Send→wireless.HandleResponse 回 transport dispatcher。RootCanal DualModeController.Inquiry 解析命令参数,调用 BrEdrController.Inquiry 并发 InquiryStatus;BrEdrController 保持 inquiry 状态、定时发送/接收模拟链路层 Inquiry,结果和超时变成 HCI 事件,取消清除 inquiry 状态。它模拟控制器,不替代 Android Host 栈。

来源:tools/rootcanal/model/controller/bredr_controller.cc,行 82~99。bredr_controller.cc 固定源码。

ErrorCode BrEdrController::Inquiry(uint8_t lap, uint8_t inquiry_length, uint8_t num_responses) {
  if (num_responses > 0xff || inquiry_length < 0x1 || inquiry_length > 0x30) {
    return ErrorCode::INVALID_HCI_COMMAND_PARAMETERS;
  }

  if (inquiry_.has_value()) {
    INFO(id_, "Inquiry command is already pending");
    return ErrorCode::COMMAND_DISALLOWED;
  }

  auto now = std::chrono::steady_clock::now();
  inquiry_ = InquiryState{
          .lap = lap,
          .num_responses = num_responses,
          .next_inquiry_event = now + kInquiryInterval,
          .inquiry_timeout = now + std::chrono::milliseconds(inquiry_length * 1280),
  };

非法长度、已有 Inquiry 都能生成错误状态;没有模拟 peer 也可能正常完成且结果为空。 此处工具源码已固定提交可读;尚未把本机 Emulator 预编译 Host、启动参数、guest fd 与这一提交的 netsim/RootCanal 逐一对应。因此它是明确的候选虚拟后端,不冒充当前实例的事实。

实际辨认后端时,应同时取得:运行 fingerprint / uname、VINTF 和服务进程、HAL fd 指向的设备或 socket、sysfs Driver/总线信息、vendor.ser.bt-uart、Host 命令行与 netsim 日志。光看到 tools/netsim 目录、Profile 配置或 /dev/hvc5 的默认值,都不能确定本轮路径。

沿接口和设备端点检查 Bluetooth HCI 的传输路径

13.6 设备属性先入缓存,发现结果再广播

HCI 的 AIDL callback 到 AidlHciCallbacks.hciEventReceived;HciCallbacksImpl 先记录 snoop 并转交 HciLayer 的 hal_callbacks。它复制字节到 PacketView / EventView,用 handler.CallOn 投递 on_hci_event。此后按 event code 分派到此前登记、绑定 Native main handler 的 BTM 回调。接收 HAL callback 的线程与处理 BTM 状态的线程分开;不能把整条上行称为同一 Binder 线程。

BTM 的 on_incoming_hci_event 区分 Inquiry Complete、标准 Inquiry Result、RSSI 和 Extended 变体。结果被解析为 tBTM_INQ_RESULTS,经保存的 bta_dm_inq_results_cb 转为 tBTA_DM_SEARCH.inq_res,再触发 btif_dm_search_devices_evt 的 BTA_DM_INQ_RES_EVT。地址、RSSI、名称/EIR、设备类型被组织为 bt_property_t 数组。数组元素有 type、len、val;val 指向当时的地址/局部数据,所以异步交接前需要深拷贝。

来源:packages/modules/Bluetooth/system/btif/src/bluetooth.cc,行 1352~1361。bluetooth.cc 固定源码。

void invoke_device_found_cb(int num_properties, bt_property_t* properties) {
  do_in_jni_thread(base::BindOnce(
          [](int num_properties, bt_property_t* properties) {
            HAL_CBACK(bt_hal_cbacks, device_found_cb, num_properties, properties);
            if (properties) {
              osi_free(properties);
            }
          },
          num_properties, property_deep_copy_array(num_properties, properties)));
}

property_deep_copy_array 在入 JNI 线程任务前复制属性。任务调用 callback 后用 osi_free 释放副本。 如果异步任务只保留原指针,生产者退出后,val 指向的局部数据可能已经失效。另一种做法是让任务持有一份有明确定义的共享所有权对象,这需要生产者和消费者共同约定生命周期。当前实现将副本交给任务,代价是每次交接都要分配和复制。 Native JNI 的 device_found_callback 从属性中找到地址,转成 jbyteArray;先调用 remote_device_properties_callback,再调用 Java deviceFoundCallback。JNI 局部引用用 ScopedLocalRef 随作用域归还,异步保活依赖前面登记的 GlobalRef。

来源:packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp,行 313~318。com_android_bluetooth_btservice_AdapterService.cpp 固定源码。

  // Add device properties before announcing device found
  remote_device_properties_callback(BT_STATUS_SUCCESS, *addr, addr_type, num_properties,
                                    properties);

  sCallbackEnv->CallVoidMethod(sJniCallbacksObj, method_deviceFoundCallback, jaddr.get());
}

AdapterNativeCallback.kt 把属性更新交给 RemoteDevices。RemoteDevices 按地址缓存 BluetoothDevice / DeviceProperties,deviceFoundCallback 查缓存再调用 AdapterService.discoveryResultHandler;后者从属性构造 ACTION_FOUND,放 EXTRA_DEVICE、EXTRA_RSSI、EXTRA_NAME 等,遍历 mDiscoveringPackages,按权限和归因过滤定向给包。应用注册的 BroadcastReceiver 最终在它指定的接收调度环境执行;本例未指定 Handler,onReceive 在应用主线程。它不会直接得到 Native 栈回调线程。

先看独立副本交给谁,再看副本释放后哪份数据仍由 Java 保留。

属性深拷贝跨过 JNI 线程,Java 缓存更新后广播结果

同样的状态路径把 BT_DISCOVERY_STARTED/STOPPED 送给 AdapterProperties.discoveryStateChangeCallback。STOPPED 置 mNativeDiscovering=false、clearDiscoveryData、更新结束时间并发 ACTION_DISCOVERY_FINISHED。搜索自然完成可能还包含远端名称处理,不能把 Inquiry Complete 与 Java FINISHED 定义成同一个协议事件。

13.7 发现之后,配对、ACL 和 Profile 怎样接上

发现设备后若要普通配对,调用 device.createBond(),默认 TRANSPORT_AUTO;它经 IBluetooth.createBond→AdapterServiceBinder→AdapterService.createBond。服务检查已绑定/绑定中、双模与 feature flag 分支、enabled 和包名,登记发起者,取消发现,再向 BondStateMachine 发送 MESSAGE_CREATE_BOND。msg.obj 是 BluetoothDevice,arg1 是 transport。

BondStateMachine 默认与 AdapterService 同用 Bluetooth 进程主 Looper(默认构造传 Looper.getMainLooper),消费消息后查当前 bond state,把设备地址转 byte[]、取得地址类型,调用 NativeInterface.createBond。createBondNative 转 RawAddress;本版 LE 地址类型和 transport 会影响调用 create_bond_le 还是 create_bond。Native 函数先查 ready / pairing busy,再投递 btif_dm_create_bond;后续 BTA_DmBond 与安全管理分支协商密钥。API true、Java 已入队、Native 接受、认证完成是不同节点。 这里是配对主支路的接口解释,未逐包展开所有 SSP/SMP 或 CTKD。

Native bond_state_changed_callback 带 status、地址、transport、state、配对算法/变体与 HCI 原因回到 AdapterNativeCallback,BondStateMachine.bondStateChangeCallback 把 Native 状态转换为 BOND_BONDING/BONDED/NONE,封入 MESSAGE_BOND_STATE_CHANGE。状态机更新属性后发 ACTION_BOND_STATE_CHANGED;应用据新旧状态观察最终结果,PIN/SSP 用户确认交由系统 UI。失败必须结合 reason/status 解释,不能把回到 BOND_NONE 一概叫“取消”。

Profile 代理解决的是业务接口取得。BluetoothAdapter.getProfileProxy 按 PROFILE_CONSTRUCTORS 创建对应代理,登记 ProfileConnection,再 getProfile 得到 Binder;getProfileReply 调 connection.connect,之后通知 ServiceListener。它的 true 表示代理取得流程被接受,onServiceConnected 表示服务接口可用,不表示远端设备已连接。A2DP 的连接状态广播才描述该 Profile 的连接,真正媒体播放还需 Audio 路由与数据通道证据。本章不把音频数据交给 Inquiry 这条命令链,也不等待 Audio 章产物。

取得 Profile 代理后按接口查询连接状态,用完配对调用 closeProfileProxy;关闭代理是本地服务接口清理,不等价于断开设备音频连接。传统 A2DP 数据经 Native 音频/协议路径与 HCI ACL 或 offload 分支,LE Audio 可能涉及 ISO;具体传输与音频 HAL 在 Audio 场景继续确认。

13.8 取消请求、系统停止与本地退出

本地停止观察时,示例会尝试用 cancelDiscovery() 请求取消已受理的发现。同步返回与系统结束广播是两个观察点。若 ignoreRedundantDiscoveryIfSameState 生效,AdapterService 从 mDiscoveringPackages 移除调用包;还有其他包时可直接 true 而不下发全局取消。旧分支或最后一包才继续 Native cancel_discovery→主线程 btif_dm_cancel_discovery→BTA_DmSearchCancel→搜索状态机→BTM 取消/名称取消与最终完成。

BTM_CancelInquiry 在活跃时发送 InquiryCancel,处理其 Command Complete,并停止相关 LE 扫描;发现结束还要处理搜索状态、待处理请求和名称查询,不要求控制器在取消后补一条自然 Inquiry Complete。RootCanal 的 InquiryCancel 只在有活动 inquiry 时成功,清空状态并返回完成响应;动画和判据不能强行要求两种完成同时出现。

这里还有两条不能画成固定先后的路径。活跃 Inquiry 分支在排出取消命令后,直接继续通知并收尾搜索状态:

来源:packages/modules/Bluetooth/system/bta/dm/bta_dm_device_search.cc,行 130~135。bta_dm_device_search.cc 固定源码

static void bta_dm_search_cancel() {
  if (BTM_IsInquiryActive()) {
    BTM_CancelInquiry();
    bta_dm_search_cancel_notify();
    bta_dm_search_cmpl();
  } else if (!bta_dm_search_cb.name_discover_done) {

BTM_CancelInquiry 不等待 HCI Command Complete;bta_dm_search_cmpl 可先经 BTA_DM_DISC_CMPL_EVT 上报 STOPPED。 HCI 取消回应后来又可经 btm_process_cancel_complete 上报 STOPPED,fix_multiple_discovery_stopped_broadcast 控制重复状态过滤。Java FINISHED、应用 LOCAL_END 和控制器取消回应不是同一个终点,不能据示意排版推出全局先后保证。

在图中比较应用自己的 Receiver 关闭与系统发现继续这两个结果。

共享发现中一个应用退出后,服务仍可为其他参与包继续发现

收到 FINISHED 广播时,示例清理 Receiver 和计时任务。用户停止、离开前台或约 25 秒后仍未观察到结束时,已受理的轮次还会尝试取消。请求返回 false 或抛出异常只清理本地,避免把拒绝的请求变成对既有发现的取消。系统端是否已经登记、搜索何时停止,需要结合服务状态与 HCI 日志判断。

现象先查哪里不能直接推出什么
startDiscovery false / SecurityException蓝牙状态、SCAN/CONNECT、用户/包归因、重复请求控制器损坏
true 后无 STARTED日志观察器、排队、复用分支、Native/HCI 状态一定没发 HCI
FINISHED 且无 FOUNDpeer 可发现模式、BR/EDR 能力、距离/虚拟 peer、过滤条件本轮发现实现失败
snoop 有结果,App 没结果属性解析、权限过滤、包登记、receiver 窗口无线控制器没收到
cancel true 后系统仍在发现其他扫描包、flag、在途事件和名称查询取消已全局完成
HAL 初始化失败或死亡VINTF/实例、fd/SELinux、user channel busy、Host 端点只重试 App 即可恢复
bond 回 NONEstatus、reason、用户确认与 peer 状态只是用户取消

正常服务结束时,Native 清理栈、JNI GlobalRef 与 callback 关联;HciHalImpl 析构 reset backend,AidlHci 析构解除 death recipient 并调用 HAL.close。默认 HAL.close 停 fd watcher,关闭 user channel 或直接 fd,清空 H4、恢复 READY;clientDied 也走 close。应用进程退出使动态 receiver 随进程结束,但这不能替代示例在正常 onStop 的显式清理,也不能证明控制器瞬时停止。HAL 死亡在本版 AIDL 客户端有致命恢复策略,不应描述为透明无损重连。

13.9 按日志回查发现进度

先在 Logcat 确认 REQUEST,再看 STARTED、FOUND、FINISHED 与 LOCAL_END。需要追到系统内部时,从 AdapterService 的包登记出发,沿 Native 调度、HCI 命令和 HAL 的所选传输走到控制器,再从事件解析、属性复制、Java 缓存回到应用广播。

核对应用观察、控制器事件与各自的结束状态

按问题查符号:入口与代理看 BluetoothManager / BluetoothAdapter;归因与分发看 AdapterServiceBinder / AdapterService;异步接受看 bluetooth.cc::start_discovery;搜索状态看 bta_dm_device_search.cc;Inquiry 打包与事件看 btm_inq.cc;HCI 队列看 hci_layer.cc;传输选择看 hci_hal_impl_android.cc / hci_backend_aidl.cc / 默认 BluetoothHci.cpp;回调与释放看 invoke_device_found_cb / AdapterNativeCallback.kt / RemoteDevices。

  1. startDiscovery true 能证明已经找到设备吗?答:不能,13.4 的返回不等待异步任务完成,不能证明控制器已接受或已发现设备,也可能共享既有发现。
  2. HCI user channel 是否让 Android 使用 Linux L2CAP?答:不能这样推导,13.5 的 user channel 接收分支跳过普通 Linux 协议处理,Android 栈仍消费事件。
  3. 为什么属性需要深拷贝?答:bt_property_t.val 可能指向当前函数局部数据,13.6 的异步任务必须延长其数据生命期。
  4. FINISHED 且零设备是否失败?答:不一定;先以受控 peer 与后端能力验证前提,见 13.2、13.8。
  5. getProfileProxy 的 onServiceConnected 是否表示耳机已连接?答:仅服务接口可用,Profile 连接与音频输出需独立证据,见 13.7。
  6. 超时后取消 true,能否据此判断控制器已经停下?答:还要对照同轮 HCI 取消回应及后端日志,见附录 B。

附录 A|可复制的发现示例

按 13.2 创建项目后覆盖下面两个文件。模板的 namespace 和 applicationId 使用 com.example.btstudy。界面由 Activity 创建,无需另装依赖或添加布局资源。

app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-feature android:name="android.hardware.bluetooth" android:required="true" />
    <application android:label="Bluetooth 一轮发现" android:theme="@android:style/Theme.Material.Light.NoActionBar">
        <activity android:name=".MainActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

app/src/main/java/com/example/btstudy/MainActivity.java

package com.example.btstudy;

import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Insets;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.WindowInsets;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ScrollView;

public final class MainActivity extends Activity {
    private final Handler handler = new Handler(Looper.getMainLooper());
    private BluetoothAdapter adapter;
    private TextView status;
    private boolean active, registered, discoveryAccepted;
    private int round;
    private Runnable timeout;

    private void event(String text) {
        String line = "round=" + round + " elapsedMs=" + SystemClock.elapsedRealtime()
                + " " + text;
        Log.i("BtStudy", line);
        status.append(line + "\n");
    }

    private boolean permissionsReady() {
        return checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN)
                == PackageManager.PERMISSION_GRANTED
                && checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT)
                == PackageManager.PERMISSION_GRANTED;
    }

    private final BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override public void onReceive(Context context, Intent intent) {
            if (!active) return;
            String action = intent.getAction();
            if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
                event("STARTED");
            } else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(
                        BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
                if (device != null) {
                    String rssi = intent.hasExtra(BluetoothDevice.EXTRA_RSSI)
                            ? Short.toString(intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, (short) 0))
                            : "unknown";
                    String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
                    event("FOUND address=" + device.getAddress()
                            + " name=" + name + " rssiDbm=" + rssi);
                }
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
                event("FINISHED");
                end("finished-broadcast", false);
            }
        }
    };

    @Override public void onCreate(Bundle state) {
        super.onCreate(state);
        BluetoothManager manager = getSystemService(BluetoothManager.class);
        adapter = manager == null ? null : manager.getAdapter();
        LinearLayout box = new LinearLayout(this);
        box.setOrientation(LinearLayout.VERTICAL);
        int padding = Math.round(12 * getResources().getDisplayMetrics().density);
        box.setOnApplyWindowInsetsListener((view, insets) -> {
            Insets bars = insets.getInsets(WindowInsets.Type.systemBars());
            view.setPadding(bars.left + padding, bars.top + padding,
                    bars.right + padding, bars.bottom + padding);
            return insets;
        });
        status = new TextView(this);
        Button start = new Button(this);
        start.setText("开始一轮发现 / 请求权限");
        start.setOnClickListener(v -> begin());
        Button stop = new Button(this);
        stop.setText("结束本地观察并请求取消");
        stop.setOnClickListener(v -> end("user-stop", true));
        box.addView(start);
        box.addView(stop);
        ScrollView logView = new ScrollView(this);
        logView.addView(status);
        box.addView(logView, new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
        setContentView(box);
        event("READY 在设置开启蓝牙,然后点击开始");
    }

    private void begin() {
        if (active) { event("ALREADY_ACTIVE"); return; }
        if (!permissionsReady()) {
            requestPermissions(new String[] {Manifest.permission.BLUETOOTH_SCAN,
                    Manifest.permission.BLUETOOTH_CONNECT}, 1);
            event("授权后再次点开始;拒绝则不发请求");
            return;
        }
        if (adapter == null) {
            event("NO_ADAPTER 当前镜像未提供蓝牙 Adapter");
            return;
        }
        if (!adapter.isEnabled()) {
            event("BLUETOOTH_OFF 请在设置打开蓝牙");
            return;
        }
        round++;
        status.setText("");
        IntentFilter filter = new IntentFilter();
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        filter.addAction(BluetoothDevice.ACTION_FOUND);
        try {
            registerReceiver(receiver, filter, Context.RECEIVER_EXPORTED);
            registered = true;
            active = true;
            discoveryAccepted = adapter.startDiscovery();
            event("REQUEST accepted=" + discoveryAccepted);
            if (!discoveryAccepted) {
                end("request-rejected", false);
                return;
            }
            final int expectedRound = round;
            timeout = () -> {
                if (active && round == expectedRound) end("timeout-25s", true);
            };
            handler.postDelayed(timeout, 25_000);
        } catch (SecurityException e) {
            event("REQUEST SecurityException");
            end("permission-error", false);
        }
    }

    private void end(String reason, boolean cancel) {
        if (!active && !registered) return;
        active = false;
        if (timeout != null) handler.removeCallbacks(timeout);
        timeout = null;
        if (cancel && discoveryAccepted && permissionsReady()) {
            try { event("CANCEL accepted=" + adapter.cancelDiscovery()); }
            catch (SecurityException e) { event("CANCEL SecurityException"); }
        }
        discoveryAccepted = false;
        if (registered) { unregisterReceiver(receiver); registered = false; }
        event("LOCAL_END reason=" + reason);
    }

    @Override protected void onStop() {
        end("onStop", true);
        super.onStop();
    }
}

附录 B|对照应用日志与 HCI

13.2 的 App 可以照常由 Android Studio 安装到受控设备。打开开发者选项的 Bluetooth HCI snoop log,按测试机要求重启蓝牙。先在终端 A 设置实际序列号并保存运行基线。另一个终端须独立设置同一序列号。

export SERIAL=your-test-serial
RUN_DIR=$(mktemp -d) && (
    set -e
    adb -s "$SERIAL" shell getprop ro.build.fingerprint > "$RUN_DIR/fingerprint.txt"
    adb -s "$SERIAL" shell dumpsys bluetooth_manager > "$RUN_DIR/bluetooth-before.txt"
    printf '%s\n' "$RUN_DIR"
)

在终端 B 打开 Logcat 采集。把路径换成终端 A 实际打印的目录,确认两端设备号一致。保持观察器运行,在 App 上发起一轮,等待结束或主动停止。

export SERIAL=your-test-serial
RUN_DIR='/replace/with/printed/path'
(
    set -o pipefail
    test -d "$RUN_DIR" || exit 1
    adb -s "$SERIAL" logcat -v threadtime BtStudy:I '*:S' 2>&1 | tee "$RUN_DIR/app.log"
)

结束后在终端 A 采集系统状态和 bugreport。

test -d "$RUN_DIR" && (
    set -e
    adb -s "$SERIAL" shell dumpsys bluetooth_manager > "$RUN_DIR/bluetooth-after.txt"
    adb -s "$SERIAL" bugreport "$RUN_DIR/bluetooth-bugreport.zip"
    test -s "$RUN_DIR/bluetooth-bugreport.zip"
)

在归档中找到本轮 btsnoop 文件,再在 Wireshark 查 Inquiry(opcode 0x0401)、Command Status、Inquiry Result 系列与 Inquiry Complete。取消路径另查 Inquiry Cancel(0x0402)及 Command Complete。若归档中没有本轮 snoop,就保留 App 侧结论,按产品支持的采集入口继续定位。

elapsedMs 来自开机后的单调时间,包含休眠。Logcat 默认的日期时间是另一套时钟。先用同一轮 REQUEST 和 LOCAL_END 标出 App 观察窗口,再结合事件地址、操作时间和状态核对 HCI。Inquiry 可能早于 REQUEST,取消回应可能晚于 LOCAL_END,因此采集应从点击前开始,并延续到观察到系统结束或达到另设的采集时限。停掉终端 B 的采集,恢复 HCI snoop 设置,并按产品要求重启蓝牙。以上为跟做步骤,当前没有对应的设备运行记录。

源码版本

项目固定提交讲解范围
packages/modules/Bluetoothc77db469de80f86660bc053bec4dee0c5d4b947cSDK、Adapter 服务、BTIF/BTA/BTM、HCI、HAL FFI
hardware/interfaces0162af698935100a590b7359581ac8b1b80693e5Bluetooth 默认 AIDL HAL
tools/rootcanal559446380c3e2cf01c82608897ba5a246996fe5c虚拟控制器与 Inquiry
tools/netsim71feaeea338b3bd7eda6914cc9b9b382d752957d虚拟 HCI 包传输
device/generic/goldfish296e55aa0244e8929e393e00e34471fef2a5d662Emulator 产品配置线索
kernel/commonccafb60de224d35a8bc14de33e1cd47caaaa786bHCI socket、USB 与 TTY 驱动

参考资料