I want to scan BLE advertisers passively within my Android App.
But I couldn\'t found how to do this.
According Bluetooth 4.0 Core spec, There exists passi
The difference between active
and passive
scan is that active
scans requests a SCAN_RESPONSE
paket from the advertiser. This is done by sending a SCAN_REQUEST
paket after an advertisements has been detected. The information (payload) of both will be in the scanRecord
parameter of the device found callback.
From the core spec:
A device may use active scanning to obtain more information about devices that may be useful to populate a user interface. Active scanning involves more link layer advertising messages.
So, for any usecase it will not be necessary to differ between those two scanning types.
But if you want to listen for advertisements in the background then you need to do this yourself by creating a Service
- there is no built in functionality (as of Android 4.4).
For background scanning take this example. But the scan will end atthe point your app is killed by the system(or stopped by the user).
Starting a PendingIntent through AlarmManager (anywhere in your app, that has to be run at least once to start the service...)
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();
}
}
The constants are just Strings to distribute the intent action and extra names. There is another preferences store that stores the how long the scanning phase should be...You can easily replace it ba your needs.
Then you have to registera broadcast reciever with an intent filter that matches the above action name (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
}
}
Find btif_gatt_client.c in AOSP repo, Edit it,
replace
BTM_BleSetScanParams(p_cb->scan_interval, p_cb->scan_window, BTM_BLE_SCAN_MODE_ACTI);
with
BTM_BleSetScanParams(p_cb->scan_interval, p_cb->scan_window, BTM_BLE_SCAN_MODE_PASS);
then build AOSP, flash the image to phone, then passive scan works.