SDP服務搜索流程源碼分析

BREDR的設備 在進行配對完成以後,進行;鏈接以前都要進行服務的搜索,服務搜索走的流程是SDP,這篇文章就分析一下,bluedroid中SDP的代碼流程,咱們從配對完成的回調函數開始分析:數據庫

/*******************************************************************************
**
** Function         btif_dm_auth_cmpl_evt
**
** Description      Executes authentication complete event in btif context
**
** Returns          void
**
*******************************************************************************/
static void btif_dm_auth_cmpl_evt (tBTA_DM_AUTH_CMPL *p_auth_cmpl)
{
    /* Save link key, if not temporary */
    bt_bdaddr_t bd_addr;
    bt_status_t status = BT_STATUS_FAIL;
    bt_bond_state_t state = BT_BOND_STATE_NONE;
    BOOLEAN skip_sdp = FALSE;
...
    if (p_auth_cmpl->success)
    {
        btif_update_remote_properties(p_auth_cmpl->bd_addr,
                                      p_auth_cmpl->bd_name, NULL, p_auth_cmpl->dev_type);
        pairing_cb.timeout_retries = 0;
        status = BT_STATUS_SUCCESS;
        state = BT_BOND_STATE_BONDED;
        bdcpy(bd_addr.address, p_auth_cmpl->bd_addr);

        if (check_sdp_bl(&bd_addr) && check_cod_hid(&bd_addr, COD_HID_MAJOR))
        {
            LOG_WARN("%s:skip SDP", __FUNCTION__);
            skip_sdp = TRUE;
        }
        if(!pairing_cb.is_local_initiated && skip_sdp)
        {
           ...
        }
        else
        {
            /* Trigger SDP on the device */
            pairing_cb.sdp_attempts = 1;;
...
            if(btif_dm_inquiry_in_progress)
                btif_dm_cancel_discovery();

            btif_dm_get_remote_services(&bd_addr);//進行服務搜索
        }
        // Do not call bond_state_changed_cb yet. Wait until remote service discovery is complete
    }
...

 我這裏分析的設備是音箱,會直接走SDP的流程。也就是會執行上面的btif_dm_get_remote_services函數,這裏注意一點就是,何時上報配對的狀態?從上面的註釋能夠看到,bluedroid 並不會在配對完成就上報配對的狀態,而是要等服務搜索完成。咱們繼續分析,服務搜索的流程:api

/*******************************************************************************
**
** Function         btif_dm_get_remote_services
**
** Description      Start SDP to get remote services
**
** Returns          bt_status_t
**
*******************************************************************************/
bt_status_t btif_dm_get_remote_services(bt_bdaddr_t *remote_addr)
{
    bdstr_t bdstr;

    BTA_DmDiscover(remote_addr->address, BTA_ALL_SERVICE_MASK,
                   bte_dm_search_services_evt, TRUE);

    return BT_STATUS_SUCCESS;
}

 

調用到BTA的BTA_DmDiscover,將搜索的 指令開始往下發,其第一個參數是設備的地址,第二個參數是BTA_ALL_SERVICE_MASK = 0x3fffffff,第三個參數是回調,這樣的回調咱們見的太多,它的命名格式也是大體相同:bte_XXX_evt,服務器

最後一個參數表明是否進行SDP 搜索,這裏是true,這個函數實如今bta_dm_api.c裏面,這裏的函數,十有八九都是組建一個msg,而後調用bta_sys_sendmsg 把消息發送到BTU task 來處理,咱們繼續分析:app

/*******************************************************************************
**
** Function         BTA_DmDiscover
**
** Description      This function does service discovery for services of a
**                  peer device
**
**
** Returns          void
**
*******************************************************************************/
void BTA_DmDiscover(BD_ADDR bd_addr, tBTA_SERVICE_MASK services,
                    tBTA_DM_SEARCH_CBACK *p_cback, BOOLEAN sdp_search)
{
    tBTA_DM_API_DISCOVER    *p_msg;

    if ((p_msg = (tBTA_DM_API_DISCOVER *) GKI_getbuf(sizeof(tBTA_DM_API_DISCOVER))) != NULL)
    {
        memset(p_msg, 0, sizeof(tBTA_DM_API_DISCOVER));

        p_msg->hdr.event = BTA_DM_API_DISCOVER_EVT;//discover msg
        bdcpy(p_msg->bd_addr, bd_addr);
        p_msg->services = services;
        p_msg->p_cback = p_cback;
        p_msg->sdp_search = sdp_search;
        bta_sys_sendmsg(p_msg);
    }
}

 

這裏向BTU task 發送BTA_DM_API_DISCOVER_EVT :(BTA got event 0x202)ide

這裏device manager search (BTA_ID_DM_SEARCH)註冊到系統的處理句柄 是:函數

static const tBTA_SYS_REG bta_dm_search_reg =
{
    bta_dm_search_sm_execute,
    bta_dm_search_sm_disable
};

 

咱們繼續看:ui

/*******************************************************************************
**
** Function         bta_dm_search_sm_execute
**
** Description      State machine event handling function for DM
**
**
** Returns          void
**
*******************************************************************************/
BOOLEAN bta_dm_search_sm_execute(BT_HDR *p_msg)
{
    tBTA_DM_ST_TBL      state_table;
    UINT8               action;
    int                 i;

    APPL_TRACE_EVENT("bta_dm_search_sm_execute state:%d, event:0x%x",
        bta_dm_search_cb.state, p_msg->event);

    /* look up the state table for the current state */
    state_table = bta_dm_search_st_tbl[bta_dm_search_cb.state];

    bta_dm_search_cb.state = state_table[p_msg->event & 0x00ff][BTA_DM_SEARCH_NEXT_STATE];


    /* execute action functions */
    for (i = 0; i < BTA_DM_SEARCH_ACTIONS; i++)
    {
        if ((action = state_table[p_msg->event & 0x00ff][i]) != BTA_DM_SEARCH_IGNORE)
        {
            (*bta_dm_search_action[action])( (tBTA_DM_MSG*) p_msg);
        }
        else
        {
            break;
        }
    }
    return TRUE;
}

 

狀態機輪轉,一看也是熟悉的套路。咱們看看狀態的轉換吧,一開始確定是idle 的狀態:bta_dm_search_idle_st_table:this

/* API_SEARCH_DISC */       {BTA_DM_API_DISCOVER,              BTA_DM_SEARCH_IGNORE,          BTA_DM_DISCOVER_ACTIVE},

 

下一個狀態是BTA_DM_DISCOVER_ACTIVE,執行的動做是BTA_DM_API_DISCOVER,執行的函數:spa

/*******************************************************************************
**
** Function         bta_dm_discover
**
** Description      Discovers services on a remote device
**
**
** Returns          void
**
*******************************************************************************/
void bta_dm_discover (tBTA_DM_MSG *p_data)
{
    /* save the search condition */
    bta_dm_search_cb.services = p_data->discover.services;

    bta_dm_search_cb.p_search_cback = p_data->discover.p_cback;//前面註冊callbak = bte_dm_search_services_evt
    bta_dm_search_cb.sdp_search = p_data->discover.sdp_search;//true
    bta_dm_search_cb.services_to_search = bta_dm_search_cb.services;//0x3fffffff
    bta_dm_search_cb.service_index = 0;
    bta_dm_search_cb.services_found = 0;
    bta_dm_search_cb.peer_name[0] = 0;
    bta_dm_search_cb.sdp_search = p_data->discover.sdp_search;//bluedroid 居然執行了兩遍
    bta_dm_search_cb.p_btm_inq_info = BTM_InqDbRead (p_data->discover.bd_addr);//查找數據庫
    bta_dm_search_cb.transport = p_data->discover.transport;

    bta_dm_search_cb.name_discover_done = FALSE;//標誌 名字搜索沒有完成
    memcpy(&bta_dm_search_cb.uuid, &p_data->discover.uuid, sizeof(tSDP_UUID));
    bta_dm_discover_device(p_data->discover.bd_addr);//開始搜索
}

 下面咱們接着分析bta_dm_discover_device:code

/*******************************************************************************
**
** Function         bta_dm_discover_device
**
** Description      Starts name and service discovery on the device
**
** Returns          void
**
*******************************************************************************/
static void bta_dm_discover_device(BD_ADDR remote_bd_addr)
{
    tBTA_DM_MSG * p_msg;
    tBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR;

    /* Reset transport state for next discovery */
    bta_dm_search_cb.transport = BTA_TRANSPORT_UNKNOWN;//這裏爲啥這樣設置?看上面註釋,爲了下一次的搜索。這一次的搜索由於知道了transport

    bdcpy(bta_dm_search_cb.peer_bdaddr, remote_bd_addr);
...

    /* if name discovery is not done and application needs remote name */
    if ((!bta_dm_search_cb.name_discover_done)
       && (( bta_dm_search_cb.p_btm_inq_info == NULL )
            ||(bta_dm_search_cb.p_btm_inq_info && (!bta_dm_search_cb.p_btm_inq_info->appl_knows_rem_name))))
    {
        if (bta_dm_read_remote_device_name(bta_dm_search_cb.peer_bdaddr, transport) == TRUE)//若是須要discovery name ,那麼將繼續進行
            return;

        /* starting name discovery failed */
        bta_dm_search_cb.name_discover_done = TRUE;
    }
    APPL_TRACE_DEBUG("bta_dm_search_cb.services %d libs_liu", bta_dm_search_cb.services);

    /* if application wants to discover service */
    if ( bta_dm_search_cb.services )//0x3fffffff
    {
        /* initialize variables */
        bta_dm_search_cb.service_index      = 0;
        bta_dm_search_cb.services_found     = 0;
        bta_dm_search_cb.services_to_search = bta_dm_search_cb.services;
...
        /* if seaching with EIR is not completed */
        if(bta_dm_search_cb.services_to_search)
        {
            /* check whether connection already exists to the device
               if connection exists, we don't have to wait for ACL
               link to go down to start search on next device */
            if (BTM_IsAclConnectionUp(bta_dm_search_cb.peer_bdaddr, BT_TRANSPORT_BR_EDR))
                bta_dm_search_cb.wait_disc = FALSE;
            else
                bta_dm_search_cb.wait_disc = TRUE;
...
            {
                bta_dm_search_cb.sdp_results = FALSE;
                bta_dm_find_services(bta_dm_search_cb.peer_bdaddr);//繼續向下傳達搜索指令
                return;
            }
        }
    }

    /* name discovery and service discovery are done for this device */
   ...
}

 

咱們直接看bta_dm_find_services:這裏是真正的搜索的邏輯:

/*******************************************************************************
**
** Function         bta_dm_find_services
**
** Description      Starts discovery on a device
**
** Returns          void
**
*******************************************************************************/
static void bta_dm_find_services ( BD_ADDR bd_addr)
{

    tSDP_UUID    uuid;
    UINT16       num_attrs = 1;
    tBTA_DM_MSG *p_msg;

    memset (&uuid, 0, sizeof(tSDP_UUID));

    while(bta_dm_search_cb.service_index < BTA_MAX_SERVICE_ID)//32,涉及不少的sevice,gatt、source、sink、hsp、hfp、map等
    {
        if( bta_dm_search_cb.services_to_search
            & (tBTA_SERVICE_MASK)(BTA_SERVICE_ID_TO_SERVICE_MASK(bta_dm_search_cb.service_index)))//搜索全部的服務
        {
            if((bta_dm_search_cb.p_sdp_db = (tSDP_DISCOVERY_DB *)GKI_getbuf(BTA_DM_SDP_DB_SIZE)) != NULL)
            {
                /* try to search all services by search based on L2CAP UUID */
                if(bta_dm_search_cb.services == BTA_ALL_SERVICE_MASK )
                {
                    if (bta_dm_search_cb.services_to_search & BTA_RES_SERVICE_MASK)/*reserved,id = 1,這裏發現了其處理和其餘的service不同*/
                    {
                        uuid.uu.uuid16 = bta_service_id_to_uuid_lkup_tbl[0];//這個reserved 服務就是UUID_SERVCLASS_PNP_INFORMATION
                        bta_dm_search_cb.services_to_search &= ~BTA_RES_SERVICE_MASK;//消除掉這個標誌位,下次就會進入到else 流程
                    }
                    else
                    {
                        uuid.uu.uuid16 = UUID_PROTOCOL_L2CAP;//搜索全部基於l2cap的服務
                        bta_dm_search_cb.services_to_search = 0;//注意這裏設置爲0,
                    }
                }
                else
                {
...
                }

                if (uuid.len == 0)
                    uuid.len = LEN_UUID_16;

                if (bta_dm_search_cb.service_index == BTA_USER_SERVICE_ID)
                {
                    memcpy(&uuid, &bta_dm_search_cb.uuid, sizeof(tSDP_UUID));
                }

                SDP_InitDiscoveryDb (bta_dm_search_cb.p_sdp_db, BTA_DM_SDP_DB_SIZE, 1, &uuid, 0, NULL);//對SDP的數據庫進行初始化

                memset(g_disc_raw_data_buf, 0, sizeof(g_disc_raw_data_buf));
                bta_dm_search_cb.p_sdp_db->raw_data = g_disc_raw_data_buf;

                bta_dm_search_cb.p_sdp_db->raw_size = MAX_DISC_RAW_DATA_BUF;

                if (!SDP_ServiceSearchAttributeRequest (bd_addr, bta_dm_search_cb.p_sdp_db, &bta_dm_sdp_callback))//調用到sdp_api.c裏面的接口
                {
                    /* if discovery not successful with this device
                    proceed to next one */
                    GKI_freebuf(bta_dm_search_cb.p_sdp_db);
                    bta_dm_search_cb.p_sdp_db = NULL;
                    bta_dm_search_cb.service_index = BTA_MAX_SERVICE_ID;

                }
                else
                {
                    bta_dm_search_cb.service_index++;
                    return;//直接返回,這裏猜測,前一次搜索結果回來以後,應該仍是會觸發下一次繼續進行搜索
                }
            }
            else
            {
                APPL_TRACE_ERROR("#### Failed to allocate SDP DB buffer! ####");
            }
        }

        bta_dm_search_cb.service_index++;
    }

    /* no more services to be discovered *///所有搜索完成會發送BTA_DM_DISCOVERY_RESULT_EVT
    if(bta_dm_search_cb.service_index >= BTA_MAX_SERVICE_ID)
    {
        if ((p_msg = (tBTA_DM_MSG *) GKI_getbuf(sizeof(tBTA_DM_MSG))) != NULL)
        {
            p_msg->hdr.event = BTA_DM_DISCOVERY_RESULT_EVT;
            p_msg->disc_result.result.disc_res.services = bta_dm_search_cb.services_found;
            bdcpy (p_msg->disc_result.result.disc_res.bd_addr, bta_dm_search_cb.peer_bdaddr);
            BCM_STRNCPY_S((char*)p_msg->disc_result.result.disc_res.bd_name, sizeof(BD_NAME),
                    bta_dm_get_remname(), (BD_NAME_LEN-1));

            /* make sure the string is terminated */
            p_msg->disc_result.result.disc_res.bd_name[BD_NAME_LEN-1] = 0;

            bta_sys_sendmsg(p_msg);
        }
    }
}

 

從上面的流程,咱們能夠知道,它是首先進行UUID_SERVCLASS_PNP_INFORMATION的搜索,而後再進行全部基於L2cap的服務的搜索,同時將bta_dm_search_cb.services_to_search設置爲0,當全部的服務搜索完成以後,會上報BTA_DM_DISCOVERY_RESULT_EVT 進行處理。那大概的流程,咱們心理已經基本清楚,咱們再繼續詳細分析其流程:

咱們首先分析SDP_ServiceSearchAttributeRequest的代碼流程:

/*******************************************************************************
**
** Function         SDP_ServiceSearchAttributeRequest
**
** Description      This function queries an SDP server for information.
**
**                  The difference between this API function and the function
**                  SDP_ServiceSearchRequest is that this one does a
**                  combined ServiceSearchAttributeRequest SDP function.
**                  (This is for Unplug Testing)
**
** Returns          TRUE if discovery started, FALSE if failed.
**
*******************************************************************************/
BOOLEAN SDP_ServiceSearchAttributeRequest (UINT8 *p_bd_addr, tSDP_DISCOVERY_DB *p_db,
                                           tSDP_DISC_CMPL_CB *p_cb)
{
#if SDP_CLIENT_ENABLED == TRUE
    tCONN_CB     *p_ccb;

    /* Specific BD address */
    p_ccb = sdp_conn_originate (p_bd_addr);//創建sdp 的channel

    if (!p_ccb)
        return(FALSE);

    p_ccb->disc_state = SDP_DISC_WAIT_CONN;
    p_ccb->p_db       = p_db;
    p_ccb->p_cb       = p_cb;

    p_ccb->is_attr_search = TRUE;

    return(TRUE);
#else
    return(FALSE);
#endif
}

 

這裏發現,每次SDP_ServiceSearchAttributeRequest 的時候都會執行sdp_conn_originate,那咱們也能夠預期,可是這一次的SDP的搜索行爲結束(收到response),應該就會把這個channel 斷開,從log 中也確實是如此的。

下面咱們繼續看看sdp_conn_originate的實現:

/*******************************************************************************
**
** Function         sdp_conn_originate
**
** Description      This function is called from the API to originate a
**                  connection.
**
** Returns          void
**
*******************************************************************************/
tCONN_CB* sdp_conn_originate (UINT8 *p_bd_addr)
{
    tCONN_CB              *p_ccb;
    UINT16                cid;

    /* Allocate a new CCB. Return if none available. */
    if ((p_ccb = sdpu_allocate_ccb()) == NULL)//在sdp_cb.ccb中分配新的channel cb,sdp_cb裏面保存了最大ccb是4
    {
        SDP_TRACE_WARNING ("SDP - no spare CCB for orig");
        return (NULL);
    }

    /* We are the originator of this connection */
    p_ccb->con_flags |= SDP_FLAGS_IS_ORIG;

    /* Save the BD Address and Channel ID. */
    memcpy (&p_ccb->device_address[0], p_bd_addr, sizeof (BD_ADDR));

    /* Transition to the next appropriate state, waiting for connection confirm. */
    p_ccb->con_state = SDP_STATE_CONN_SETUP;

    cid = L2CA_ConnectReq (SDP_PSM, p_bd_addr);//爲此次transaction 創建l2cap 鏈接

    /* Check if L2CAP started the connection process */
    if (cid != 0)
    {
        p_ccb->connection_id = cid;

        return (p_ccb);
    }
    else
    {
        SDP_TRACE_WARNING ("SDP - Originate failed");
        sdpu_release_ccb (p_ccb);
        return (NULL);
    }
}

 

其實也很簡單,主要就是先分配相應的l2cap的channel,爲了下面的SDP流程作好準備。

從hci 的log中 能夠看到,l2cap channel 創建起來以後,還會進行對於l2cap 傳輸參數的配置,log 截圖以下:

要分析這個過程,咱們須要先 看一下sdp的 註冊到l2cap中的狀況,實現是在sdp_init 中,這是在整個bluedroid 初始化的時候就會執行,下面咱們分析一下整個函數:

/*******************************************************************************
**
** Function         sdp_init
**
** Description      This function initializes the SDP unit.
**
** Returns          void
**
*******************************************************************************/
void sdp_init (void)
{
    /* Clears all structures and local SDP database (if Server is enabled) */
    memset (&sdp_cb, 0, sizeof (tSDP_CB));

    /* Initialize the L2CAP configuration. We only care about MTU and flush */
    sdp_cb.l2cap_my_cfg.mtu_present       = TRUE;
    sdp_cb.l2cap_my_cfg.mtu               = SDP_MTU_SIZE;//672
    sdp_cb.l2cap_my_cfg.flush_to_present  = TRUE;
    sdp_cb.l2cap_my_cfg.flush_to          = SDP_FLUSH_TO;

    sdp_cb.max_attr_list_size             = SDP_MTU_SIZE - 16;
    sdp_cb.max_recs_per_search            = SDP_MAX_DISC_SERVER_RECS;
...
#if SDP_CLIENT_ENABLED == TRUE
    /* Register with Security Manager for the specific security level */
    if (!BTM_SetSecurityLevel (TRUE, SDP_SERVICE_NAME, BTM_SEC_SERVICE_SDP_SERVER,
                               SDP_SECURITY_LEVEL, SDP_PSM, 0, 0))
    {
        SDP_TRACE_ERROR ("Security Registration for Client failed");
        return;
    }
#endif

#if defined(SDP_INITIAL_TRACE_LEVEL)
    sdp_cb.trace_level = SDP_INITIAL_TRACE_LEVEL;//設置trace level
#else
    sdp_cb.trace_level = BT_TRACE_LEVEL_NONE;    /* No traces */
#endif

    sdp_cb.reg_info.pL2CA_ConnectInd_Cb = sdp_connect_ind;//接受到對端發過來的鏈接消息
    sdp_cb.reg_info.pL2CA_ConnectCfm_Cb = sdp_connect_cfm;//接受到對端的鏈接反饋
    sdp_cb.reg_info.pL2CA_ConnectPnd_Cb = NULL;
    sdp_cb.reg_info.pL2CA_ConfigInd_Cb  = sdp_config_ind;//收到對端的配置信息
    sdp_cb.reg_info.pL2CA_ConfigCfm_Cb  = sdp_config_cfm;//收到對端的配置反饋
    sdp_cb.reg_info.pL2CA_DisconnectInd_Cb = sdp_disconnect_ind;
    sdp_cb.reg_info.pL2CA_DisconnectCfm_Cb = sdp_disconnect_cfm;
    sdp_cb.reg_info.pL2CA_QoSViolationInd_Cb = NULL;
    sdp_cb.reg_info.pL2CA_DataInd_Cb = sdp_data_ind;
    sdp_cb.reg_info.pL2CA_CongestionStatus_Cb = NULL;
    sdp_cb.reg_info.pL2CA_TxComplete_Cb       = NULL;

    /* Now, register with L2CAP */
    if (!L2CA_Register (SDP_PSM, &sdp_cb.reg_info))//註冊到來l2cap
    {
        SDP_TRACE_ERROR ("SDP Registration failed");
    }

}

 

這裏的註冊和GATT的註冊的思路也是同樣的。

當對端 回覆咱們 connect 的消息的時候,執行sdp_connect_cfm,咱們看看其具體的實現:

/*******************************************************************************
**
** Function         sdp_connect_cfm
**
** Description      This function handles the connect confirm events
**                  from L2CAP. This is the case when we are acting as a
**                  client and have sent a connect request.
**
** Returns          void
**
*******************************************************************************/
static void sdp_connect_cfm (UINT16 l2cap_cid, UINT16 result)
{
    tCONN_CB    *p_ccb;
    tL2CAP_CFG_INFO cfg;

    /* Find CCB based on CID */
    if ((p_ccb = sdpu_find_ccb_by_cid (l2cap_cid)) == NULL)
    {
        SDP_TRACE_WARNING ("SDP - Rcvd conn cnf for unknown CID 0x%x", l2cap_cid);
        return;
    }

    /* If the connection response contains success status, then */
    /* Transition to the next state and startup the timer.      */
    if ((result == L2CAP_CONN_OK) && (p_ccb->con_state == SDP_STATE_CONN_SETUP))
    {
        p_ccb->con_state = SDP_STATE_CFG_SETUP;

        cfg = sdp_cb.l2cap_my_cfg;

        if (cfg.fcr_present)
        {
/*打印*/
        }

        if ((!L2CA_ConfigReq (l2cap_cid, &cfg)) && cfg.fcr_present
             && cfg.fcr.mode != L2CAP_FCR_BASIC_MODE)//開始配置
        {
            /* FCR not desired; try again in basic mode */
            cfg.fcr_present = FALSE;
            cfg.fcr.mode = L2CAP_FCR_BASIC_MODE;
            L2CA_ConfigReq (l2cap_cid, &cfg);
        }

        SDP_TRACE_EVENT ("SDP - got conn cnf, sent cfg req, CID: 0x%x", p_ccb->connection_id);
    }
    else
    {
        ...
     }
        sdpu_release_ccb (p_ccb);
    }
}

 

