当前位置: 首页 > news >正文

安卓蓝牙扫描流程

目录

系统广播

流程图

源码跟踪


系统广播

扫描开启广播:BluetoothAdapter.ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";扫描关闭广播:BluetoothAdapter.ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";扫描结果广播:BluetoothAdapter.ACTION_FOUND = "android.bluetooth.device.action.FOUND";

流程图

安卓系统中应用Application通过蓝牙适配器接口BluetoothAdapter.startDiscovery()的调用触发搜索流程的开始,从这里开始流程分析。

源码跟踪

frameworks\base\core\java\android\bluetooth\BluetoothAdapter.java中调用 startDiscovery

    public boolean startDiscovery() {if (getState() != STATE_ON) {return false;}try {mServiceLock.readLock().lock();if (mService != null) {return mService.startDiscovery(getOpPackageName(), getAttributionTag());}} catch (RemoteException e) {Log.e(TAG, "", e);} finally {mServiceLock.readLock().unlock();}return false;}

这里的Service.startDiscovery属于aidl跨进程通信,通过IBluetooth.aidl调用远程服务中packages\apps\Bluetooth\src\com\android\bluetooth\btservice\AdapterService.java中的startDiscovery

    boolean startDiscovery(String callingPackage, @Nullable String callingFeatureId) {UserHandle callingUser = UserHandle.of(UserHandle.getCallingUserId());debugLog("startDiscovery");mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);boolean isQApp = Utils.isQApp(this, callingPackage);String permission = null;if (Utils.checkCallerHasNetworkSettingsPermission(this)) {permission = android.Manifest.permission.NETWORK_SETTINGS;} else if (Utils.checkCallerHasNetworkSetupWizardPermission(this)) {permission = android.Manifest.permission.NETWORK_SETUP_WIZARD;} else if (isQApp) {if (!Utils.checkCallerHasFineLocation(this, mAppOps, callingPackage, callingFeatureId,callingUser)) {return false;}permission = android.Manifest.permission.ACCESS_FINE_LOCATION;} else {if (!Utils.checkCallerHasCoarseLocation(this, mAppOps, callingPackage, callingFeatureId,callingUser)) {return false;}permission = android.Manifest.permission.ACCESS_COARSE_LOCATION;}synchronized (mDiscoveringPackages) {mDiscoveringPackages.add(new DiscoveringPackage(callingPackage, permission));}return startDiscoveryNative();}

调用packages\apps\Bluetooth\jni\com_android_bluetooth_btservice_AdapterService.cpp中的startDiscoveryNative

