Android BLE Пассивное сканирование

Я хочу сканировать BLE-рекламодателей пассивно в своем приложении для Android.
Но я не мог найти, как это сделать.

  • Согласно спецификации Bluetooth 4.0 Core, существует режим пассивного сканирования.
    Том 6: Основной системный пакет [Объем контроллера низкой мощности],
    Часть D: 4.1 ПАССИВНОЕ СКАНИРОВАНИЕ
    https://www.bluetooth.org/DocMan/handlers/DownloadDoc.ashx?doc_id=282159
    " Устройство может использовать пассивное сканирование для поиска рекламных устройств в этой области".

  • Кроме того, у android есть параметр, определяющий тип сканирования. (Активный/пассивный)
    http://androidxref.com/4.3_r2.1/xref/external/bluetooth/bluedroid/stack/btm/btm_ble_gap.c#555
    " scan_type: активное сканирование или пассивное сканирование"

  • Между тем, iOS может сканировать рекламодателей пассивно. (к сожалению, только в фоновом режиме)
    http://lists.apple.com/archives/bluetooth-dev/2012/May/msg00041.html
    " Когда приложение находится в фоновом режиме, iOS выполняет пассивное сканирование".

Но я не могу определить, можно ли использовать режим пассивного сканирования или нет.

Вопрос. Можно ли использовать "ПАССИВНЫЙ СКАНИРОВАНИЕ" на Android? Если возможно, как использовать эту функцию?

Ответ 1

Разница между active и passive проверкой заключается в том, что active сканирует запрос a SCAN_RESPONSE paket от рекламодателя. Это делается путем отправки пакета SCAN_REQUEST после обнаружения рекламы. Информация (полезная нагрузка) обоих будет в параметре scanRecord обнаруженного устройством обратного вызова.

Из основной спецификации:

Устройство может использовать активное сканирование для получения дополнительной информации об устройствах которые могут быть полезны для заполнения пользовательского интерфейса. Активное сканирование включает в себя больше рекламные сообщения на уровне канала.

Таким образом, для любого использования нет необходимости различать эти два типа сканирования.

Но если вы хотите прослушать рекламу в фоновом режиме, вам нужно сделать это самостоятельно, создав Service - встроенная функциональность (как и на Android 4.4) отсутствует.


Для фонового сканирования возьмите этот пример. Но сканирование закончится тем, что ваше приложение будет убито системой (или остановлено пользователем).

Запуск PendingIntent через AlarmManager (в любом месте вашего приложения, который должен запускаться хотя бы один раз, чтобы запустить службу...)

