扫描:BluetoothLeScanner.startScan

必务先看“connectGatt(1/4):从frameworks到bt-JNI”,看过它才能知道sGattIf指向bt协议栈的btgattInterface等概念。

图1 BluetoothLeScanner.startScan

一、app代码

使用BluetoothLeScanner,指定ScanCallback进行扫描。

BluetoothLeScanner mLEScanner;
ScanCallback mScanCallback = {} // 一个app提供的回调结构,包括onScanResult。

public void startScan() {
    ScanSettings settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
    List<ScanFilter> filters = new ArrayList<ScanFilter>();
    settings = setScanSettings();
    //  An app must hold ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission in order to get results.
    mLEScanner.startScan(mScanFilters, settings, mScanCallback);
}

List<ScanFilter> mScanFilters

bt协议栈扫描到一个peripheral,并通知到bt-JNI。bt-JNI用它和peripheral广播出的数据进行匹配,匹配通过的话,就上报给app的onScanResult,否则不上报。行为发生在GattService.onScanResultInternal中的matchesFilters。它是在扫描到peripheral后才进行筛选,不会影响发到蓝牙硬件模块的扫描命令参数。

ScanSettings setting

类型SCAN_MODE_LOW_POWER(0)SCAN_MODE_BALANCED(1)SCAN_MODE_LOW_LATENCY(2)
扫描窗口(scan window)512毫秒/819 1024毫秒/16384096毫秒/6553
扫描间隔(scan interval)5120毫秒/81924096毫秒/65534096毫秒/6553

用于设置扫描参数。当中有较多字段,这里只说三个。

  • mScanMode。默认SCAN_MODE_LOW_POWER(0),其它常用值:SCAN_MODE_BALANCED(1)、SCAN_MODE_LOW_LATENCY(2)。该值决定蓝牙硬件模块要使用的扫描间隔,什么是扫描间隔参考“ble原理(4)主机的扫描间隔”。因为设置的扫描间隔、扫描窗口必须是0.625ms的整倍数,“4096毫秒/6553”中65535就是4096除以0.625之后的值,也是送到bt协议栈的值。额外说下,一个扫描窗口能扫到多个peripheral,不是只限一个。librose这么填这个值:如果该app在前台,使用SCAN_MODE_BALANCED,在后台用SCAN_MODE_LOW_POWER。
  • mCallbackType。不会影响蓝牙硬件模块扫描参数。默认CALLBACK_TYPE_ALL_MATCHES(1)。位掩码类型标记。如果想收到扫描到的peripheral,必须包含CALLBACK_TYPE_ALL_MATCHES。GattService.onScanResultInternal有在用mCallbackType。
  • mLegacy。不会影响蓝牙硬件模块扫描参数。默认true。是否只支持legacy广播。legacy最多支持62字节的广播数据,其中31字节的定时包,31字节的扫描回复包。GattService.onScanResultInternal有在用mLegacy。

 

二、frameworks:BluetoothLeScanner、BleScanCallbackWrapper

<aosp>/frameworks/base/core/java/android/bluetooth/le/BluetoothLeScanner.java
------
    public void startScan(List<ScanFilter> filters, ScanSettings settings,
            final ScanCallback callback) {
        startScan(filters, settings, null, callback, /*callbackIntent=*/ null, null);
    }
三参数版本startScan转到6参数版本。
    private int startScan(List<ScanFilter> filters, ScanSettings settings,
            final WorkSource workSource, final ScanCallback callback,
            final PendingIntent callbackIntent,
            List<List<ResultStorageDescriptor>> resultStorages) {
        // 从3参数版本可看到,此处的workSource、callbackIntent、resultStorages可能是null。
        // callback、callbackIntent不能同时是null。
        synchronized (mLeScanClients) {
            if (callback != null && mLeScanClients.containsKey(callback)) {
                return postCallbackErrorOrReturn(callback,
                            ScanCallback.SCAN_FAILED_ALREADY_STARTED);
            }
            IBluetoothGatt gatt;
            try {
                gatt = mBluetoothManager.getBluetoothGatt();
            } catch (RemoteException e) {
                gatt = null;
            }
            ...
            if (callback != null) {
                // 进这个入口
                BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters,
                        settings, workSource, callback, resultStorages);
                wrapper.startRegistration();
            } else {
                try {
                    gatt.startScanForIntent(callbackIntent, settings, filters,
                            mAttributionSource);
                } catch (RemoteException e) {
                    return ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
                }
            }
        }
        return ScanCallback.NO_ERROR;
    }

对gatt,之前已分析过,它是bt-jni中binder本地对象BluetoothGattBinder的代理对象。“callback != null”时,创建BleScanCallbackWrapper,并调用startRegistration方法。

    private class BleScanCallbackWrapper extends IScannerCallback.Stub {
        public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt,
                List<ScanFilter> filters, ScanSettings settings,
                WorkSource workSource, ScanCallback scanCallback,
                List<List<ResultStorageDescriptor>> resultStorages) {
            mBluetoothGatt = bluetoothGatt;
            mFilters = filters;
            mSettings = settings;
            mWorkSource = workSource;
            mScanCallback = scanCallback;
            mScannerId = 0;
            mResultStorages = resultStorages;
        }

        public void startRegistration() {
            synchronized (this) {
                // Scan stopped.
                ...
                try {
                    // 调用BluetoothGattBinder.registerScanner。
                    mBluetoothGatt.registerScanner(this, mWorkSource, mAttributionSource);
                    wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS);
                } 
                ...
            }
        }
}

