Android藍芽協議-藍芽掃描 startDiscovery

weixin_33850890發表於2017-06-07

藍芽開啟位於setting中,即 package:com.android.settings.bluetooth
BluetoothSettings.java 中。
選單選項MENU_ID_SCAN:

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case MENU_ID_SCAN:
                if (mLocalAdapter.getBluetoothState() == BluetoothAdapter.STATE_ON) {
                    MetricsLogger.action(getActivity(), MetricsLogger.ACTION_BLUETOOTH_SCAN);
                    startScanning();
                }
                return true;

            case MENU_ID_RENAME_DEVICE:
                MetricsLogger.action(getActivity(), MetricsLogger.ACTION_BLUETOOTH_RENAME);
                new BluetoothNameDialogFragment().show(
                        getFragmentManager(), "rename device");
                return true;

            case MENU_ID_SHOW_RECEIVED:
                MetricsLogger.action(getActivity(), MetricsLogger.ACTION_BLUETOOTH_FILES);
                Intent intent = new Intent(BTOPP_ACTION_OPEN_RECEIVED_FILES);
                getActivity().sendBroadcast(intent);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

呼叫startScanning,新增可用裝置列表,並將快取的裝置中沒有配對的裝置清除。呼叫LocalBluetoothAdapter.startScanning方法開始掃描。

LocalBluetoothAdapter.startScanning

    public void startScanning(boolean force) {
        // Only start if we're not already scanning
        if (!mAdapter.isDiscovering()) {
            //如果不是強制掃描,在SCAN_EXPIRATION_MS間隔內,之掃描一次
            if (!force) {
                // Don't scan more than frequently than SCAN_EXPIRATION_MS,
                // unless forced
                if (mLastScan + SCAN_EXPIRATION_MS > System.currentTimeMillis()) {
                    return;
                }
                
                // If we are playing music, don't scan unless forced.
                //播放音樂時,不掃描
                A2dpProfile a2dp = mProfileManager.getA2dpProfile();
                if (a2dp != null && a2dp.isA2dpPlaying()) {
                    return;
                }
            }
            //開始掃描
            if (mAdapter.startDiscovery()) {
                mLastScan = System.currentTimeMillis();
            }
        }
    }
  • 真在掃描則跳過
  • 如果不是強制開始掃描,在SCAN_EXPIRATION_MS間隔時間內,只掃描一次;如果在播放音樂時,也不執行掃描
  • 呼叫BluetoothAdapter.startDiscovery方法,啟動掃描任務

BluetoothAdapter.startDiscovery方法呼叫遠端服務AdapterServiceBinder.startDiscovery方法,開啟掃描。
AdapterServiceBinder是AdapterService內IBluetooth.Stub介面類,主要用來處理藍芽相關的服務呼叫;AdapterServiceBinder將傳入的服務呼叫轉交AdapterService處理。

AdapterServiceBinder.startDiscovery

public boolean startDiscovery() {
    if (!Utils.checkCaller()) {
    Log.w(TAG, "startDiscovery() - Not allowed for non-active user");
    return false;
    }

    AdapterService service = getService();
    if (service == null) return false;
    return service.startDiscovery();
}

AdapterService.startDiscovery

     boolean startDiscovery() {
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
                                       "Need BLUETOOTH ADMIN permission");

        return startDiscoveryNative();
    }

進入JNI層

startDiscoveryNative

在com_android_bluetooth_btservice_AdapterService.cpp檔案中,呼叫startDiscoveryNative方法,

//藍芽掃描
static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {
    ALOGV("%s:",__FUNCTION__);

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

start_discovery

startDiscoveryNative方法呼叫bluetooth.c中start_discovery方法

//藍芽掃描
static int start_discovery(void)
{
    /* sanity check */ //完整性檢查
    if (interface_ready() == FALSE)
        return BT_STATUS_NOT_READY;

    return btif_dm_start_discovery();
}

start_discovery方法呼叫btif_dm.c的btif_dm_start_discovery方法

btif_dm_start_discovery

btif_dm.c 裝置管理(DM)相關的功能

btif_dm_start_discovery方法中最終呼叫 BTA_DmSearch(&inq_params, services, bte_search_devices_evt);

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;

    if ((p_msg = (tBTA_DM_API_SEARCH *) GKI_getbuf(sizeof(tBTA_DM_API_SEARCH))) != NULL)
    {
        memset(p_msg, 0, 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傳送掃描任務訊息。

bte_search_devices_evt方法作為掃描任務的回撥函式,
當掃描到裝置時,回撥bte_search_devices_evt方法,將上下文從BTE切換到BTIF

static void bte_search_devices_evt(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data)
{
    UINT16 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 += HCI_EXT_INQ_RESPONSE_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", __FUNCTION__, 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) event, (void *)p_data, param_len,
        (param_len > sizeof(tBTA_DM_SEARCH)) ? search_devices_copy_cb : NULL);
}

btif_dm_search_devices_evt

再呼叫btif_dm_search_devices_evt,btif_dm_search_devices_evt並將掃描到的裝置通過HAL_CBACK方式返回。

static void btif_dm_search_devices_evt (UINT16 event, char *p_param)
{
    tBTA_DM_SEARCH *p_search_data;
    BTIF_TRACE_EVENT("%s event=%s", __FUNCTION__, 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_bdaddr_t bdaddr;
                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);
                bdcpy(bdaddr.address, 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? */
        }
    
        ..........................省略............................
  }
}

掃描到裝置時,呼叫的是HAL_CBACK(bt_hal_cbacks, device_found_cb,
num_properties, properties);
device_found_cb即是device_found_callback方法,在com_android_bluetooth_btservice_AdapterService.cpp中實現

static void device_found_callback(int num_properties, bt_property_t *properties) {
    jbyteArray addr = NULL;
    int addr_index;

    for (int i = 0; i < num_properties; i++) {
        if (properties[i].type == BT_PROPERTY_BDADDR) {
            addr = callbackEnv->NewByteArray(properties[i].len);
            if (addr) {
                callbackEnv->SetByteArrayRegion(addr, 0, properties[i].len,
                                                (jbyte*)properties[i].val);
                addr_index = i;
            } else {
                ALOGE("Address is NULL (unable to allocate) in %s", __FUNCTION__);
                return;
            }
        }
    }
    if (addr == NULL) {
        ALOGE("Address is NULL in %s", __FUNCTION__);
        return;
    }

    ALOGV("%s: Properties: %d, Address: %s", __FUNCTION__, num_properties,
        (const char *)properties[addr_index].val);

    remote_device_properties_callback(BT_STATUS_SUCCESS, (bt_bdaddr_t *)properties[addr_index].val,
                                      num_properties, properties);

    callbackEnv->CallVoidMethod(sJniCallbacksObj, method_deviceFoundCallback, addr);
    checkAndClearExceptionFromCallback(callbackEnv, __FUNCTION__);
    callbackEnv->DeleteLocalRef(addr);
}

該方法對應JNI方法為method_deviceFoundCallback,即

jclass jniCallbackClass =
        env->FindClass("com/android/bluetooth/btservice/JniCallbacks");
        ..................
method_deviceFoundCallback = env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");

對應JniCallbacks.java的deviceFoundCallback方法。

deviceFoundCallback

void deviceFoundCallback(byte[] address) {
        mRemoteDevices.deviceFoundCallback(address);
    }

deviceFoundCallback將掃描到的裝置通過廣播傳送出去

    void deviceFoundCallback(byte[] address) {
        // The device properties are already registered - we can send the intent
        // now
        BluetoothDevice 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);

        mAdapterService.sendBroadcastMultiplePermissions(intent,
                new String[] {AdapterService.BLUETOOTH_PERM,
                        android.Manifest.permission.ACCESS_COARSE_LOCATION});
    }

註冊android.bluetooth.device.action.FOUND的廣播接收器都可以收到該廣播。

相關文章