從上面的代碼,咱們看到其從Code: Connection Request到Code: Configure Request流程的轉換,咱們繼續看sdp_config_cfm的實現,預計這裏應該會轉到真正作服務搜索的動做。前面咱們在分析SDP_ServiceSearchAttributeRequest的時候是沒有看見其作搜索的動做,只是在挑選一些參數,而後作l2cap的鏈接。

/*******************************************************************************
**
** Function         sdp_config_cfm
**
** Description      This function processes the L2CAP configuration confirmation
**                  event.
**
** Returns          void
**
*******************************************************************************/
static void sdp_config_cfm (UINT16 l2cap_cid, tL2CAP_CFG_INFO *p_cfg)
{
    tCONN_CB    *p_ccb;

    /* Find CCB based on CID */
    if ((p_ccb = sdpu_find_ccb_by_cid (l2cap_cid)) == NULL)
    {
        SDP_TRACE_WARNING ("SDP - Rcvd L2CAP cfg ind, unknown CID: 0x%x", l2cap_cid);
        return;
    }

    /* For now, always accept configuration from the other side */
    if (p_cfg->result == L2CAP_CFG_OK)
    {
        p_ccb->con_flags |= SDP_FLAGS_MY_CFG_DONE;

        if (p_ccb->con_flags & SDP_FLAGS_HIS_CFG_DONE)
        {
            p_ccb->con_state = SDP_STATE_CONNECTED;

            if (p_ccb->con_flags & SDP_FLAGS_IS_ORIG)
                sdp_disc_connected (p_ccb);//這裏開始引向搜索
            else
                /* Start inactivity timer */
                btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_SDP, SDP_INACT_TIMEOUT);//若是咱們不是org,那麼過timeout 時間會斷開
        }
    }
    else
    {
        /* If peer has rejected FCR and suggested basic then try basic */
        if (p_cfg->fcr_present)//若是失敗會retry
        {
            tL2CAP_CFG_INFO cfg = sdp_cb.l2cap_my_cfg;
            cfg.fcr_present = FALSE;
            L2CA_ConfigReq (l2cap_cid, &cfg);

            /* Remain in configure state */
            return;
        }

#if SDP_CLIENT_ENABLED == TRUE
        sdp_disconnect(p_ccb, SDP_CFG_FAILED);
#endif
    }
}

 

 上面過程的核心的就是sdp_disc_connected ,