startRegistration的主要任务是调用mBluetoothGatt.registerScanner,即BluetoothGattBinder.registerScanner。至此进入bt-JNI。

 

二、bt-JNI:BluetoothGattBinder.registerScanner

<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
------
public class GattService extends ProfileService {
    private ScanManager mScanManager;

    private static class BluetoothGattBinder extends IBluetoothGatt.Stub
            implements IProfileServiceBinder {
        public void registerScanner(IScannerCallback callback, WorkSource workSource,
                AttributionSource attributionSource) throws RemoteException {
            GattService service = getService();
            if (service == null) {
                return;
            }
            service.registerScanner(callback, workSource, attributionSource);
        }
    }

    void registerScanner(IScannerCallback callback, WorkSource workSource,
            AttributionSource attributionSource) throws RemoteException {
        if (!Utils.checkScanPermissionForDataDelivery(
                this, attributionSource, "GattService registerScanner")) {
            return;
        }

        UUID uuid = UUID.randomUUID();
        if (DBG) {
            Log.d(TAG, "registerScanner() - UUID=" + uuid);
        }

        enforceImpersonatationPermissionIfNeeded(workSource);

        AppScanStats app = mScannerMap.getAppScanStatsByUid(Binder.getCallingUid());
        if (app != null && app.isScanningTooFrequently()
                && !Utils.checkCallerHasPrivilegedPermission(this)) {
            Log.e(TAG, "App '" + app.appName + "' is scanning too frequently");
            callback.onScannerRegistered(ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY, -1);
            return;
        }

        mScannerMap.add(uuid, workSource, callback, null, this);
        mScanManager.registerScanner(uuid);
    }
}

BluetoothGattBinder.registerScanner逻辑很简单,调用service.registerScanner,sevice是GattService。GattService.registerScanner主要两个操作。

  1. 随机生成一个uuid,类似connectGatt,将作为唯一标识,用于在扫描client集合中作为key。
  2. mScanManager.registerScanner(uuid)。调用ScanManager.registerScanner,继续注册Scanner。
<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/gatt/ScanManager.java
------
public class ScanManager {
    private ScanNative mScanNative;

    void registerScanner(UUID uuid) {
        mScanNative.registerScannerNative(uuid.getLeastSignificantBits(),
                uuid.getMostSignificantBits());
    }
}

private class ScanNative {
    private native void registerScannerNative(long appUuidLsb, long appUuidMsb);
}

ScanManager.registerScanner调用mScanNative.registerScannerNative,后者出现了Native字样,直觉会猜这是用c/c++实现。的确如此,此至要转入c/c++代码。

<aosp>/packages/apps/Bluetooth/jni/com_android_bluetooth_gatt.cpp
------
static JNINativeMethod sScanMethods[] = {
    {"registerScannerNative", "(JJ)V", (void*)registerScannerNative},
    {"unregisterScannerNative", "(I)V", (void*)unregisterScannerNative},
    {"gattClientScanNative", "(Z)V", (void*)gattClientScanNative},
    ......
};

static void registerScannerNative(JNIEnv* env, jobject object,
                                  jlong app_uuid_lsb, jlong app_uuid_msb) {
  if (!sGattIf) return;

  Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb);
  sGattIf->scanner->RegisterScanner(
      uuid, base::Bind(&btgattc_register_scanner_cb, uuid));
}

sGattIf指向bt协议栈的btgattInterface。sGattIf->scanner指向bt协议栈的get_ble_scanner_instance()。

 

三、bt协议栈:btLeScannerInstance->RegisterScanner

<aosp>/system/bt/btif/src/btif_ble_scanner.cc
------
BleScannerInterface* get_ble_scanner_instance() {
  if (bluetooth::shim::is_gd_scanning_enabled()) {
    LOG_INFO("Use gd le scanner");
    return bluetooth::shim::get_ble_scanner_instance();
  } else if (btLeScannerInstance == nullptr) {
    btLeScannerInstance = new BleScannerInterfaceImpl();
  }
  return btLeScannerInstance;
}

btLeScannerInstance是个指向BleScannerInterface指针,不同主板可能使用不同实现,ROC-RK3588S-PC用的是BleScannerInterfaceImpl。BleScannerInterfaceImpl是一个class,不是c struct,scanner->RegisterScanner(...)是调用BleScannerInterfaceImpl成员函数RegisterScanner。

<aosp>/system/bt/btif/src/btif_ble_scanner.cc
------
class BleScannerInterfaceImpl : public BleScannerInterface {
  ~BleScannerInterfaceImpl() override{};

  void RegisterScanner(const bluetooth::Uuid& app_uuid,
                       RegisterCallback cb) override {
    do_in_main_thread(FROM_HERE,
                      Bind(
                          [](RegisterCallback cb) {
                            BTA_GATTC_AppRegister(
                                bta_cback,
                                jni_thread_wrapper(FROM_HERE, std::move(cb)),
                                false);
                          },
                          std::move(cb)));
  }
};

