Android App内でBLE広告主を受動的にスキャンしたい。
しかし、これを行う方法が見つかりませんでした。
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はble広告主を受動的にスキャンできます。 (残念ながら、バックグラウンドモードのみ)
http://lists.Apple.com/archives/bluetooth-dev/2012/May/msg00041.html
「アプリがバックグラウンドにある場合、iOSはパッシブスキャンを実行します。」
しかし、パッシブスキャンモードを使用できるかどうかはわかりません。
質問:Androidで「パッシブスキャン」を使用することはできますか?可能であれば、この機能の使い方は?
active
スキャンとpassive
スキャンの違いは、active
スキャンが広告主にSCAN_RESPONSE
パケットをリクエストすることです。これは、広告が検出された後にSCAN_REQUEST
パケットを送信することによって行われます。両方の情報(ペイロード)は、デバイス検出コールバックのscanRecord
パラメーターにあります。
コアスペック から:
デバイスは、アクティブスキャンを使用して、ユーザーインターフェイスにデータを入力するのに役立つ可能性のあるデバイスに関する詳細情報を取得できます。アクティブスキャンには、より多くのリンク層広告メッセージが含まれます。
したがって、どのユースケースでも、これら2つのスキャンタイプを区別する必要はありません。
ただし、バックグラウンドで広告をリッスンする場合は、Service
を作成して自分で行う必要があります。組み込みの機能はありません(Android 4.4以降)。
バックグラウンドスキャンの場合、この例を見てください。ただし、スキャンは、アプリがシステムによって強制終了された(またはユーザーによって停止された)時点で終了します。
AlarmManagerを介してPendingIntentを開始します(アプリ内の任意の場所で、サービスを開始するには少なくとも1回実行する必要があります...)
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
}
}
AOSPリポジトリでbtif_gatt_client.cを見つけ、編集し、
交換
BTM_BleSetScanParams(p_cb->scan_interval, p_cb->scan_window, BTM_BLE_SCAN_MODE_ACTI);
と
BTM_BleSetScanParams(p_cb->scan_interval, p_cb->scan_window, BTM_BLE_SCAN_MODE_PASS);
次に、AOSPを構築し、画像を電話にフラッシュすると、パッシブスキャンが機能します。