/*******************************************************************************
**
** Function         sdp_disc_connected
**
** Description      This function is called when an SDP discovery attempt is
**                  connected.
**
** Returns          void
**
*******************************************************************************/
void sdp_disc_connected (tCONN_CB *p_ccb)
{
    if (p_ccb->is_attr_search)
    {
        p_ccb->disc_state = SDP_DISC_WAIT_SEARCH_ATTR;

        process_service_search_attr_rsp (p_ccb, NULL);
    }
    else
    {
        /* First step is to get a list of the handles from the server. */
        /* We are not searching for a specific attribute, so we will   */
        /* first search for the service, then get all attributes of it */

        p_ccb->num_handles = 0;
        sdp_snd_service_search_req(p_ccb, 0, NULL);//開始搜索
    }

}

 

也就是 紅框中的過程:

這裏發現,當數據傳輸完畢,還會把這條l2cap channel 解除掉,咱們繼續分析源碼來尋找該過程的實現:

能夠猜測,該過程確定是在 sdp response中引向 channel 斷開的流程的。咱們先看看sdp response的執行函數:

根據sdp_init的註冊信息:

  sdp_cb.reg_info.pL2CA_DataInd_Cb = sdp_data_ind;

 

咱們一會兒就能想到 執行的是 sdp_data_ind,在sdp_init 中,咱們沒有發現實際上是處理client 端的信息仍是server端的信息,這種狀況說明確定會在這個函數中細分,根據不一樣的狀況,路由到不一樣的函數去處理:

/*******************************************************************************
**
** Function         sdp_data_ind
**
** Description      This function is called when data is received from L2CAP.
**                  if we are the originator of the connection, we are the SDP
**                  client, and the received message is queued up for the client.
**
**                  If we are the destination of the connection, we are the SDP
**                  server, so the message is passed to the server processing
**                  function.
**
** Returns          void
**
*******************************************************************************/
static void sdp_data_ind (UINT16 l2cap_cid, BT_HDR *p_msg)
{
    tCONN_CB    *p_ccb;

    /* Find CCB based on CID */
    if ((p_ccb = sdpu_find_ccb_by_cid (l2cap_cid)) != NULL)
    {
        if (p_ccb->con_state == SDP_STATE_CONNECTED)
        {
            if (p_ccb->con_flags & SDP_FLAGS_IS_ORIG)
                sdp_disc_server_rsp (p_ccb, p_msg);//本地是client
            else
                sdp_server_handle_client_req (p_ccb, p_msg);
        }
        else
        {
            SDP_TRACE_WARNING ("SDP - Ignored L2CAP data while in state: %d, CID: 0x%x",
                                p_ccb->con_state, l2cap_cid);
        }
    }
    else
    {
        SDP_TRACE_WARNING ("SDP - Rcvd L2CAP data, unknown CID: 0x%x", l2cap_cid);
    }

    GKI_freebuf (p_msg);
}

 