在bt_main_thread线程执行BTA_GATTC_AppRegister。对它,“connectGatt(2/4):btif_gattc_register_app”(https://www.cswamp.com/post/55)有详细介绍。它依次执行两个任务。1)如果没有注册过BTA_ID_GATTC事件类处理函数,调用bta_sys_register执行注册。2)生成一个随机uuid,在bt_main_thread执行bta_gattc_register。以下是第二步bta_gattc_register执行的四个操作。

  1. 遍历cl_rcb列表,找到一个未使用(in_use==false)插槽,然后调用GATT_Register算出client_if,告知我这个app使用这个插槽了。注意,client_if值是占用单元在一个数组内从1开始的索引,但这个数组不是插槽所在的cl_rcb,而是全局数组gatt_cb.cl_rcb[GATT_MAX_APPS]。因为GATT_MAX_APPS值是32,所以client_if范围是[1, 32]。
  2. 给bta_gattc_cb.cl_rcb[i]各字段赋值。client_if、in_use、p_cback、app_uuid。额外说下p_cback,它的值来自传给BTA_GATTC_AppRegister的第一个参数,即bta_gattc_cback。后面多个地方会发现,bt协议栈向回调bt-JNI中函数靠的就是它。
  3. 在bt_main_thread执行bta_gattc_start_if。
  4. cb.Run(client_if, status)。把此次注册结果返回给bt-JNI,返回说来就是回调bt-JNI注册的回调函数。

可以看到,扫描和connectGatt一样,也会占用一个client_if。

第2个操作赋值的p_cback,值来自传给BTA_GATTC_AppRegister的第一个参数,在这里是bta_cback。

<aosp>/system/bt/btif/src/btif_ble_scanner.cc
------
void bta_cback(tBTA_GATTC_EVT, tBTA_GATTC*) {}

这就是个空操作。所以类似connectGatt的“(*p_clreg->p_cback)(BTA_GATTC_OPEN_EVT, &cb_data)”就没有意义了。p_clreg->p_cback功能是回调bt-JNI中函数,没了这方法,扫描时需要回调bt-JNI,是怎么实现的?

  1. register_scanner成功后,通过这里的cb.Run回调。
  2. 扫描到一个peripheral,通过bta_scan_results_cb回调。

当然,还会有其它回调,总之,因为回调类型少,扫描过程时不需要p_clreg->p_cback这种“统一”回调途径。

继续说第四个操作,cb.Run(client_if, status)。溯源cb,它是bt-JNI中的btgattc_register_scanner_cb,调用它,意味着在回调bt-JNI。

 

四、bt-JNI:btgattc_register_scanner_cb

此时bt协议栈已为此次扫描注册了一个client_if,client_if传给这里的参数scannerId。

<aosp>/packages/apps/Bluetooth/jni/com_android_bluetooth_gatt.cpp
------
void btgattc_register_scanner_cb(const Uuid& app_uuid, uint8_t scannerId,
                                 uint8_t status) {
  CallbackEnv sCallbackEnv(__func__);
  if (!sCallbackEnv.valid()) return;
  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScannerRegistered,
                               status, scannerId, UUID_PARAMS(app_uuid));
}

static void classInitNative(JNIEnv* env, jclass clazz) {
  // Client callbacks
  ...
  method_onScannerRegistered =
      env->GetMethodID(clazz, "onScannerRegistered", "(IIJJ)V");
  method_onScanResult = env->GetMethodID(clazz, "onScanResult",
                                         "(IILjava/lang/String;IIIIII[BLjava/lang/String;)V");
  ...
};

clazz的是GattService,method_onScannerRegistered就是GattService.onScannerRegistered。

<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
------
public class GattService extends ProfileService {
    void onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb)
            throws RemoteException {
        UUID uuid = new UUID(uuidMsb, uuidLsb);
        if (DBG) {
            Log.d(TAG, "onScannerRegistered() - UUID=" + uuid + ", scannerId=" + scannerId
                    + ", status=" + status);
        }

        // First check the callback map
        ScannerMap.App cbApp = mScannerMap.getByUuid(uuid);
        if (cbApp != null) {
            ...
            if (cbApp.callback != null) {
                cbApp.callback.onScannerRegistered(status, scannerId);
            }
        }
    }
}

onScannerRegistered主要操作是调用cbApp.callback.onScannerRegistered。cbApp.callback是什么?——framewaorks中的BleScanCallbackWrapper,继续向上到frameworks代码。

private class BleScanCallbackWrapper extends IScannerCallback.Stub {
        public void onScannerRegistered(int status, int scannerId) {
            Log.d(TAG, "onScannerRegistered() - status=" + status
                    + " scannerId=" + scannerId + " mScannerId=" + mScannerId);
            synchronized (this) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    try {
                        if (mScannerId == -1) {
                            // Registration succeeds after timeout, unregister scanner.
                            mBluetoothGatt.unregisterScanner(scannerId, mAttributionSource);
                        } else {
                            mScannerId = scannerId;
                            mBluetoothGatt.startScan(mScannerId, mSettings, mFilters,
                                    mResultStorages, mAttributionSource);
                        }
                    } catch (RemoteException e) {
                        Log.e(TAG, "fail to start le scan: " + e);
                        mScannerId = -1;
                    }
                }
                ...
            }
        }
}

此时mScannerId是个[1, 32]范围内的client_if,将执行mBluetoothGatt.startScan。mBluetoothGatt是bt-JNI的BluetoothGattBinder,这是第二次执行BluetoothGattBinder中方法了,第一次是registerScanner,这一次是startScan。