AlarmManager alarmMgr = (AlarmManager) getActivity().getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(getActivity(), BleScanService.class);
PendingIntent scanIntent = PendingIntent.getService(getActivity(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, SystemClock.elapsedRealtime(), intervalMillis, scanIntent);

BleScanService

public class BleScanService extends Service implements LeScanCallback {

private final static String TAG = BleScanService.class.getSimpleName();

private final IBinder mBinder = new LocalBinder();

private BluetoothManager mBluetoothManager;

private BluetoothAdapter mBluetoothAdapter;

public class LocalBinder extends Binder {
        public BleScanService getService() {
            return BleScanService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        initialize();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        long timeToScan = preferences.scanLength().get();
        startScan(timeToScan);

        return super.onStartCommand(intent, flags, startId);
    }

    /**
     * Initializes a reference to the local bluetooth adapter.
     * 
     * @return Return true if the initialization is successful.
     */
    public boolean initialize() {
        // For API level 18 and above, get a reference to BluetoothAdapter
        // through
        // BluetoothManager.
        if (mBluetoothManager == null) {
            mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            if (mBluetoothManager == null) {
                Log.e(TAG, "Unable to initialize BluetoothManager.");
                return false;
            }
        }

        if (mBluetoothAdapter == null) {
            mBluetoothAdapter = mBluetoothManager.getAdapter();
            if (mBluetoothAdapter == null) {
                Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
                return false;
            }
        }

        Log.d(TAG, "Initialzed scanner.");
        return true;
    }

    /**
     * Checks if bluetooth is correctly set up.
     * 
     * @return
     */
    protected boolean isInitialized() {
        return mBluetoothManager != null && mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
    }

    /**
     * Checks if ble is ready and bluetooth is correctly setup.
     * 
     * @return
     */
    protected boolean isReady() {
        return isInitialized() && isBleReady();
    }

    /**
     * Checks if the device is ble ready.
     * 
     * @return
     */
    protected boolean isBleReady() {
        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
    }

    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        Log.d(TAG, "Found ble device " + device.getName() + " " + device.getAddress());
        broadcastOnDeviceFound(device, scanRecord);
    }

    /**
     * Broadcasts a message with the given device.
     * 
     * @param device
     * @param scanRecord 
     */
    protected void broadcastOnDeviceFound(final BluetoothDevice device, byte[] scanRecord) {
        assert device != null : "Device should not be null.";

        Intent intent = new Intent(BleServiceConstants.ACTION_DEVICE_DISCOVERED);
        intent.putExtra(BleServiceConstants.EXTRA_DEVICE_DISCOVERED_DEVICE, device);
        intent.putExtra(BleServiceConstants.EXTRA_DEVICE_DISCOVERED_SCAN_RECORD, scanRecord);
        sendBroadcast(intent);
    }

    /**
     * Starts the bluetooth low energy scan It scans at least the
     * delayStopTimeInMillis.
     * 
     * @param delayStopTimeInMillis
     *            the duration of the scan
     * @return <code>true</code> if the scan is successfully started.
     */
    public boolean startScan(long delayStopTimeInMillis) {
        if (!isReady())
            return false;

        if (preferences.shouldScan().get()) {
            if (delayStopTimeInMillis <= 0) {
                Log.w(TAG, "Did not start scanning with automatic stop delay time of " + delayStopTimeInMillis);
                return false;
            }

            Log.d(TAG, "Auto-Stop scan after " + delayStopTimeInMillis + " ms");
            getMainHandler().postDelayed(new Runnable() {

                @Override
                public void run() {
                    Log.d(TAG, "Stopped scan.");
                    stopScan();
                }
            }, delayStopTimeInMillis);
        }
        return startScan();
    }

    /**
     * @return an handler with the main (ui) looper.
     */
    private Handler getMainHandler() {
        return new Handler(getMainLooper());
    }

    /**
     * Starts the bluetooth low energy scan. It scans without time limit.
     * 
     * @return <code>true</code> if the scan is successfully started.
     */
    public boolean startScan() {
        if (!isReady())
            return false;

        if (preferences.shouldScan().get()) {
            if (mBluetoothAdapter != null) {
                Log.d(TAG, "Started scan.");
                return mBluetoothAdapter.startLeScan(this);
            } else {
                Log.d(TAG, "BluetoothAdapter is null.");
                return false;
            }
        }
        return false;
    }

    /**
     * Stops the bluetooth low energy scan.
     */
    public void stopScan() {
        if (!isReady())
            return;

        if (mBluetoothAdapter != null)
            mBluetoothAdapter.stopLeScan(this);
        else {
            Log.d(TAG, "BluetoothAdapter is null.");
        }
    }

    @Override
    public void onDestroy() {
        preferences.edit().shouldScan().put(false).apply();
        super.onDestroy();
    }
}

Константы - это просто строки, чтобы распространять действие намерения и дополнительные имена. Существует еще один магазин настроек, в котором хранится время, в течение которого должна выполняться фаза сканирования. Вы можете легко заменить его на ваши нужды.

Затем вам нужно зарегистрировать широковещательный приемник с фильтром намерения, который соответствует указанному выше имени действия (BleServiceConstants.ACTION_DEVICE_DISCOVERED)

public class DeviceWatcher extends BroadcastReceiver {

  @Override
    public void onReceive(Context context, Intent intent) {
        BluetoothDevice device =  intent.getParcelableExtra(BleServiceConstants.EXTRA_DEVICE_DISCOVERED_DEVICE);

  // do anything with this information

  }
}