咱們繼續看看sdp_disc_server_rsp的實現:這裏面主要作的就是 對於服務器發過來的信息的保存和處理,具體就不分析了:

/*******************************************************************************
**
** Function         process_service_search_attr_rsp
**
** Description      This function is called when there is a search attribute
**                  response from the server.
**
** Returns          void
**
*******************************************************************************/
static void process_service_search_attr_rsp (tCONN_CB *p_ccb, UINT8 *p_reply)
{
    UINT8           *p, *p_start, *p_end, *p_param_len;
    UINT8           type;
    UINT32          seq_len;
    UINT16          param_len, lists_byte_count = 0;
    BOOLEAN         cont_request_needed = FALSE;
...
    /* Since we got everything we need, disconnect the call */
    sdp_disconnect (p_ccb, SDP_SUCCESS);//所有處理完成,斷開這個ccb鏈接
}

 

 分析到這裏,其實咱們只分析了搜索service的一個回合,sdp 的搜索每每有不少回合的,下面咱們分析一下,本次搜索介紹是如何觸發下一個回合的搜索的。這裏咱們能夠本身先思考一下,第一次搜索的時候,咱們搜索的reserved 的服務,而且在bta_dm_search_cb中把已經搜索過的標誌清掉了:bta_dm_search_cb.services_to_search &= ~BTA_RES_SERVICE_MASK

那咱們猜測,下一次搜索前確定仍是會判斷bta_dm_search_cb.services_to_search來check 服務搜索是否完成,

下面咱們繼續分析,看看對端給本端發了sdp disconnect 的response 以後,本端是如何處理的:

/*******************************************************************************
**
** Function         sdp_disconnect_cfm
**
** Description      This function handles a disconnect confirm event from L2CAP.
**
** Returns          void
**
*******************************************************************************/
static void sdp_disconnect_cfm (UINT16 l2cap_cid, UINT16 result)
{
    tCONN_CB    *p_ccb;
    UNUSED(result);

    /* Find CCB based on CID */
    if ((p_ccb = sdpu_find_ccb_by_cid (l2cap_cid)) == NULL)
    {
        SDP_TRACE_WARNING ("SDP - Rcvd L2CAP disc cfm, unknown CID: 0x%x", l2cap_cid);
        return;
    }

    /* Tell the user if he has a callback */
    if (p_ccb->p_cb)
        (*p_ccb->p_cb) (p_ccb->disconnect_reason);//調用callback=bta_dm_sdp_callback
    else if (p_ccb->p_cb2)
        (*p_ccb->p_cb2) (p_ccb->disconnect_reason, p_ccb->user_data);

    sdpu_release_ccb (p_ccb);//這裏才真正去釋放ccb
}

 

咱們繼續看bta_dm_sdp_callback回調:

/*******************************************************************************
**
** Function         bta_dm_sdp_callback
**
** Description      Callback from sdp with discovery status
**
** Returns          void
**
*******************************************************************************/
static void bta_dm_sdp_callback (UINT16 sdp_status)
{

    tBTA_DM_SDP_RESULT * p_msg;

    if ((p_msg = (tBTA_DM_SDP_RESULT *) GKI_getbuf(sizeof(tBTA_DM_SDP_RESULT))) != NULL)
    {
        p_msg->hdr.event = BTA_DM_SDP_RESULT_EVT;//0x205
        p_msg->sdp_result = sdp_status;
        bta_sys_sendmsg(p_msg);

    }
}

 

咱們看看這個時間的處理:

BOOLEAN bta_dm_search_sm_execute(BT_HDR *p_msg)
{
    tBTA_DM_ST_TBL      state_table;
    UINT8               action;
    int                 i;
    /* look up the state table for the current state */
    state_table = bta_dm_search_st_tbl[bta_dm_search_cb.state];

    bta_dm_search_cb.state = state_table[p_msg->event & 0x00ff][BTA_DM_SEARCH_NEXT_STATE];

    /* execute action functions */
    for (i = 0; i < BTA_DM_SEARCH_ACTIONS; i++)
    {
        if ((action = state_table[p_msg->event & 0x00ff][i]) != BTA_DM_SEARCH_IGNORE)
        {
            (*bta_dm_search_action[action])( (tBTA_DM_MSG*) p_msg);
        }
    }
    return TRUE;
}

 

看看其狀態表:

/* SDP_RESULT_EVT */        {BTA_DM_SDP_RESULT,                BTA_DM_SEARCH_IGNORE,          BTA_DM_DISCOVER_ACTIVE},

 

下一個狀態仍是BTA_DM_DISCOVER_ACTIVE,執行的行爲是BTA_DM_SDP_RESULT:咱們看看其實現,由於代碼量很是的大,這裏刪除了一些無關的代碼:

    void bta_dm_sdp_result (tBTA_DM_MSG *p_data)
    {
    
        tSDP_DISC_REC    *p_sdp_rec = NULL;
        tBTA_DM_MSG     *p_msg;
        BOOLEAN          scn_found = FALSE;
        UINT16             service = 0xFFFF;
        tSDP_PROTOCOL_ELEM    pe;
    
        UINT32 num_uuids = 0;
        UINT8  uuid_list[32][MAX_UUID_SIZE]; // assuming a max of 32 services
    
        if((p_data->sdp_event.sdp_result == SDP_SUCCESS)
            || (p_data->sdp_event.sdp_result == SDP_NO_RECS_MATCH)
            || (p_data->sdp_event.sdp_result == SDP_DB_FULL))//處理結果
    {
    do
    {
        p_sdp_rec = NULL;
        if( bta_dm_search_cb.service_index == BTA_USER_SERVICE_ID+1) 
        {
...
        }
        else
        {
        service = bta_service_id_to_uuid_lkup_tbl[bta_dm_search_cb.service_index-1];//轉換成相應的UUID
        p_sdp_rec = SDP_FindServiceInDb(bta_dm_search_cb.p_sdp_db, service, p_sdp_rec);//講搜索到的service 都保存在p_sdp_rec中
                }
                
                {
                    /* SDP_DB_FULL means some records with the
                       required attributes were received */
        if (((p_data->sdp_event.sdp_result == SDP_DB_FULL) &&
        bta_dm_search_cb.services != BTA_ALL_SERVICE_MASK) ||
                            (p_sdp_rec != NULL))
            {
        if (service != UUID_SERVCLASS_PNP_INFORMATION)//說明是基於l2cap的搜索
                {
            UINT16 tmp_svc = 0xFFFF;
            bta_dm_search_cb.services_found |=    (tBTA_SERVICE_MASK)(BTA_SERVICE_ID_TO_SERVICE_MASK(bta_dm_search_cb.service_index-1));
            tmp_svc = bta_service_id_to_uuid_lkup_tbl[bta_dm_search_cb.service_index-1];
                            /* Add to the list of UUIDs */
                            sdpu_uuid16_to_uuid128(tmp_svc, uuid_list[num_uuids]);//保存在uuid_list 裏面
                            num_uuids++;
                        }
                    }
                }
    
                if(bta_dm_search_cb.services == BTA_ALL_SERVICE_MASK &&
                    bta_dm_search_cb.services_to_search == 0)
                {
                        bta_dm_search_cb.service_index++;
                }
                else /* regular one service per search or PNP search */
                    break;
                }
    while(bta_dm_search_cb.service_index <= BTA_MAX_SERVICE_ID);
    
    //          GKI_freebuf(bta_dm_search_cb.p_sdp_db);
    //          bta_dm_search_cb.p_sdp_db = NULL;
    
            /* Collect the 128-bit services here and put them into the list */
            if(bta_dm_search_cb.services == BTA_ALL_SERVICE_MASK)
            {
                p_sdp_rec = NULL;
                do
                {
                    tBT_UUID temp_uuid;
                    /* find a service record, report it */
                    p_sdp_rec = SDP_FindServiceInDb_128bit(bta_dm_search_cb.p_sdp_db, p_sdp_rec);
                    if (p_sdp_rec)
                    {
                        if (SDP_FindServiceUUIDInRec_128bit(p_sdp_rec, &temp_uuid))
                        {
                            memcpy(uuid_list[num_uuids], temp_uuid.uu.uuid128, MAX_UUID_SIZE);
                            num_uuids++;
                        }
                    }
                } while (p_sdp_rec);
            }
            /* if there are more services to search for */
            if(bta_dm_search_cb.services_to_search)//若是隻是搜索了reserved的服務,那麼繼續搜索
            {
                /* Free up the p_sdp_db before checking the next one */
                bta_dm_free_sdp_db(NULL);
                bta_dm_find_services(bta_dm_search_cb.peer_bdaddr);
            }
            else//不然發送BTA_DM_DISCOVERY_RESULT_EVT 消息
            {
                /* callbacks */
                /* start next bd_addr if necessary */
    BTM_SecDeleteRmtNameNotifyCallback(&bta_dm_service_search_remname_cback);
    
    
                if ((p_msg = (tBTA_DM_MSG *) GKI_getbuf(sizeof(tBTA_DM_MSG))) != NULL)
                {
                    p_msg->hdr.event = BTA_DM_DISCOVERY_RESULT_EVT;
                    p_msg->disc_result.result.disc_res.result = BTA_SUCCESS;
                    p_msg->disc_result.result.disc_res.p_raw_data = NULL;
                    p_msg->disc_result.result.disc_res.raw_data_size = 0;
                    p_msg->disc_result.result.disc_res.num_uuids = num_uuids;
                    p_msg->disc_result.result.disc_res.p_uuid_list = NULL;
                    if (num_uuids > 0) {
                        p_msg->disc_result.result.disc_res.p_uuid_list = (UINT8*)GKI_getbuf(num_uuids*MAX_UUID_SIZE);
                        if (p_msg->disc_result.result.disc_res.p_uuid_list) {
                            memcpy(p_msg->disc_result.result.disc_res.p_uuid_list, uuid_list,
                                   num_uuids*MAX_UUID_SIZE);
                        }
                        ...
                    }
                    //copy the raw_data to the discovery result  structure
                    //
    
                    if (  bta_dm_search_cb.p_sdp_db != NULL && bta_dm_search_cb.p_sdp_db->raw_used != 0   &&
                        bta_dm_search_cb.p_sdp_db->raw_data != NULL) {
    
                        p_msg->disc_result.result.disc_res.p_raw_data = GKI_getbuf(bta_dm_search_cb.p_sdp_db->raw_used);
                        if ( NULL != p_msg->disc_result.result.disc_res.p_raw_data    ) {
                            memcpy(     p_msg->disc_result.result.disc_res.p_raw_data,
                                        bta_dm_search_cb.p_sdp_db->raw_data,
                                        bta_dm_search_cb.p_sdp_db->raw_used );
    
                            p_msg->disc_result.result.disc_res.raw_data_size =
                                bta_dm_search_cb.p_sdp_db->raw_used;
    
                        }
                        ...
                        bta_dm_search_cb.p_sdp_db->raw_data = NULL;     //no need to free this - it is a global assigned.
                        bta_dm_search_cb.p_sdp_db->raw_used = 0;
                        bta_dm_search_cb.p_sdp_db->raw_size = 0;
                    }

                    /* Done with p_sdp_db. Free it */
                    bta_dm_free_sdp_db(NULL);
                    p_msg->disc_result.result.disc_res.services = bta_dm_search_cb.services_found;
                    ...
                    bdcpy (p_msg->disc_result.result.disc_res.bd_addr, bta_dm_search_cb.peer_bdaddr);
                    BCM_STRNCPY_S((char*)p_msg->disc_result.result.disc_res.bd_name, sizeof(BD_NAME),
                            bta_dm_get_remname(), (BD_NAME_LEN-1));
                    /* make sure the string is null terminated */
                    p_msg->disc_result.result.disc_res.bd_name[BD_NAME_LEN-1] = 0;
                    bta_sys_sendmsg(p_msg);
                }
            }
        }
    }

 

上面的代碼 實現看起來 比較亂,其實邏輯其實也不復雜。主要作的事情以下:

  1. 判斷是不是UUID_SERVCLASS_PNP_INFORMATION,若是是,繼續進行搜索
  2. 不然 說明已經完成了 基於L2CAP的service的搜索,那麼進行數據保存,解析,以及上報,上報調用的時間是BTA_DM_DISCOVERY_RESULT_EVT

咱們下面繼續分析BTA_DM_DISCOVERY_RESULT_EVT 的流程:

BTA got event 0x207
/* DISCV_RES_EVT */         {BTA_DM_DISC_RESULT,               BTA_DM_SEARCH_IGNORE,          BTA_DM_DISCOVER_ACTIVE},

 

發現下一個狀態依然是BTA_DM_DISCOVER_ACTIVE,執行的動做是BTA_DM_DISC_RESULT,咱們繼續看這個函數的實現:

/*******************************************************************************
**
** Function         bta_dm_disc_result
**
** Description      Service discovery result when discovering services on a device
**
** Returns          void
**
*******************************************************************************/
void bta_dm_disc_result (tBTA_DM_MSG *p_data)
{
    APPL_TRACE_EVENT("%s", __func__);
    bta_dm_search_cb.p_search_cback(BTA_DM_DISC_RES_EVT, &p_data->disc_result.result);//調用回調上報BTA_DM_DISC_RES_EVT

    tBTA_DM_MSG *p_msg = (tBTA_DM_MSG *) GKI_getbuf(sizeof(tBTA_DM_MSG));

    /* send a message to change state */
    if (p_msg != NULL)
    {
        p_msg->hdr.event = BTA_DM_SEARCH_CMPL_EVT;//再次發送event
        p_msg->hdr.layer_specific = BTA_DM_API_DISCOVER_EVT;
        bta_sys_sendmsg(p_msg);
    }
}

 

上面的代碼也是分爲兩個部分:

  1. bta_dm_search_cb.p_search_cback(BTA_DM_DISC_RES_EVT, &p_data->disc_result.result); 進行上報數據和狀態到 device manager
  2.  繼續進行狀態機的輪轉,由於此刻的狀態依然是BTA_DM_DISCOVER_ACTIVE,按照預期,最後應該是idle的狀態。

咱們先看:

bta_dm_search_cb.p_search_cback(BTA_DM_DISC_RES_EVT, &p_data->disc_result.result);

 這個回調,咱們也很熟悉,每次搜索都會有這個回調,執行的函數是bte_dm_search_services_evt,其實這個函數也是另外的函數的封裝,

 

/*******************************************************************************
**
** Function         bte_dm_search_services_evt
**
** Description      Switches context from BTE to BTIF for DM search services
**                  event
**
** Returns          void
**
*******************************************************************************/
static void bte_dm_search_services_evt(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data)
{
    UINT16 param_len = 0;
   if (p_data)
       param_len += sizeof(tBTA_DM_SEARCH);
   switch (event)
   {
         case BTA_DM_DISC_RES_EVT:
         {
             if ((p_data->disc_res.result == BTA_SUCCESS) && (p_data->disc_res.num_uuids > 0)) {
                  param_len += (p_data->disc_res.num_uuids * MAX_UUID_SIZE);
             }
         } break;
   }
   /* TODO: The only other member that needs a deep copy is the p_raw_data. But not sure
    * if raw_data is needed. */
   btif_transfer_context(btif_dm_search_services_evt, event, (char*)p_data, param_len,
         (param_len > sizeof(tBTA_DM_SEARCH)) ? search_services_copy_cb : NULL);
}

 

 

 咱們繼續看btif_dm_search_services_evt的實現,

/*******************************************************************************
**
** Function         btif_dm_search_services_evt
**
** Description      Executes search services event in btif context
**
** Returns          void
**
*******************************************************************************/
static void btif_dm_search_services_evt(UINT16 event, char *p_param)
{
    tBTA_DM_SEARCH *p_data = (tBTA_DM_SEARCH*)p_param;

    switch (event)
    {
        case BTA_DM_DISC_RES_EVT:
        {
            bt_property_t prop;
            uint32_t i = 0;
            bt_bdaddr_t bd_addr;
            bt_status_t ret;

            bdcpy(bd_addr.address, p_data->disc_res.bd_addr);

            if  ((p_data->disc_res.result != BTA_SUCCESS) &&
                 (pairing_cb.state == BT_BOND_STATE_BONDING ) &&
                 (pairing_cb.sdp_attempts < BTIF_DM_MAX_SDP_ATTEMPTS_AFTER_PAIRING))//sdp的最大的retry的次數是BTIF_DM_MAX_SDP_ATTEMPTS_AFTER_PAIRING = 2
            {
                BTIF_TRACE_WARNING("%s:SDP failed after bonding re-attempting", __FUNCTION__);
                pairing_cb.sdp_attempts++;
                btif_dm_get_remote_services(&bd_addr);//失敗以後,從新進行服務搜索
                return;
            }
            prop.type = BT_PROPERTY_UUIDS;
            prop.len = 0;
            if ((p_data->disc_res.result == BTA_SUCCESS) && (p_data->disc_res.num_uuids > 0))
            {
                 prop.val = p_data->disc_res.p_uuid_list;
                 prop.len = p_data->disc_res.num_uuids * MAX_UUID_SIZE;
                 for (i=0; i < p_data->disc_res.num_uuids; i++)
                 {
                      char temp[256];
                      uuid_to_string_legacy((bt_uuid_t*)(p_data->disc_res.p_uuid_list + (i*MAX_UUID_SIZE)), temp);
                      LOG_INFO("%s index:%d uuid:%s", __func__, i, temp);
                 }
            }

            /* onUuidChanged requires getBondedDevices to be populated.
            ** bond_state_changed needs to be sent prior to remote_device_property
            */
            if ((pairing_cb.state == BT_BOND_STATE_BONDING) &&
                ((bdcmp(p_data->disc_res.bd_addr, pairing_cb.bd_addr) == 0) ||
                 (bdcmp(p_data->disc_res.bd_addr, pairing_cb.static_bdaddr.address) == 0)) &&
                  pairing_cb.sdp_attempts > 0)
            {
                 BTIF_TRACE_DEBUG("%s Remote Service SDP done. Call bond_state_changed_cb BONDED",
                                   __FUNCTION__);
                 pairing_cb.sdp_attempts  = 0;//復原次變量,sdp流程完成

                 // If bonding occured due to cross-key pairing, send bonding callback
                 // for static address now
                 if (bdcmp(p_data->disc_res.bd_addr, pairing_cb.static_bdaddr.address) == 0)
                    bond_state_changed(BT_STATUS_SUCCESS, &bd_addr, BT_BOND_STATE_BONDING);

                 bond_state_changed(BT_STATUS_SUCCESS, &bd_addr, BT_BOND_STATE_BONDED);//sdp完成以後 才上傳綁定完成的狀態
            }

            if (p_data->disc_res.num_uuids != 0)
            {
                /* Also write this to the NVRAM */
                ret = btif_storage_set_remote_device_property(&bd_addr, &prop);//保存到文件
                /* Send the event to the BTIF */
                HAL_CBACK(bt_hal_cbacks, remote_device_properties_cb,
                                 BT_STATUS_SUCCESS, &bd_addr, 1, &prop);//調用remote_device_properties_cb 彙報到framework
            }
        }

 

上面的流程,很是簡單,就是上報綁定完成的狀態,以及彙報service discovery的結果,其實分析到這裏,咱們只是分析一個BTA_DM_DISC_RES_EVT,很容易想到,後續確定還會分析discovery 完成事件:BTA_DM_DISC_CMPL_EVT

,下面咱們繼續分析第二點:

 繼續進行狀態機的輪轉,發送BTA_DM_SEARCH_CMPL_EVT

 直接看狀態表:

/* SEARCH_CMPL_EVT */       {BTA_DM_SEARCH_CMPL,               BTA_DM_SEARCH_IGNORE,          BTA_DM_SEARCH_IDLE},

 

咱們終於看到了 狀態最後輪轉到idle 狀態了:BTA_DM_SEARCH_IDLE,執行的action是BTA_DM_SEARCH_CMPL,執行的函數是:bta_dm_search_cmpl

/*******************************************************************************
**
** Function         bta_dm_search_cmpl
**
** Description      Sends event to application
**
** Returns          void
**
*******************************************************************************/
void bta_dm_search_cmpl (tBTA_DM_MSG *p_data)
{
    APPL_TRACE_EVENT("%s", __func__);

    if (p_data->hdr.layer_specific == BTA_DM_API_DI_DISCOVER_EVT)
        bta_dm_di_disc_cmpl(p_data);
    else
        bta_dm_search_cb.p_search_cback(BTA_DM_DISC_CMPL_EVT, NULL);//仍是調用這個函數,上報BTA_DM_DISC_CMPL_EVT
}

 

而對於BTA_DM_DISC_CMPL_EVT 的處理,btif_dm_search_services_evt函數並無去實現,不過這裏也不須要作什麼,搜索的數據和狀態都已經上報給framework了,

那到這裏,sdp 的代碼流程就分析完了。

相關文章
相關標籤/搜索