static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {ALOGV("%s", __func__);if (!sBluetoothInterface) return JNI_FALSE;int ret = sBluetoothInterface->start_discovery();return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

调到system\bt\btif\src\bluetooth.cc中的start_discovery

static int start_discovery(void) {/* sanity check */完整性检查if (!interface_ready()) return BT_STATUS_NOT_READY;return btif_dm_start_discovery();
}

start_discovery调到system\bt\btif\src\btif_dm.cc中的btif_dm_start_discovery;btif_dm.cc用于设备管理相关功能

/********************************************************************************* Function         btif_dm_start_discovery** Description      Start device discovery/inquiry** Returns          bt_status_t*******************************************************************************/
bt_status_t btif_dm_start_discovery(void) {tBTA_DM_INQ inq_params;tBTA_SERVICE_MASK services = 0;BTIF_TRACE_EVENT("%s", __func__);/* Cleanup anything remaining on index 0 */do_in_main_thread(FROM_HERE,base::Bind(&BTM_BleAdvFilterParamSetup, BTM_BLE_SCAN_COND_DELETE, 0,nullptr, base::Bind(&bte_scan_filt_param_cfg_evt, 0)));auto adv_filt_param = std::make_unique<btgatt_filt_param_setup_t>();/* Add an allow-all filter on index 0*/adv_filt_param->dely_mode = IMMEDIATE_DELY_MODE;adv_filt_param->feat_seln = ALLOW_ALL_FILTER;adv_filt_param->filt_logic_type = BTA_DM_BLE_PF_FILT_LOGIC_OR;adv_filt_param->list_logic_type = BTA_DM_BLE_PF_LIST_LOGIC_OR;adv_filt_param->rssi_low_thres = LOWEST_RSSI_VALUE;adv_filt_param->rssi_high_thres = LOWEST_RSSI_VALUE;do_in_main_thread(FROM_HERE, base::Bind(&BTM_BleAdvFilterParamSetup, BTM_BLE_SCAN_COND_ADD,0, base::Passed(&adv_filt_param),base::Bind(&bte_scan_filt_param_cfg_evt, 0)));/* TODO: Do we need to handle multiple inquiries at the same time? *//* Set inquiry params and call API */inq_params.mode = BTA_DM_GENERAL_INQUIRY | BTA_BLE_GENERAL_INQUIRY;inq_params.duration = BTIF_DM_DEFAULT_INQ_MAX_DURATION;inq_params.max_resps = BTIF_DM_DEFAULT_INQ_MAX_RESULTS;inq_params.report_dup = true;inq_params.filter_type = BTA_DM_INQ_CLR;/* TODO: Filter device by BDA needs to be implemented here *//* Will be enabled to true once inquiry busy level has been received */btif_dm_inquiry_in_progress = false;/* find nearby devices */BTA_DmSearch(&inq_params, services, bte_search_devices_evt);return BT_STATUS_SUCCESS;
}

再调到system\bt\bta\dm\bta_dm_api.cc中的BTA_DmSearch

/********************************************************************************* Function         BTA_DmSearch** Description      This function searches for peer Bluetooth devices. It*                  performs an inquiry and gets the remote name for devices.*                  Service discovery is done if services is non zero*** Returns          void*******************************************************************************/
void BTA_DmSearch(tBTA_DM_INQ* p_dm_inq, tBTA_SERVICE_MASK services,tBTA_DM_SEARCH_CBACK* p_cback) {tBTA_DM_API_SEARCH* p_msg =(tBTA_DM_API_SEARCH*)osi_calloc(sizeof(tBTA_DM_API_SEARCH));p_msg->hdr.event = BTA_DM_API_SEARCH_EVT;memcpy(&p_msg->inq_params, p_dm_inq, sizeof(tBTA_DM_INQ));p_msg->services = services;p_msg->p_cback = p_cback;p_msg->rs_res = BTA_DM_RS_NONE;bta_sys_sendmsg(p_msg);//发送到另一个线程
}

调用bta_sys_sendmsg向BTA发送扫描任务消息。bta_sys_sendmsg是蓝牙协议栈的进程间收发消息机制,比较复杂。通过搜索event标记BTA_DM_API_SEARCH_EVT可以找到system\bt\bta\dm\bta_dm_act.cc中bta_dm_rs_cback

/********************************************************************************* Function         bta_dm_rs_cback** Description      Receives the role switch complete event** Returns*******************************************************************************/
static void bta_dm_rs_cback(UNUSED_ATTR void* p1) {APPL_TRACE_WARNING("bta_dm_rs_cback:%d", bta_dm_cb.rs_event);if (bta_dm_cb.rs_event == BTA_DM_API_SEARCH_EVT) {bta_dm_cb.search_msg.rs_res =BTA_DM_RS_OK; /* do not care about the result for now */bta_dm_cb.rs_event = 0;bta_dm_search_start((tBTA_DM_MSG*)&bta_dm_cb.search_msg);//调用内部的开启扫描方法}
}

这里判断如果event是BTA_DM_API_SEARCH_EVT就调用bta本身的bta_dm_search_start方法

/********************************************************************************* Function         bta_dm_search_start** Description      Starts an inquiry*** Returns          void*******************************************************************************/
void bta_dm_search_start(tBTA_DM_MSG* p_data) {tBTM_INQUIRY_CMPL result = {};size_t len = sizeof(Uuid) * p_data->search.num_uuid;bta_dm_gattc_register();APPL_TRACE_DEBUG("%s avoid_scatter=%d", __func__,p_bta_dm_cfg->avoid_scatter);if (p_bta_dm_cfg->avoid_scatter &&(p_data->search.rs_res == BTA_DM_RS_NONE) &&bta_dm_check_av(BTA_DM_API_SEARCH_EVT)) {LOG(INFO) << __func__ << ": delay search to avoid scatter";memcpy(&bta_dm_cb.search_msg, &p_data->search, sizeof(tBTA_DM_API_SEARCH));return;}BTM_ClearInqDb(nullptr); //清楚BTM中之前保存的搜索数据/* save search params */bta_dm_search_cb.p_search_cback = p_data->search.p_cback;bta_dm_search_cb.services = p_data->search.services;osi_free_and_reset((void**)&bta_dm_search_cb.p_srvc_uuid);if ((bta_dm_search_cb.num_uuid = p_data->search.num_uuid) != 0 &&p_data->search.p_uuid != nullptr) {bta_dm_search_cb.p_srvc_uuid = (Uuid*)osi_malloc(len);*bta_dm_search_cb.p_srvc_uuid = *p_data->search.p_uuid;}result.status = BTM_StartInquiry((tBTM_INQ_PARMS*)&p_data->search.inq_params,bta_dm_inq_results_cb, bta_dm_inq_cmpl_cb);//调用btm中的启动扫描APPL_TRACE_EVENT("%s status=%d", __func__, result.status);if (result.status != BTM_CMD_STARTED) {LOG(ERROR) << __func__ << ": BTM_StartInquiry returned "<< std::to_string(result.status);result.num_resp = 0;bta_dm_inq_cmpl_cb((void*)&result);}
}

这里主要就是先清楚BTM中之前的搜索数据,然后保存搜索的参数,调用btm中BTM_StartInquiry方法进行扫描。btm中的扫描在system\bt\stack\btm\btm_inq.cc里

/********************************************************************************* Function         BTM_StartInquiry** Description      This function is called to start an inquiry.** Parameters:      p_inqparms - pointer to the inquiry information*                      mode - GENERAL or LIMITED inquiry, BR/LE bit mask*                             seperately*                      duration - length in 1.28 sec intervals (If '0', the*                                 inquiry is CANCELLED)*                      max_resps - maximum amount of devices to search for*                                  before ending the inquiry*                      filter_cond_type - BTM_CLR_INQUIRY_FILTER,*                                         BTM_FILTER_COND_DEVICE_CLASS, or*                                         BTM_FILTER_COND_BD_ADDR*                      filter_cond - value for the filter (based on*                                                          filter_cond_type)**                  p_results_cb   - Pointer to the callback routine which gets*                                called upon receipt of an inquiry result. If*                                this field is NULL, the application is not*                                notified.**                  p_cmpl_cb   - Pointer to the callback routine which gets*                                called upon completion.  If this field is*                                NULL, the application is not notified when*                                completed.* Returns          tBTM_STATUS*                  BTM_CMD_STARTED if successfully initiated*                  BTM_BUSY if already in progress*                  BTM_ILLEGAL_VALUE if parameter(s) are out of range*                  BTM_NO_RESOURCES if could not allocate resources to start*                                   the command*                  BTM_WRONG_MODE if the device is not up.*******************************************************************************/
tBTM_STATUS BTM_StartInquiry(tBTM_INQ_PARMS* p_inqparms,tBTM_INQ_RESULTS_CB* p_results_cb,tBTM_CMPL_CB* p_cmpl_cb) {tBTM_INQUIRY_VAR_ST* p_inq = &btm_cb.btm_inq_vars;if (bluetooth::shim::is_gd_shim_enabled()) {return bluetooth::shim::BTM_StartInquiry(p_inqparms, p_results_cb,p_cmpl_cb);}BTM_TRACE_API("BTM_StartInquiry: mode: %d, dur: %d, rsps: %d, flt: %d",p_inqparms->mode, p_inqparms->duration, p_inqparms->max_resps,p_inqparms->filter_cond_type);/* Only one active inquiry is allowed in this implementation.Also do not allow an inquiry if the inquiry filter is being updated */if (p_inq->inq_active || p_inq->inqfilt_active) {LOG(ERROR) << __func__ << ": BTM_BUSY";return (BTM_BUSY);} else {p_inq->scan_type = INQ_GENERAL;}/*** Make sure the device is ready ***/if (!BTM_IsDeviceUp()) {LOG(ERROR) << __func__ << ": adapter is not up";return BTM_WRONG_MODE;}if ((p_inqparms->mode & BTM_BR_INQUIRY_MASK) != BTM_GENERAL_INQUIRY &&(p_inqparms->mode & BTM_BR_INQUIRY_MASK) != BTM_LIMITED_INQUIRY &&(p_inqparms->mode & BTM_BLE_INQUIRY_MASK) != BTM_BLE_GENERAL_INQUIRY &&(p_inqparms->mode & BTM_BLE_INQUIRY_MASK) != BTM_BLE_LIMITED_INQUIRY) {LOG(ERROR) << __func__ << ": illegal inquiry mode "<< std::to_string(p_inqparms->mode);return (BTM_ILLEGAL_VALUE);}/* Save the inquiry parameters to be used upon the completion of* setting/clearing the inquiry filter */p_inq->inqparms = *p_inqparms;/* Initialize the inquiry variables */p_inq->state = BTM_INQ_ACTIVE_STATE;p_inq->p_inq_cmpl_cb = p_cmpl_cb;p_inq->p_inq_results_cb = p_results_cb;p_inq->inq_cmpl_info.num_resp = 0; /* Clear the results counter */p_inq->inq_active = p_inqparms->mode;BTM_TRACE_DEBUG("BTM_StartInquiry: p_inq->inq_active = 0x%02x",p_inq->inq_active);tBTM_STATUS status = BTM_CMD_STARTED;/* start LE inquiry here if requested */if ((p_inqparms->mode & BTM_BLE_INQUIRY_MASK)) {if (!controller_get_interface()->supports_ble()) {LOG(ERROR) << __func__ << ": trying to do LE scan on a non-LE adapter";p_inq->inqparms.mode &= ~BTM_BLE_INQUIRY_MASK;status = BTM_ILLEGAL_VALUE;} else {/* BLE for now does not support filter condition for inquiry */status = btm_ble_start_inquiry((uint8_t)(p_inqparms->mode & BTM_BLE_INQUIRY_MASK),p_inqparms->duration);if (status != BTM_CMD_STARTED) {LOG(ERROR) << __func__ << ": Error Starting LE Inquiry";p_inq->inqparms.mode &= ~BTM_BLE_INQUIRY_MASK;}}p_inqparms->mode &= ~BTM_BLE_INQUIRY_MASK;BTM_TRACE_DEBUG("BTM_StartInquiry: mode = %02x", p_inqparms->mode);}/* we're done with this routine if BR/EDR inquiry is not desired. */if ((p_inqparms->mode & BTM_BR_INQUIRY_MASK) == BTM_INQUIRY_NONE) {return status;}/* BR/EDR inquiry portion *//* If a filter is specified, then save it for later and clear the currentfilter.The setting of the filter is done upon completion of clearing of thepreviousfilter.*/switch (p_inqparms->filter_cond_type) {case BTM_CLR_INQUIRY_FILTER:p_inq->state = BTM_INQ_SET_FILT_STATE;break;case BTM_FILTER_COND_DEVICE_CLASS:case BTM_FILTER_COND_BD_ADDR:/* The filter is not being used so simply clear it;the inquiry can start after this operation */p_inq->state = BTM_INQ_CLR_FILT_STATE;p_inqparms->filter_cond_type = BTM_CLR_INQUIRY_FILTER;/* =============>>>> adding LE filtering here ????? */break;default:LOG(ERROR) << __func__ << ": invalid filter condition type "<< std::to_string(p_inqparms->filter_cond_type);return (BTM_ILLEGAL_VALUE);}/* Before beginning the inquiry the current filter must be cleared, so* initiate the command */status = btm_set_inq_event_filter(p_inqparms->filter_cond_type,&p_inqparms->filter_cond);if (status != BTM_CMD_STARTED) {LOG(ERROR) << __func__ << ": failed to set inquiry event filter";p_inq->state = BTM_INQ_INACTIVE_STATE;}return (status);
}

调用system\bt\stack\btm\btm_ble_gap.cc 中的btm_ble_start_inquiry

/********************************************************************************* Function         btm_ble_start_inquiry** Description      This function is called to start BLE inquiry procedure.*                  If the duration is zero, the periodic inquiry mode is*                  cancelled.** Parameters:      mode - GENERAL or LIMITED inquiry*                  p_inq_params - pointer to the BLE inquiry parameter.*                  p_results_cb - callback returning pointer to results*                                 (tBTM_INQ_RESULTS)*                  p_cmpl_cb - callback indicating the end of an inquiry**** Returns          BTM_CMD_STARTED if successfully started*                  BTM_NO_RESOURCES if could not allocate a message buffer*                  BTM_BUSY - if an inquiry is already active*******************************************************************************/
tBTM_STATUS btm_ble_start_inquiry(uint8_t mode, uint8_t duration) {tBTM_STATUS status = BTM_CMD_STARTED;tBTM_BLE_CB* p_ble_cb = &btm_cb.ble_ctr_cb;tBTM_INQUIRY_VAR_ST* p_inq = &btm_cb.btm_inq_vars;BTM_TRACE_DEBUG("btm_ble_start_inquiry: mode = %02x inq_active = 0x%02x",mode, btm_cb.btm_inq_vars.inq_active);/* if selective connection is active, or inquiry is already active, reject it*/if (BTM_BLE_IS_INQ_ACTIVE(p_ble_cb->scan_activity)) {BTM_TRACE_ERROR("LE Inquiry is active, can not start inquiry");return (BTM_BUSY);}if (!BTM_BLE_IS_SCAN_ACTIVE(p_ble_cb->scan_activity)) {btm_send_hci_set_scan_params(BTM_BLE_SCAN_MODE_ACTI, BTM_BLE_LOW_LATENCY_SCAN_INT,BTM_BLE_LOW_LATENCY_SCAN_WIN,btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type, SP_ADV_ALL);
#if (BLE_PRIVACY_SPT == TRUE)/* enable IRK list */btm_ble_enable_resolving_list_for_platform(BTM_BLE_RL_SCAN);
#endifp_ble_cb->inq_var.scan_type = BTM_BLE_SCAN_MODE_ACTI;p_ble_cb->inq_var.scan_duplicate_filter = BTM_BLE_DUPLICATE_DISABLE;status = btm_ble_start_scan();} else if ((p_ble_cb->inq_var.scan_interval !=BTM_BLE_LOW_LATENCY_SCAN_INT) ||(p_ble_cb->inq_var.scan_window != BTM_BLE_LOW_LATENCY_SCAN_WIN)) {BTM_TRACE_DEBUG("%s, restart LE scan with low latency scan params",__func__);btm_send_hci_scan_enable(BTM_BLE_SCAN_DISABLE, BTM_BLE_DUPLICATE_ENABLE);btm_send_hci_set_scan_params(BTM_BLE_SCAN_MODE_ACTI, BTM_BLE_LOW_LATENCY_SCAN_INT,BTM_BLE_LOW_LATENCY_SCAN_WIN,btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type, SP_ADV_ALL);btm_send_hci_scan_enable(BTM_BLE_SCAN_ENABLE, BTM_BLE_DUPLICATE_DISABLE);}if (status == BTM_CMD_STARTED) {p_inq->inq_active |= mode;p_ble_cb->scan_activity |= mode;BTM_TRACE_DEBUG("btm_ble_start_inquiry inq_active = 0x%02x",p_inq->inq_active);if (duration != 0) {/* start inquiry timer */uint64_t duration_ms = duration * 1000;alarm_set_on_mloop(p_ble_cb->inq_var.inquiry_timer, duration_ms,btm_ble_inquiry_timer_timeout, NULL);}}return status;
}

再调用btm_send_hci_scan_enable向HCI发送扫描通知

void btm_send_hci_scan_enable(uint8_t enable, uint8_t filter_duplicates) {if (controller_get_interface()->supports_ble_extended_advertising()) {btsnd_hcic_ble_set_extended_scan_enable(enable, filter_duplicates, 0x0000,0x0000);} else {btsnd_hcic_ble_set_scan_enable(enable, filter_duplicates);//真正与HCI打交道}
}

这里判断是否支持ble扩展,则调用system\bt\stack\hcic\hciblecmds.cc中的btsnd_hcic_ble_set_scan_enable

void btsnd_hcic_ble_set_scan_enable(uint8_t scan_enable, uint8_t duplicate) {BT_HDR* p = (BT_HDR*)osi_malloc(HCI_CMD_BUF_SIZE);uint8_t* pp = (uint8_t*)(p + 1);p->len = HCIC_PREAMBLE_SIZE + HCIC_PARAM_SIZE_BLE_WRITE_SCAN_ENABLE;p->offset = 0;UINT16_TO_STREAM(pp, HCI_BLE_WRITE_SCAN_ENABLE);UINT8_TO_STREAM(pp, HCIC_PARAM_SIZE_BLE_WRITE_SCAN_ENABLE);UINT8_TO_STREAM(pp, scan_enable);UINT8_TO_STREAM(pp, duplicate);btu_hcif_send_cmd(LOCAL_BR_EDR_CONTROLLER_ID, p);//这里向hci层发命令
}

这里通过和HCI层的通信,host告诉controlor蓝牙地址、数据、命令等,从而控制底层硬件发起配对操作。btu如何与hci通信也比较复杂...

扫描结果返回

主要看下扫描到的设备信息返回。开始扫描和结束扫描的流程见图

之前在调用了system\bt\bta\dm\bta_dm_act.cc中bta_dm_search_start时,传了三个参数,第一个是启动扫描所需传下去的参数,第二个bta_dm_inq_results_cb是扫描结果返回的回调,第三个是扫描完成的回调。

  result.status = BTM_StartInquiry((tBTM_INQ_PARMS*)&p_data->search.inq_params,bta_dm_inq_results_cb, bta_dm_inq_cmpl_cb);//调用btm中的启动扫描
static void bta_dm_inq_results_cb(tBTM_INQ_RESULTS* p_inq, uint8_t* p_eir,uint16_t eir_len) {tBTA_DM_SEARCH result;tBTM_INQ_INFO* p_inq_info;uint16_t service_class;result.inq_res.bd_addr = p_inq->remote_bd_addr;memcpy(result.inq_res.dev_class, p_inq->dev_class, DEV_CLASS_LEN);BTM_COD_SERVICE_CLASS(service_class, p_inq->dev_class);result.inq_res.is_limited =(service_class & BTM_COD_SERVICE_LMTD_DISCOVER) ? true : false;result.inq_res.rssi = p_inq->rssi;result.inq_res.ble_addr_type = p_inq->ble_addr_type;result.inq_res.inq_result_type = p_inq->inq_result_type;result.inq_res.device_type = p_inq->device_type;result.inq_res.flag = p_inq->flag;/* application will parse EIR to find out remote device name */result.inq_res.p_eir = p_eir;result.inq_res.eir_len = eir_len;p_inq_info = BTM_InqDbRead(p_inq->remote_bd_addr);if (p_inq_info != NULL) {/* initialize remt_name_not_required to false so that we get the name by* default */result.inq_res.remt_name_not_required = false;}if (bta_dm_search_cb.p_search_cback)bta_dm_search_cb.p_search_cback(BTA_DM_INQ_RES_EVT, &result);if (p_inq_info) {/* application indicates if it knows the remote name, inside the callbackcopy that to the inquiry data base*/if (result.inq_res.remt_name_not_required)p_inq_info->appl_knows_rem_name = true;}
}

在这里把扫描的devices数据放到result里通过bta_dm_search_cb.p_search_cback(BTA_DM_INQ_RES_EVT, &result);回传。event是BTA_DM_INQ_RES_EVT,通过搜索BTA_DM_INQ_RES_EVT发现在system\bt\btif\src\btif_dm.cc中有一个bte_search_devices_evt方法

static void bte_search_devices_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_data) {uint16_t param_len = 0;if (p_data) param_len += sizeof(tBTA_DM_SEARCH);/* Allocate buffer to hold the pointers (deep copy). The pointers will point* to the end of the tBTA_DM_SEARCH */switch (event) {case BTA_DM_INQ_RES_EVT: {if (p_data->inq_res.p_eir) param_len += p_data->inq_res.eir_len;} break;case BTA_DM_DISC_RES_EVT: {if (p_data->disc_res.raw_data_size && p_data->disc_res.p_raw_data)param_len += p_data->disc_res.raw_data_size;} break;}BTIF_TRACE_DEBUG("%s event=%s param_len=%d", __func__,dump_dm_search_event(event), param_len);/* if remote name is available in EIR, set teh flag so that stack doesnt* trigger RNR */if (event == BTA_DM_INQ_RES_EVT)p_data->inq_res.remt_name_not_required =check_eir_remote_name(p_data, NULL, NULL);btif_transfer_context(btif_dm_search_devices_evt, (uint16_t)event, (char*)p_data, param_len,(param_len > sizeof(tBTA_DM_SEARCH)) ? search_devices_copy_cb : NULL);
}

这里当扫描到设备时,回调这个方法,将上下文从BTE切换到BTIF再调用btif_dm_search_devices_evt,将扫描到的设备通过HAL_CBACK方式返回

/******************************************************************************** Function         btif_dm_search_devices_evt** Description      Executes search devices callback events in btif context** Returns          void******************************************************************************/
static void btif_dm_search_devices_evt(uint16_t event, char* p_param) {tBTA_DM_SEARCH* p_search_data;BTIF_TRACE_EVENT("%s event=%s", __func__, dump_dm_search_event(event));switch (event) {case BTA_DM_DISC_RES_EVT: {p_search_data = (tBTA_DM_SEARCH*)p_param;/* Remote name update */if (strlen((const char*)p_search_data->disc_res.bd_name)) {bt_property_t properties[1];bt_status_t status;properties[0].type = BT_PROPERTY_BDNAME;properties[0].val = p_search_data->disc_res.bd_name;properties[0].len = strlen((char*)p_search_data->disc_res.bd_name);RawAddress& bdaddr = p_search_data->disc_res.bd_addr;status =btif_storage_set_remote_device_property(&bdaddr, &properties[0]);ASSERTC(status == BT_STATUS_SUCCESS,"failed to save remote device property", status);HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb, status, &bdaddr,1, properties);}/* TODO: Services? */} break;
......省略
}

扫描到设备时,调用HAL_CBACK(bt_hal_cbacks, device_found_cb, num_properties, properties);

device_found_cb是device_found_callback方法,在packages\apps\Bluetooth\jni\com_android_bluetooth_btservice_AdapterService.cpp中实现

static void device_found_callback(int num_properties,bt_property_t* properties) {CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return;ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), NULL);int addr_index;for (int i = 0; i < num_properties; i++) {if (properties[i].type == BT_PROPERTY_BDADDR) {addr.reset(sCallbackEnv->NewByteArray(properties[i].len));if (!addr.get()) {ALOGE("Address is NULL (unable to allocate) in %s", __func__);return;}sCallbackEnv->SetByteArrayRegion(addr.get(), 0, properties[i].len,(jbyte*)properties[i].val);addr_index = i;}}if (!addr.get()) {ALOGE("Address is NULL in %s", __func__);return;}ALOGV("%s: Properties: %d, Address: %s", __func__, num_properties,(const char*)properties[addr_index].val);remote_device_properties_callback(BT_STATUS_SUCCESS,(RawAddress*)properties[addr_index].val,num_properties, properties);sCallbackEnv->CallVoidMethod(sJniCallbacksObj, method_deviceFoundCallback,addr.get());
}

该方法对应JNI为method_deviceFoundCallback,即

  method_deviceFoundCallback =env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");

对应JniCallbacks.java中的deviceFoundCallback方法

deviceFoundCallback再将扫描到的广播发送出去

packages\apps\Bluetooth\src\com\android\bluetooth\btservice\RemoteDevices.java

    void deviceFoundCallback(byte[] address) {// The device properties are already registered - we can send the intent// nowBluetoothDevice device = getDevice(address);debugLog("deviceFoundCallback: Remote Address is:" + device);DeviceProperties deviceProp = getDeviceProperties(device);if (deviceProp == null) {errorLog("Device Properties is null for Device:" + device);return;}Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);intent.putExtra(BluetoothDevice.EXTRA_CLASS,new BluetoothClass(deviceProp.mBluetoothClass));intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);final ArrayList<DiscoveringPackage> packages = sAdapterService.getDiscoveringPackages();synchronized (packages) {for (DiscoveringPackage pkg : packages) {intent.setPackage(pkg.getPackageName());sAdapterService.sendBroadcastMultiplePermissions(intent, new String[]{AdapterService.BLUETOOTH_PERM, pkg.getPermission()});}}}

相关文章:

安卓蓝牙扫描流程

目录 系统广播 流程图 源码跟踪 系统广播 扫描开启广播&#xff1a;BluetoothAdapter.ACTION_DISCOVERY_STARTED "android.bluetooth.adapter.action.DISCOVERY_STARTED";扫描关闭广播&#xff1a;BluetoothAdapter.ACTION_DISCOVERY_FINISHED "android.b…...

【视觉惯性SLAM:对极几何】

对极几何&#xff08;Epipolar Geometry&#xff09;介绍 对极几何是立体视觉中的核心内容之一&#xff0c;它描述了两个相机在观察同一个三维场景时&#xff0c;成像平面之间的几何关系。对极几何能够约束图像中对应点的位置关系&#xff0c;是双目立体匹配、三维重建、以及位…...

Stream `Collectors.toList()` 和 `Stream.toList()` 的区别(Java)

Stream Collectors.toList() 和 Stream.toList() 的区别 问题背景 在以下代码中&#xff1a; Test void test() {JSONArray nodes new JSONArray();String[] names {"df1", "df2", "df3"};for (String name : names) {JSONObject obj new …...

【Python知识】Python面向对象编程知识

Python面向对象编程知识 概述1. 类&#xff08;Class&#xff09;2. 对象&#xff08;Object&#xff09;3. 封装&#xff08;Encapsulation&#xff09;4. 继承&#xff08;Inheritance&#xff09;5. 多态&#xff08;Polymorphism&#xff09;6. 抽象&#xff08;Abstractio…...

安卓帧率获取

背景 性能优化&#xff0c;经常用到一些指标&#xff0c;诸如帧率、功耗等。对于普通app来讲&#xff0c; 之前一直使用gfxinfo指令获取丢帧率。但是这个指令无法获取游戏的帧率&#xff0c;查阅资料&#xff0c;发现SurfaceFlinger可以获取游戏帧率。 帧率获取原理 获取当前f…...

shell脚本(全)

shell脚本概述 第一个shell脚本 shell注释 shell变量 shell位置参数 shell字符串 shell内置命令 shell命令替换 输出 流程控制IF export命令 退出脚本 运行Shell脚本 实例导航 shell脚本概述 在说什么是shell脚本之前&#xff0c;先说说什么是shell。 从程序员的…...

Flask-----SQLAlchemy教程

存session session[username] username # 存储数据到 session 取session username session.get(username) render_template return render_template(index.html, usernameAlice)&#xff0c;渲染一个包含 username 变量的模板。 redirect return redirect(url_for(profil…...

【C++11】可变模板参数

目录 可变模板的定义方式 参数包的展开方式 递归的方式展开参数包 STL中的emplace相关接口函数 STL容器中emplace相关插入接口函数 ​编辑 模拟实现&#xff1a;emplace接口 C11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板&#xff0c;相比 C9…...

.NET开发人员学习书籍推荐

作为一名.NET开发人员&#xff0c;掌握相关技术是提升开发能力和拓展职业发展的关键。无论你是刚入门的新人&#xff0c;还是希望精进技术的资深开发者&#xff0c;选择合适的学习资源至关重要。下面是一些经典且实用的学习书籍推荐&#xff0c;帮助你在C#、SQL、前端开发等方面…...

jupyter切换内核方法配置问题总结

下面这个博客总结了3种不同的方法&#xff0c;很有调理&#xff0c;推荐尝试 【最全指南】如何在 Jupyter Notebook 中切换/使用 conda 虚拟环境&#xff1f; !!! 注意使用上面介绍的ipykernel方法2, 要在每一个希望被jupyter识别到的环境内【分别】安装ipykernel以及添加配置 …...

SVM理论推导

本文介绍支持向量机&#xff08;SVM&#xff09;的理论推导。 一、SVM 的基本思想 SVM 的目标是找到一个最优超平面&#xff0c;将样本分为不同的类别&#xff0c;并最大化类别间的间隔。 1. 线性可分情况下&#xff1a; 在特征空间中找到一个超平面&#xff0c;使得&#…...

如何永久解决Apache Struts文件上传漏洞

Apache Struts又双叒叕爆文件上传漏洞了。 自Apache Struts框架发布以来&#xff0c;就存在多个版本的漏洞&#xff0c;其中一些漏洞涉及到文件上传功能。这些漏洞可能允许攻击者通过构造特定的请求来绕过安全限制&#xff0c;从而上传恶意文件。虽然每次官方都发布补丁进行修…...

【Java数据结构与算法】第10-14章

第10章 树结构的基础部分 10.1 二叉树 10.1.1 为什么需要树这种数据结构 10.1.2 树示意图 10.1.3 二叉树的概念 10.1.4 二叉树遍历的说明 10.1.5 二叉树遍历应用实例(前序,中序,后序) 10.1.6 二叉树-查找指定节点 思路图解 10.1.7 二叉树-删除节点 package com.atguigu.tree;…...

MacOS M3源代码编译Qt6.8.1

编译时间过长&#xff0c;如果不想自己编译&#xff0c;可以通过如果网盘进行下载&#xff1a; 链接: https://pan.baidu.com/s/17lvF5jQ-vR6vE-KEchzrVA?pwdts26 提取码: ts26 在macOS上编译Qt 6需要一些前置步骤和工具。以下是编译Qt 6的基本步骤&#xff1a; 安装Xcode和…...

3.银河麒麟V10 离线安装Nginx

1. 下载nginx离线安装包 前往官网下载离线压缩包 2. 下载3个依赖 openssl依赖&#xff0c;前往 官网下载 pcre2依赖下载&#xff0c;前往Git下载 zlib依赖下载&#xff0c;前往Git下载 下载完成后完整的包如下&#xff1a; 如果网速下载不到请使用网盘下载 通过网盘分享的文件…...

实现 QTreeWidget 中子节点勾选状态的递归更新功能只影响跟节点的状态父节点状态不受影响

在 Qt 开发中&#xff0c;QTreeWidget 提供了树形结构的显示和交互功能。为了实现某个子节点勾选或取消勾选时&#xff0c;只影响当前节点及其子节点的状态&#xff0c;同时递归更新父节点的状态以正确显示 Qt::PartiallyChecked 或 Qt::Checked&#xff0c;我们可以借助 Qt 的…...

ubuntu24.04使用opencv4

ubuntu24.04LTS自带opencv4.5代码实例 //opencv_example.cpp #include <opencv2/opencv.hpp> #include <iostream>int main() {// 读取图像cv::Mat img cv::imread("image.jpg", cv::IMREAD_COLOR);if (img.empty()) {std::cerr << "无法读…...

R语言数据分析案例46-不同区域教育情况回归分析和探索

一、研究背景 教育是社会发展的基石&#xff0c;对国家和地区的经济、文化以及社会进步起着至关重要的作用。在全球一体化进程加速的今天&#xff0c;不同区域的教育发展水平呈现出多样化的态势。这种差异不仅体现在教育资源的分配上&#xff0c;还表现在教育成果、教育投入与…...

flink sink doris

接上文&#xff1a;一文说清flink从编码到部署上线 网上关于flink sink drois的例子较多&#xff0c;大部分不太全面&#xff0c;故本文详细说明&#xff0c;且提供完整代码。 flink doris版本对照表 1.添加依赖 <!--doris cdc--><!-- 参考&#xff1a;"https…...

《探索 Apache Spark MLlib 与 Java 结合的卓越之道》

在当今大数据与人工智能蓬勃发展的时代&#xff0c;Apache Spark MLlib 作为强大的机器学习库&#xff0c;与广泛应用的 Java 语言相结合&#xff0c;为数据科学家和开发者们提供了丰富的可能性。那么&#xff0c;Apache Spark MLlib 与 Java 结合的最佳实践究竟是什么呢&#…...

Net9解决Spire.Pdf替换文字后,文件格式乱掉解决方法

官方文档 https://www.e-iceblue.com/Tutorials/Spire.PDF/Program-Guide/Text/Find-and-replace-text-on-PDF-document-in-C.html C# 在 PDF 中查找替换文本 原文件如下图&#xff0c;替换第一行的新编码&#xff0c;把41230441044替换为41230441000 替换代码如下&#xff…...

Kafka可视化工具 Offset Explorer (以前叫Kafka Tool)

数据的存储是基于 主题&#xff08;Topic&#xff09; 和 分区&#xff08;Partition&#xff09; 的 Kafka是一个高可靠性的分布式消息系统&#xff0c;广泛应用于大规模数据处理和实时, 为了更方便地管理和监控Kafka集群&#xff0c;开发人员和运维人员经常需要使用可视化工具…...

青少年编程与数学 02-004 Go语言Web编程 21课题、应用部署

青少年编程与数学 02-004 Go语言Web编程 21课题、应用部署 一、应用部署二、GoWeb部署到WINDOWS系统中1. 安装Go环境2. 创建并编写Go Web应用3. 初始化Go模块4. 编译Go Web应用5. 配置和运行Nginx6. 运行Go Web应用7. 访问应用总结 三、GoWeb部署到LINUX系统中1. 准备Linux服务…...

009-spring-bean的实例化流程

1 spring容器初始化时&#xff0c;将xml配置的bean 信息封装在 beandefinition对象 2 所有的beandefinition存储在 beandefinitionMap的map集合中 3 spring对map进行遍历&#xff0c;使用反射创建bean实例对象 4 创建好的bean存在名为singletonObjects的map集合中 5 调用ge…...

Timsort算法

Timsort算法是一种混合、稳定且高效的排序算法&#xff0c;源自归并排序和插入排序。它通过将已识别的子序列&#xff08;称为“run”&#xff09;与现有run合并直到满足某些条件来完成排序。以下是对Timsort算法的详细解释及举例说明&#xff1a; Timsort算法概述 混合性&…...

uniapp+vue 前端防多次点击表单,防误触多次请求方法。

最近项目需求写了个uniappvue前端H5,有个页面提交表单的时候发现会有用户乱点导致数据库多条重复脏数据。故需要优化&#xff0c;多次点击表单只请求一次。 思路: 直接调用uni.showToast&#xff0c;点完按钮跳一个提交成功的提示。然后把防触摸穿透mask设置成true就行&#…...

八、Hbase

Hbase 一、NoSQL非关系型数据库简介1.NoSQL 的起因2.NoSQL 的特点3.NoSQL 面临的挑战4.NoSQL 的分类 二、HBase数据库概述1.HBase数据库简介2.HBase数据模型简介3.HBase数据模型基本概念4.Hbase概念视图(逻辑视图)5.Hbase物理视图6.Hbase主要组件7.Hbase安装8.Hbase的数据读写流…...

ubuntu安装sublime安装与免费使用

1. ubuntu安装sublime 参考官网: Linux Package Manager Repositories 2. 破解过程 打开如下网址,打开/opt/sublime_text/sublime_text https://hexed.it/ 3. 替换在hexed打开的文件中查找并替换: 4180激活方法 使用二进制编辑器 8079 0500 0f94 c2替换为 c641 05…...

Onedrive精神分裂怎么办(有变更却不同步)

Onedrive有时候会分裂&#xff0c;你在本地删除文件&#xff0c;并没有同步到云端&#xff0c;但是本地却显示同步成功。 比如删掉了一个目录&#xff0c;在本地看已经删掉&#xff0c;onedrive显示已同步&#xff0c;但是别的电脑并不会同步到这个删除操作&#xff0c;在网页版…...

图像裁剪与批量推理:解决分割和变化检测中的大图处理问题

引言 在分割、变化检测等任务中&#xff0c;我们经常会遇到一个问题&#xff1a;模型的输入尺寸是固定且较小的&#xff08;如256256或512512&#xff09;。当需要处理分辨率较高的大图时&#xff0c;直接输入到模型中显然是不切实际的。那么&#xff0c;如何高效地解决这个问…...

第4章 函数

2024年12月25日一稿 4.1 函数的定义 4.1.1 函数和像 4.1.2 函数的性质 4.1.3 常用函数 4.2 复合函数和反函数 4.2.1 复合函数 4.2.2 反函数 4.3 特征函数与模糊子集 4.4 基数的概念 4.4.1 后继与归纳集 4.4.2 自然数&#xff0c;有穷集&#xff0c;无穷集 4.4.3 基数 4.5 可数…...

【JavaEE进阶】Spring传递请求参数

目录 &#x1f38d;序言 &#x1f334;传递单个参数 &#x1f340;传递多个参数 &#x1f384;传递对象 &#x1f333;后端参数重命名&#xff08;后端参数映射&#xff09; &#x1f6a9;ReuqestParam注解 &#x1f38d;序言 访问不同的路径,就是发送不同的请求.在发送…...

在跨平台开发环境中构建高效的C++项目:从基础到最佳实践20241225

在跨平台开发环境中构建高效的C项目&#xff1a;从基础到最佳实践 引言 在现代软件开发中&#xff0c;跨平台兼容性和高效开发流程是每个工程师追求的目标。尤其是对于 C 开发者&#xff0c;管理代码的跨平台构建以及调试流程可能成为一项棘手的挑战。在本文中&#xff0c;我…...

无人零售及开源 AI 智能名片 S2B2C 商城小程序的深度剖析

摘要&#xff1a;本文聚焦无人零售这一新兴零售模式及其发展浪潮中崛起的开源 AI 智能名片 S2B2C 商城小程序。深入阐述无人零售的发展态势&#xff0c;细致剖析其驱动因素、现存问题&#xff0c;全面详细介绍小程序的功能特性、应用优势以及对无人零售的潜在价值&#xff0c;旨…...

PCL点云库入门——PCL库点云滤波算法之直通滤波(PassThrough)和条件滤波(ConditionalRemoval)

0、滤波算法概述 PCL点云库中的滤波算法是处理点云数据不可或缺的一部分&#xff0c;它们能够有效地去除噪声、提取特征或进行数据降维。例如&#xff0c;使用体素网格滤波&#xff08;VoxelGrid&#xff09;可以减少点云数据量&#xff0c;同时保留重要的形状特征。此外&#…...

v语言介绍

V 语言是一种多用途的编程语言&#xff0c;可以用于前端开发、后端开发、系统编程、游戏开发等多个领域。它的设计哲学是提供接近 C 语言的性能&#xff0c;同时简化开发过程并提高代码的安全性和可读性。接下来我会详细介绍 V 在前后端开发中的应用&#xff0c;并给出一个具体…...

GPT-O3:简单介绍

GPT-O3&#xff1a;人工智能领域的重大突破 近日&#xff0c;OpenAI发布了其最新的AI模型GPT-O3&#xff0c;这一模型在AGI评估中取得了惊人的成绩&#xff0c;展现出强大的能力和潜力。GPT-O3的出现标志着人工智能领域的重大进步&#xff0c;预计将在2025年实现更大的突破。 …...

重温设计模式--适配器模式

文章目录 适配器模式&#xff08;Adapter Pattern&#xff09;概述适配器模式UML图适配器模式的结构目标接口&#xff08;Target&#xff09;&#xff1a;适配器&#xff08;Adapter&#xff09;&#xff1a;被适配者&#xff08;Adaptee&#xff09;&#xff1a; 作用&#xf…...

API部署大模型

由于生产测试环境的服务器配置较低 不能够支撑大模型运行的配置 所以需要将大模型封装部署在A服务器上 在B服务器上进行调用 封装时可以使用FastAPI与Websocket两种通信方式进行通信 Websocket 在A服务器端部署大模型&#xff08;服务端&#xff09; import asyncio import …...

Linux -- 同步与条件变量

目录 同步 条件变量 pthread_cond_t pthread_cond_init&#xff08;初始化条件变量&#xff09; pthread_cond_destroy&#xff08;销毁条件变量&#xff09; pthread_cond_wait&#xff08;线程等待条件变量&#xff09; 重要提醒 pthread_cond_boardcast&#xff08…...

Linux之ARM(MX6U)裸机篇----1.开发环境搭建

下载开启FTP服务 作用&#xff1a;用于电脑与linux系统之前文件传输 如上&#xff0c;编辑完成后重启 Window下FTP客户端安装使用http://www.filezilla.cn/download网址下载 新建网络连接站点 主机后写虚拟机的ip地址&#xff0c;用ifconfig查出ipv4的地址 笔记本电脑中虚拟…...

【C语言】结构体模块化编程

在模块化编程中&#xff0c;结构体作为数据存储的主要方式之一&#xff0c;它不仅用于存储数据&#xff0c;还帮助实现代码的封装与隐私保护。通过将结构体定义放在 .c 文件中并使用 get_ 和 set_ 函数进行访问&#xff0c;我们可以实现对结构体数据的保护&#xff0c;同时降低…...

SpringCloudAlibaba技术栈-Nacos

1、什么是Nacos&#xff1f; Nacos是个服务中心&#xff0c;就是你项目每个功能模块都会有个名字&#xff0c;比如支付模块,我们先给这个模块起个名字就叫paymentService,然后将这个名字和这个模块的配置放到Nacos中&#xff0c;其他模块也是这样的。好处是这样能更好地管理项…...

Windows11家庭版启动Hyper-V

Hyper-V 是微软的硬件虚拟化产品&#xff0c;允许在 Windows 上以虚拟机形式运行多个操作系统。每个虚拟机都在虚拟硬件上运行&#xff0c;可以创建虚拟硬盘驱动器、虚拟交换机等虚拟设备。使用虚拟化可以运行需要较旧版本的 Windows 或非 Windows 操作系统的软件&#xff0c;以…...

《信管通低代码信息管理系统开发平台》Linux环境安装说明

1 简介 信管通低代码信息管理系统应用平台提供多环境软件产品开发服务&#xff0c;包括单机、局域网和互联网。我们专注于适用国产硬件和操作系统应用软件开发应用。为事业单位和企业提供行业软件定制开发&#xff0c;满足其独特需求。无论是简单的应用还是复杂的系统&#xff…...

第一节:电路连接【51单片机-L298N-步进电机教程】

摘要&#xff1a;本节介绍如何搭建一个51单片机L298N步进电机控制电路&#xff0c;所用材料均为常见的模块&#xff0c;简单高效的方式搭建起硬件环境 一、硬件清单 ①51单片机模块 ②恒流模块 ③开关电源 ④L298N模块 ⑤二相四线步进电机 ⑥电线若干 二、接线 三、L298N模…...

YoloDotNet 识别图像中特定关键点的位置

文章目录 1、初始化 Yolo 对象2、加载图像与检测关键点3、处理检测结果4、自定义关键点绘制和处理5、注意事项1、初始化 Yolo 对象 设置 YoloOptions,包括模型路径、模型类型(如果有专门的关键点检测模型类型则指定)、GPU 使用相关参数等。例如: var yoloOptions = new Yo…...

山景BP1048增加AT指令,实现单片机串口控制播放音乐(一)

1、设计目的 山景提供的SDK是蓝牙音箱demo&#xff0c;用户使用ADC按键或者IR遥控器&#xff0c;进行人机交互。然而现实很多场景&#xff0c;需要和单片机通信&#xff0c;不管是ADC按键或者IR接口都不适合和单片机通信。这里设计个AT指令用来和BP1048通信。AT指令如下图所示…...

Leetcode3218. 切蛋糕的最小总开销 I

题目描述&#xff1a; 有一个 m x n 大小的矩形蛋糕&#xff0c;需要切成 1 x 1 的小块。 给你整数 m &#xff0c;n 和两个数组&#xff1a; horizontalCut 的大小为 m - 1 &#xff0c;其中 horizontalCut[i] 表示沿着水平线 i 切蛋糕的开销。verticalCut 的大小为 n - 1 …...

基于自然语言处理(NLP)的智能客服系统

基于自然语言处理&#xff08;NLP&#xff09;的智能客服系统是现代客户服务领域的一项重要技术&#xff0c;它通过模拟人类对话的方式&#xff0c;为用户提供及时、准确和个性化的服务。以下是关于基于NLP的智能客服系统的一些关键要素和功能&#xff1a; 1. 自然语言理解&am…...