<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
------
public class GattService extends ProfileService {
    private ScanManager mScanManager;

    private static class BluetoothGattBinder extends IBluetoothGatt.Stub
            implements IProfileServiceBinder {
        @Override
        public void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
                List storages, AttributionSource attributionSource) {
            GattService service = getService();
            if (service == null) {
                return;
            }
            service.startScan(scannerId, settings, filters, storages, attributionSource);
        }
    }

    void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
            List<List<ResultStorageDescriptor>> storages, AttributionSource attributionSource) {
        if (DBG) {
            Log.d(TAG, "start scan with filters");
        }

        if (!Utils.checkScanPermissionForDataDelivery(
                this, attributionSource, "Starting GATT scan.")) {
            return;
        }

        enforcePrivilegedPermissionIfNeeded(settings);
        String callingPackage = attributionSource.getPackageName();
        settings = enforceReportDelayFloor(settings);
        enforcePrivilegedPermissionIfNeeded(filters);
        final ScanClient scanClient = new ScanClient(scannerId, settings, filters, storages);
        scanClient.userHandle = UserHandle.of(UserHandle.getCallingUserId());
        mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);
        scanClient.eligibleForSanitizedExposureNotification =
                callingPackage.equals(mExposureNotificationPackage);

        ...

        scanClient.isQApp = Utils.isQApp(this, callingPackage);
        // isQApp指的是targetSdkVersion>= Build.VERSION_CODES.Q的app,
        // Q对应android-10,那要没意外,这值是true。
        // 一次运时的变量值
        // callingPackage: com.kos.launcher
        // scanClient.isQApp: true
        // scanClient.hasisavowedLocation: false
        if (!scanClient.hasisavowedLocation) {
            if (scanClient.isQApp) {
                // 一个普通用户级app,进入后台,
                // 数分钟后,像4分钟,checkCallerHasFineLocation会返回false。
                scanClient.hasLocationPermission = Utils.checkCallerHasFineLocation(
                        this, attributionSource, scanClient.userHandle);
            } else {
                scanClient.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(
                        this, attributionSource, scanClient.userHandle);
            }
        }
        // 一个普通用户级app,对应scanClient要获取扫描到的peripheral,scanClient.hasLocationPermission须是true。
        scanClient.hasNetworkSettingsPermission =
                Utils.checkCallerHasNetworkSettingsPermission(this);
        scanClient.hasNetworkSetupWizardPermission =
                Utils.checkCallerHasNetworkSetupWizardPermission(this);
        scanClient.hasScanWithoutLocationPermission =
                Utils.checkCallerHasScanWithoutLocationPermission(this);
        scanClient.associatedDevices = getAssociatedDevices(callingPackage, scanClient.userHandle);

        AppScanStats app = mScannerMap.getAppScanStatsById(scannerId);
        ScannerMap.App cbApp = mScannerMap.getById(scannerId);
        if (app != null) {
            scanClient.stats = app;
            boolean isFilteredScan = (filters != null) && !filters.isEmpty();
            boolean isCallbackScan = false;
            if (cbApp != null) {
                isCallbackScan = cbApp.callback != null;
            }
            app.recordScanStart(settings, filters, isFilteredScan, isCallbackScan, scannerId);
        }

        mScanManager.startScan(scanClient);
    }
}

BluetoothGattBinder.startScan逻辑很简单,调用service.startScan,即GattService.startScan,它主要两个操作。

  1. 创建一个scanClient,并填充它。这里要注意,一个普通用户级app,对应scanClient要获取扫描到的peripheral,scanClient.hasLocationPermission须是true。但是,如果这个app切到后台,数分钟后,像4分钟,hasLocationPermission是会得到false。所以,要是按常规设定,切到后台数分钟后,普通用户级app是无法扫描到peripheral。
  2. mScanManager.startScan(scanClient)。调用ScanManager.startScan,继续启动扫描。
<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/gatt/ScanManager.java
------
public class ScanManager {
    void startScan(ScanClient client) {
        sendMessage(MSG_START_BLE_SCAN, client);
    }

    private class ClientHandler extends Handler {
        ClientHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_START_BLE_SCAN:
                    handleStartScan((ScanClient) msg.obj);
                    break;
                ......
            }
        }

        void handleStartScan(ScanClient client) {
            ...
            // Begin scan operations.
            if (isBatchClient(client)) {
                mBatchClients.add(client);
                mScanNative.startBatchScan(client);
            } else {
                mRegularScanClients.add(client);
                // 主要操作:mScanNative.startRegularScan(client)
                mScanNative.startRegularScan(client);
                if (!mScanNative.isOpportunisticScanClient(client)) {
                    mScanNative.configureRegularScanParams();

                    if (!mScanNative.isExemptFromScanDowngrade(client)) {
                        Message msg = obtainMessage(MSG_SCAN_TIMEOUT);
                        msg.obj = client;
                        // Only one timeout message should exist at any time
                        sendMessageDelayed(msg, AppScanStats.getScanTimeoutMillis());
                    }
                }
            }
        }
    }
}

在ClientHandler线程执行handleStartScan,后者调用mScanNative.startRegularScan。

<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/gatt/ScanManager.java
------
    private class ScanNative {
        void startRegularScan(ScanClient client) {
            ...
            // Start scan native only for the first client.
            if (numRegularScanClients() == 1) {
                gattClientScanNative(true);
            }
        }

        private native void gattClientScanNative(boolean start);
    }

startRegularScan调用gattClientScanNative,后者是个c/c++实现的原生函数,至此再次进入c/c++代码。    

<aosp>/packages/apps/Bluetooth/jni/com_android_bluetooth_gatt.cpp
------
static void gattClientScanNative(JNIEnv* env, jobject object, jboolean start) {
  if (!sGattIf) return;
  sGattIf->scanner->Scan(start);
}

sGattIf指向bt协议栈的btgattInterface。sGattIf->scanner指向bt协议栈的BleScannerInterfaceImpl。BleScannerInterfaceImpl是一个class,scanner->Scan调用它的Scan方法。

 

五、bt-协议栈:btLeScannerInstance->Scan

<aosp>/system/bt/btif/src/btif_ble_scanner.cc
------
  void Scan(bool start) override {
    do_in_jni_thread(Bind(
        [](bool start) {
          if (!start) {
            do_in_main_thread(FROM_HERE,
                              Bind(&BTA_DmBleObserve, false, 0, nullptr));
            return;
          }

          btif_address_cache_init();
          do_in_main_thread(
              FROM_HERE, Bind(&BTA_DmBleObserve, true, 0, bta_scan_results_cb));
        },
        start));
  }

在bt_jni_thread线程执行一个lambda函数。对启动扫描,即start==true,lambda函数是两个操作。

  1. btif_address_cache_init。
  2. 在bt_main_thread线程执行BTA_DmBleObserve。
<aosp>/system/bt/bta/dm/bta_dm_api.cc
------
extern void BTA_DmBleObserve(bool start, uint8_t duration,
                             tBTA_DM_SEARCH_CBACK* p_results_cb) {
  APPL_TRACE_API("%s:start = %d ", __func__, start);
  do_in_main_thread(
      FROM_HERE, base::Bind(bta_dm_ble_observe, start, duration, p_results_cb));
}

<aosp>/system/bt/bta/dm/bta_dm_act.cc
------
void bta_dm_ble_observe(bool start, uint8_t duration,
                        tBTA_DM_SEARCH_CBACK* p_cback) {
  if (!start) {
    bta_dm_search_cb.p_scan_cback = NULL;
    BTM_BleObserve(false, 0, NULL, NULL);
    return;
  }

  /*Save the  callback to be called when a scan results are available */
  bta_dm_search_cb.p_scan_cback = p_cback;
  tBTM_STATUS status = BTM_BleObserve(true, duration, bta_dm_observe_results_cb,
                                      bta_dm_observe_cmpl_cb);
  if (status != BTM_CMD_STARTED) {
    ...
  }
}

BTA_DmBleObserve操作都在bta_dm_ble_observe,它依次执行两个操作。

  1. bta_dm_search_cb.p_scan_cback = p_cback。设置函数指针p_scan_cback,它会在bta_dm_observe_results_cb内被回调。
  2. BTM_BleObserve。通过hci,告知底层蓝牙硬件模块,开始扫描。

这里不深入BTM_BleObserve,只要知道,底层蓝牙硬件模块扫描到一个peripheral,它会回调传给BTM_BleObserve的第二个参数:bta_dm_observe_results_cb。

<aosp>/system/bt/bta/dm/bta_dm_act.cc
------
static void bta_dm_observe_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;
  APPL_TRACE_DEBUG("bta_dm_observe_results_cb");
  result.inq_res.bd_addr = p_inq->remote_bd_addr;
  result.inq_res.original_bda = p_inq->original_bda;
  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;
  result.inq_res.ble_evt_type = p_inq->ble_evt_type;
  result.inq_res.ble_primary_phy = p_inq->ble_primary_phy;
  result.inq_res.ble_secondary_phy = p_inq->ble_secondary_phy;
  result.inq_res.ble_advertising_sid = p_inq->ble_advertising_sid;
  result.inq_res.ble_tx_power = p_inq->ble_tx_power;
  result.inq_res.ble_periodic_adv_int = p_inq->ble_periodic_adv_int;

  /* 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_scan_cback) {
    // 回调传给bta_dm_ble_observe第三个参数:p_cback
    bta_dm_search_cb.p_scan_cback(BTA_DM_INQ_RES_EVT, &result);
  }

  ...
}

扫描到一个peripheral后,就会调用一次bta_dm_observe_results_cb,然后它会调用bta_dm_search_cb.p_scan_cback(BTA_DM_INQ_RES_EVT, &result),那bta_dm_search_cb.p_scan_cback是什么?——bta_dm_search_cb.p_scan_cback --> p_cback --> p_results_cb --> bta_scan_results_cb

<aosp>/system/bt/btif/src/btif_ble_scanner.cc
------
void bta_scan_results_cb(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH* p_data) {
  uint8_t len;

  ...

  tBTA_DM_INQ_RES* r = &p_data->inq_res;
  do_in_jni_thread(Bind(bta_scan_results_cb_impl, r->bd_addr, r->device_type,
                        r->rssi, r->ble_addr_type, r->ble_evt_type,
                        r->ble_primary_phy, r->ble_secondary_phy,
                        r->ble_advertising_sid, r->ble_tx_power,
                        r->ble_periodic_adv_int, std::move(value), r->original_bda));
}

此时传给bta_scan_results_cb的两个参数值。

  • event。BTA_DM_INQ_RES_EVT。
  • p_data。bta_dm_observe_results_cb生成的一个tBTA_DM_SEARC类型的结构。

主要操作是在bt_jni_thread执行bta_scan_results_cb_impl。

<aosp>/system/bt/btif/src/btif_ble_scanner.cc
------
void bta_scan_results_cb_impl(RawAddress bd_addr, tBT_DEVICE_TYPE device_type,
                              int8_t rssi, tBLE_ADDR_TYPE addr_type,
                              uint16_t ble_evt_type, uint8_t ble_primary_phy,
                              uint8_t ble_secondary_phy,
                              uint8_t ble_advertising_sid, int8_t ble_tx_power,
                              uint16_t ble_periodic_adv_int,
                              vector<uint8_t> value, RawAddress original_bda) {
  uint8_t remote_name_len;
  bt_device_type_t dev_type;
  bt_property_t properties;

  const uint8_t* p_eir_remote_name = AdvertiseDataParser::GetFieldByType(
      value, BTM_EIR_COMPLETE_LOCAL_NAME_TYPE, &remote_name_len);

  ...

  dev_type = (bt_device_type_t)device_type;
  BTIF_STORAGE_FILL_PROPERTY(&properties, BT_PROPERTY_TYPE_OF_DEVICE,
                             sizeof(dev_type), &dev_type);
  btif_storage_set_remote_device_property(&(bd_addr), &properties);

  btif_storage_set_remote_addr_type(&bd_addr, addr_type);
  HAL_CBACK(bt_gatt_callbacks, scanner->scan_result_cb, ble_evt_type, addr_type,
            &bd_addr, ble_primary_phy, ble_secondary_phy, ble_advertising_sid,
            ble_tx_power, rssi, ble_periodic_adv_int, std::move(value), &original_bda);
}

以上HAL_CBACK语句会在bt_jni_thread线程调用位在bt-JNI内btgattc_scan_result_cb。

 

六、bt-JNI:btgattc_scan_result_cb

<aosp>/packages/apps/Bluetooth/jni/com_android_bluetooth_gatt.cpp
------
void btgattc_scan_result_cb(uint16_t event_type, uint8_t addr_type,
                            RawAddress* bda, uint8_t primary_phy,
                            uint8_t secondary_phy, uint8_t advertising_sid,
                            int8_t tx_power, int8_t rssi,
                            uint16_t periodic_adv_int,
                            std::vector<uint8_t> adv_data, RawAddress* original_bda) {
  CallbackEnv sCallbackEnv(__func__);

  ...

  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanResult, event_type,
                               addr_type, address.get(), primary_phy,
                               secondary_phy, advertising_sid, tx_power, rssi,
                               periodic_adv_int, jb.get(), original_address.get());
}

clazz的是GattService,method_onScanResult就是GattService.onScanResult。

<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
------
public class GattService extends ProfileService {
    void onScanResult(int eventType, int addressType, String address, int primaryPhy,
            int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvInt,
            byte[] advData, String originalAddress) {
        // When in testing mode, ignore all real-world events
        if (isTestModeEnabled()) return;

        onScanResultInternal(eventType, addressType, address, primaryPhy, secondaryPhy,
                advertisingSid, txPower, rssi, periodicAdvInt, advData, originalAddress);
    }

    void onScanResultInternal(int eventType, int addressType, String address, int primaryPhy,
            int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvInt,
            byte[] advData, String originalAddress) {

        // 一次运行时变量值
        // mScanManager.getRegularScanQueue().size: 1;
        byte[] legacyAdvData = Arrays.copyOfRange(advData, 0, 62);

        for (ScanClient client : mScanManager.getRegularScanQueue()) {
            ScannerMap.App app = mScannerMap.getById(client.scannerId);
            if (app == null) {
                if (VDBG || mAdapterService.getIsVerboseLoggingEnabledForAll()) {
                    Log.d(TAG, "App is null for scanner ID " + client.scannerId);
                }
                continue;
            }

            BluetoothDevice device = getAnonymousDevice(address);

            ScanSettings settings = client.settings;
            byte[] scanRecordData;
            // This is for compability with applications that assume fixed size scan data.
            if (settings.getLegacy()) {
                if ((eventType & ET_LEGACY_MASK) == 0) {
                    // If this is legacy scan, but nonlegacy result - skip.
                    Log.i(TAG, "Non legacy result in legacy scan, skipping scanner id "
                               + client.scannerId + ", eventType=" + eventType);
                    continue;
                } else {
                    // Some apps are used to fixed-size advertise data.
                    scanRecordData = legacyAdvData;
                }
            } else {
                scanRecordData = advData;
            }

            ScanRecord scanRecord = ScanRecord.parseFromBytes(scanRecordData);
            ScanResult result =
                    new ScanResult(device, eventType, primaryPhy, secondaryPhy, advertisingSid,
                            txPower, rssi, periodicAdvInt, scanRecord,
                            SystemClock.elapsedRealtimeNanos());

            ...

            boolean hasPermission = hasScanResultPermission(client);            
            // 普通用户级app,切到后台,持续几分钟后,hasScanResultPermission将返回false。
            // 使得hasPermission是false,false将导致没法收到扫描到的peripheral。但有两例外情况。
            if (!hasPermission) {                 
                // 例外(1/2):该peripheral属于associatedDevices
                for (String associatedDevice : client.associatedDevices) {
                    if (associatedDevice.equalsIgnoreCase(address)) {
                        hasPermission = true;
                        break;
                    }
                }
            }
            if (!hasPermission && client.eligibleForSanitizedExposureNotification) {
                // 例外(2/2):该peripheral发出的广播属于SanitizedExposureNotification
                ScanResult sanitized = getSanitizedExposureNotification(result);
                if (sanitized != null) {
                    hasPermission = true;
                    result = sanitized;
                }
            }
            if (!hasPermission) {
                if (VDBG || mAdapterService.getIsVerboseLoggingEnabledForAll()) {
                    Log.d(TAG, "scanner id " + client.scannerId + " has no result permission");
                }
                continue;
            }

            MatchResult matchResult = matchesFilters(client, result, originalAddress);
            Log.w(TAG, "{leagor}{bt}pre matchesFilters: " + matchResult);
            if (!matchResult.getMatches()) {
                if (VDBG || mAdapterService.getIsVerboseLoggingEnabledForAll()) {
                    Log.d(TAG, "result did not match filter for scanner id " + client.scannerId);
                }
                continue;
            }

            if (matchResult.getMatchOrigin() == MatchOrigin.ORIGINAL_ADDRESS) {
                result = new ScanResult(getAnonymousDevice(originalAddress), eventType, primaryPhy,
                        secondaryPhy, advertisingSid, txPower, rssi, periodicAdvInt, scanRecord,
                            SystemClock.elapsedRealtimeNanos());

            }

            if ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_ALL_MATCHES) == 0) {
                if (VDBG || mAdapterService.getIsVerboseLoggingEnabledForAll()) {
                    Log.d(TAG, "callback type " + settings.getCallbackType()
                            + " is not ALL_MATCHES for scanner id " + client.scannerId);
                }
                continue;
            }

            try {
                app.appScanStats.addResult(client.scannerId);
                if (app.callback != null) {
                    // 调用BleScanCallbackWrapper.onScanResult
                    app.callback.onScanResult(result);
                } else {
                    // Send the PendingIntent
                    ArrayList<ScanResult> results = new ArrayList<>();
                    results.add(result);
                    sendResultsByPendingIntent(app.info, results,
                            ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
                }
            } catch (RemoteException | PendingIntent.CanceledException e) {
                Log.e(TAG, "Stop scan for scanner id " + client.scannerId + " due to : " + e);
                mScannerMap.remove(client.scannerId);
                mScanManager.stopScan(client.scannerId);
            }
        }
    }
}

mScanManager.getRegularScanQueue()存储着等待蓝牙扫描结果的app。一般来说,就一个。

首先检查扫描设置中的mLeagacy(settings.getLegacy()),目前一般是true。如果是,那意味着只要legacy广播,“copyOfRange(advData, 0, 62)”,只取前面62字节。因为legacy最多支持62字节的广播数据,其中31字节的定时包,31字节的扫描回复包。

对每个app,能否收到要经过三种滤除。一是得有接收到的权限,二是自设的筛选器没把它滤掉,三是扫描设置的CALLBACK_TYPE_ALL_MATCHES。

滤除1:需要有接收权限。

boolean hasPermission = hasScanResultPermission(client);

这里需要注意,一旦app切到后台,数分钟后,此时client.hasLocationPermission是false,将导致hasScanResultPermission是false。但在这种情况,有两个例外会让它重新有权限。换句话说,这两种情况可让app在后台时,仍旧能扫描到peripheral。

  • 例外1:该peripheral属于associatedDevices。associatedDevices是啥?——不确定是否对,“Android 12引入了CompanionDeviceService API,旨在改善管理智能手表和健身追踪器等设备的应用。当一个可穿戴设备与你的手机亲密接触时,伴侣应用将被唤醒。”
  • 例外2:该peripheral发出的广播属于SanitizedExposureNotification。SanitizedExposureNotification是啥?——不确定是否对,“手机的接触通知功能(exposure notification),用于提醒可能接触过COVID-19(新冠病毒)的用户。可以在安卓手机上启用MassNotify,以便阻止COVID-19扩散”。

或许有些人认为,在ScanFilter设置ServiceUuid,可以让自个app切到后台后,仍旧可以收到这ServiceUuid的peripheral,这是不存在的。

滤除2:自设的筛选器(ScanFilter)没把它滤掉

自设的筛选器指调用mLEScanner.startScan时,设置的第一个参数:List<ScanFilter> filters。举个例子,在ScanFilter,可以设置ServiceUuid,那这client只接收是这ServiceUuid的peripherial。

滤除3:扫描设置的CALLBACK_TYPE_ALL_MATCHES

“(settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_ALL_MATCHES) == 0”,要上报到app,app需要在扫描设置中让mCallbackType含有ScanSettings.CALLBACK_TYPE_ALL_MATCHES标记。

判断出这peripherl不滤除后,调用app.callback.onScanResult,app.callback是frameworks的BleScanCallbackWrapper。

<aosp>/frameworks/base/core/java/android/bluetooth/le/BluetoothLeScanner.java
------
    private class BleScanCallbackWrapper extends IScannerCallback.Stub {
        public void onScanResult(final ScanResult scanResult) {
            Attributable.setAttributionSource(scanResult, mAttributionSource);
            if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString());

            // Check null in case the scan has been stopped
            synchronized (this) {
                if (mScannerId <= 0) return;
            }
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    // 调用app提供的onScanResult。
                    mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult);
                }
            });
        }

BleScanCallbackWrapper.onScanResult继结向上回调mScanCallback.onScanResult,这个就是app提供的onScanResult,至此app知道了扫描到一个peripheral。

 

七、app进入后台后,允许正常蓝牙扫描

app进入后台,只能扫到associatedDevices、SanitizedExposureNotification这两类peripheral,但在一些应用,这是不够的。

<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
------
    void startScan(int scannerId, ScanSettings settings, List<ScanFilter> filters,
            List<List<ResultStorageDescriptor>> storages, AttributionSource attributionSource) {
        ...
        scanClient.isQApp = Utils.isQApp(this, callingPackage);
        // isQApp指的是targetSdkVersion>= Build.VERSION_CODES.Q的app,
        // Q对应android-10,要没意外,这值总是true。
        if (!scanClient.hasEisavowedLocation) {
            if (scanClient.isQApp) {
                // 检查是否有ACCESS_FINE_LOCATION权限
                scanClient.hasLocationPermission = Utils.checkCallerHasFineLocation(
                        this, attributionSource, scanClient.userHandle);
            } else {
                scanClient.hasLocationPermission = Utils.checkCallerHasCoarseOrFineLocation(
                        this, attributionSource, scanClient.userHandle);
            }
        }

<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/Utils.java
------
    public static boolean checkCallerHasFineLocation(
            Context context, AttributionSource attributionSource, UserHandle userHandle) {
        if (blockedByLocationOff(context, userHandle)) {
            Log.e(TAG, "Permission denial: Location is off.");
            return false;
        }

        // STOPSHIP(b/188391719): enable this security enforcement
        // attributionSource.enforceCallingUid();
        // app进入后台,数分钟后,checkPermissionForDataDeliveryFromDataSource将返回false
        if (PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
                context, ACCESS_FINE_LOCATION, PID_UNKNOWN,
                new AttributionSource(context.getAttributionSource(), attributionSource),
                "Bluetooth location check") == PERMISSION_GRANTED) {
            return true;
        }

        Log.e(TAG, "Permission denial: Need ACCESS_FINE_LOCATION "
                + "permission to get scan results");
        return false;
    }

app进入后台,数分钟后,前台时返回true的PermissionChecker.checkPermissionForDataDeliveryFromDataSource,此时会改为false,导致checkCallerHasFineLocation返回false,让认为app没有ACCESS_FINE_LOCATION权限。因为scanClient.isQApp是true,checkCallerHasFineLocation返回false导致scanClient.hasLocationPermission值是false。

<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
------
    private boolean hasScanResultPermission(final ScanClient client) {
        if (client.hasNetworkSettingsPermission
                || client.hasNetworkSetupWizardPermission
                || client.hasScanWithoutLocationPermission) {
            return true;
        }
        if (client.hasEisavowedLocation) {
            return true;
        }
        Log.d(TAG, "{leagor}{bt}client.hasLocationPermission=" + client.hasLocationPermission + 
            ", !blockedByLocationOff=" + (!Utils.blockedByLocationOff(this, client.userHandle)));
        return client.hasLocationPermission && !Utils.blockedByLocationOff(this, client.userHandle);
    }

scanClient.hasLocationPermission是flase导致hasScanResultPermission返回false。在GattService.onScanResultInternal已说过,hasScanResultPermission是false,将导致除那两种例外的periphernal都不会上报到app。

源头是PermissionChecker.checkPermissionForDataDeliveryFromDataSource返回false,当然,有更深原因导致checkPermissionForDataDeliveryFromDataSource返回false。这我没深入,猜原因类似“(android-12)app进入后台,关掉杀死serivce、停止摄像头”。

<aosp>/packages/apps/Bluetooth/src/com/android/bluetooth/Utils.java
------
    public static boolean checkCallerHasFineLocation(
            Context context, AttributionSource attributionSource, UserHandle userHandle) {
        if (blockedByLocationOff(context, userHandle)) {
            Log.e(TAG, "Permission denial: Location is off.");
            return false;
        }

        // STOPSHIP(b/188391719): enable this security enforcement
        // attributionSource.enforceCallingUid();
        if (PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
                context, ACCESS_FINE_LOCATION, PID_UNKNOWN,
                new AttributionSource(context.getAttributionSource(), attributionSource),
                "Bluetooth location check") == PERMISSION_GRANTED) {
            return true;
        } else {
            // 修改:checkCallerHasFineLocation==false时,函数依旧返回true。
            Log.e(TAG, "{leagor}checkPermissionForDataDeliveryFromDataSource's false to true");
            return true;
        }
/*
        Log.e(TAG, "Permission denial: Need ACCESS_FINE_LOCATION "
                + "permission to get scan results");
        return false;
*/
    }

修改方法:checkPermissionForDataDeliveryFromDataSource返回false时,checkCallerHasFineLocation依旧返回true。由于“if”两个分支都已有return,得注释掉后面Log.e、return false,免得编译出错。

全部评论: 0

    写评论: