From 466e67f34a72bbb43fa03aae7ad96cb41f02451d Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Sat, 26 Jul 2014 21:08:54 -0700 Subject: [PATCH] Fix service data filter and report delay issue. Change-Id: I1e4dd400c94f8740e4e950c42be6beac858f3ab8 --- src/com/android/bluetooth/gatt/GattService.java | 7 ++ .../android/bluetooth/gatt/ScanFilterQueue.java | 53 ++++++++++++++- src/com/android/bluetooth/gatt/ScanManager.java | 77 ++++++++++++++++++++-- 3 files changed, 127 insertions(+), 10 deletions(-) diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java index bc8df8d2..800cde89 100644 --- a/src/com/android/bluetooth/gatt/GattService.java +++ b/src/com/android/bluetooth/gatt/GattService.java @@ -43,6 +43,7 @@ import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.ProfileService; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -595,6 +596,7 @@ public class GattService extends ProfileService { if (client.filters == null || client.filters.isEmpty()) { return true; } + if (DBG) Log.d(TAG, "result: " + scanResult.toString()); for (ScanFilter filter : client.filters) { if (DBG) Log.d(TAG, "filter: " + filter.toString()); if (filter.matches(scanResult)) { @@ -954,6 +956,9 @@ public class GattService extends ProfileService { ClientMap.App app = mClientMap.getById(clientIf); if (app == null) return; Set results = parseBatchScanResults(numRecords, reportType, recordData); + for (ScanResult result : new ArrayList(results)) { + Log.d(TAG, result.getScanRecord().toString()); + } app.callback.onBatchScanResults(new ArrayList(results)); } @@ -994,6 +999,7 @@ public class GattService extends ProfileService { } private Set parseFullResults(int numRecords, byte[] batchRecord) { + Log.d(TAG, "Batch record : " + Arrays.toString(batchRecord)); Set results = new HashSet(numRecords); int position = 0; while (position < batchRecord.length) { @@ -1019,6 +1025,7 @@ public class GattService extends ProfileService { System.arraycopy(advertiseBytes, 0, scanRecord, 0, advertisePacketLen); System.arraycopy(scanResponseBytes, 0, scanRecord, advertisePacketLen, scanResponsePacketLen); + Log.d(TAG, "ScanRecord : " + Arrays.toString(scanRecord)); results.add(new ScanResult(device, ScanRecord.parseFromBytes(scanRecord), rssi, timestampNanos)); } diff --git a/src/com/android/bluetooth/gatt/ScanFilterQueue.java b/src/com/android/bluetooth/gatt/ScanFilterQueue.java index d41d9f06..89336287 100644 --- a/src/com/android/bluetooth/gatt/ScanFilterQueue.java +++ b/src/com/android/bluetooth/gatt/ScanFilterQueue.java @@ -16,7 +16,10 @@ package com.android.bluetooth.gatt; +import android.bluetooth.BluetoothUuid; import android.bluetooth.le.ScanFilter; +import android.os.ParcelUuid; +import android.util.Log; import java.util.Arrays; import java.util.HashSet; @@ -32,11 +35,15 @@ import java.util.UUID; */ /* package */class ScanFilterQueue { public static final int TYPE_DEVICE_ADDRESS = 0; - public static final int TYPE_SERVICE_DATA = 1; + public static final int TYPE_SERVICE_DATA_CHANGED = 1; public static final int TYPE_SERVICE_UUID = 2; public static final int TYPE_SOLICIT_UUID = 3; public static final int TYPE_LOCAL_NAME = 4; public static final int TYPE_MANUFACTURER_DATA = 5; + public static final int TYPE_SERVICE_DATA = 6; + + // Max length is 31 - 3(flags) - 2 (one byte for length and one byte for type). + private static final int MAX_LEN_PER_FIELD = 26; // Values defined in bluedroid. private static final byte DEVICE_TYPE_ALL = 0; @@ -91,7 +98,7 @@ import java.util.UUID; void addServiceChanged() { Entry entry = new Entry(); - entry.type = TYPE_SERVICE_DATA; + entry.type = TYPE_SERVICE_DATA_CHANGED; mEntries.add(entry); } @@ -146,6 +153,14 @@ import java.util.UUID; mEntries.add(entry); } + void addServiceData(byte[] data, byte[] dataMask) { + Entry entry = new Entry(); + entry.type = TYPE_SERVICE_DATA; + entry.data = data; + entry.data_mask = dataMask; + mEntries.add(entry); + } + Entry pop() { if (isEmpty()) { return null; @@ -178,7 +193,6 @@ import java.util.UUID; int getFeatureSelection() { int selc = 0; for (Entry entry : mEntries) { - System.out.println("entry selc value " + (1 << entry.type)); selc |= (1 << entry.type); } return selc; @@ -212,5 +226,38 @@ import java.util.UUID; filter.getManufacturerData(), filter.getManufacturerDataMask()); } } + if (filter.getServiceDataUuid() != null && filter.getServiceData() != null) { + ParcelUuid serviceDataUuid = filter.getServiceDataUuid(); + byte[] serviceData = filter.getServiceData(); + byte[] serviceDataMask = filter.getServiceDataMask(); + if (serviceDataMask == null) { + serviceDataMask = new byte[serviceData.length]; + Arrays.fill(serviceDataMask, (byte) 0xFF); + } + serviceData = concate(serviceDataUuid, serviceData); + serviceDataMask = concate(serviceDataUuid, serviceDataMask); + if (serviceData != null && serviceDataMask != null) { + addServiceData(serviceData, serviceDataMask); + } + } + } + + private byte[] concate(ParcelUuid serviceDataUuid, byte[] serviceData) { + int dataLen = 2 + (serviceData == null ? 0 : serviceData.length); + // If data is too long, don't add it to hardware scan filter. + if (dataLen > MAX_LEN_PER_FIELD) { + return null; + } + byte[] concated = new byte[dataLen]; + // Extract 16 bit UUID value. + int uuidValue = BluetoothUuid.getServiceIdentifierFromParcelUuid( + serviceDataUuid); + // First two bytes are service data UUID in little-endian. + concated[0] = (byte) (uuidValue & 0xFF); + concated[1] = (byte) ((uuidValue >> 8) & 0xFF); + if (serviceData != null) { + System.arraycopy(serviceData, 0, concated, 2, serviceData.length); + } + return concated; } } diff --git a/src/com/android/bluetooth/gatt/ScanManager.java b/src/com/android/bluetooth/gatt/ScanManager.java index a5a2fb7b..be2e0a2d 100644 --- a/src/com/android/bluetooth/gatt/ScanManager.java +++ b/src/com/android/bluetooth/gatt/ScanManager.java @@ -16,13 +16,20 @@ package com.android.bluetooth.gatt; +import android.app.AlarmManager; +import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanSettings; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.os.SystemClock; import android.util.Log; import com.android.bluetooth.Utils; @@ -57,6 +64,9 @@ public class ScanManager { private static final int MSG_STOP_BLE_SCAN = 1; private static final int MSG_FLUSH_BATCH_RESULTS = 2; + private static final String ACTION_REFRESH_BATCHED_SCAN = + "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN"; + // Timeout for each controller operation. private static final int OPERATION_TIME_OUT_MILLIS = 500; @@ -72,8 +82,8 @@ public class ScanManager { ScanManager(GattService service) { mRegularScanClients = new HashSet(); mBatchClients = new HashSet(); - mScanNative = new ScanNative(); mService = service; + mScanNative = new ScanNative(); } void start() { @@ -85,6 +95,7 @@ public class ScanManager { void cleanup() { mRegularScanClients.clear(); mBatchClients.clear(); + mScanNative.cleanup(); } /** @@ -236,10 +247,34 @@ public class ScanManager { private final Deque mFilterIndexStack; // Map of clientIf and Filter indices used by client. private final Map> mClientFilterIndexMap; + private AlarmManager mAlarmManager; + private PendingIntent mBatchScanIntervalIntent; ScanNative() { mFilterIndexStack = new ArrayDeque(); mClientFilterIndexMap = new HashMap>(); + + mAlarmManager = (AlarmManager) mService.getSystemService(Context.ALARM_SERVICE); + Intent batchIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null); + mBatchScanIntervalIntent = PendingIntent.getBroadcast(mService, 0, batchIntent, 0); + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_REFRESH_BATCHED_SCAN); + mService.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "awakened up at time " + SystemClock.elapsedRealtime()); + String action = intent.getAction(); + + if (action.equals(ACTION_REFRESH_BATCHED_SCAN)) { + if (mBatchClients.isEmpty()) { + return; + } + // TODO: find out if we need to flush all clients at once. + flushBatchScanResults(mBatchClients.iterator().next()); + } + } + }, filter); } private void resetCountDownLatch() { @@ -286,10 +321,24 @@ public class ScanManager { int scanWindowUnit = 8; int discardRule = 2; int addressType = 0; - logd("Starting BLE batch scan"); - gattClientStartBatchScanNative(client.clientIf, scanMode, scanIntervalUnit, scanWindowUnit, - addressType, - discardRule); + gattClientStartBatchScanNative(client.clientIf, scanMode, scanIntervalUnit, + scanWindowUnit, addressType, discardRule); + logd("Starting BLE batch scan, scanMode -" + scanMode); + gattClientStartBatchScanNative(client.clientIf, scanMode, scanIntervalUnit, + scanWindowUnit, addressType, discardRule); + setBatchAlarm(); + } + + private void setBatchAlarm() { + if (mBatchClients.isEmpty()) { + mAlarmManager.cancel(mBatchScanIntervalIntent); + return; + } + long batchTriggerIntervalMillis = getBatchTriggerIntervalMillis(); + mAlarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + batchTriggerIntervalMillis, + batchTriggerIntervalMillis, + mBatchScanIntervalIntent); } void stopRegularScan(ScanClient client) { @@ -306,6 +355,7 @@ public class ScanManager { removeScanFilters(client.clientIf); mBatchClients.remove(client); gattClientStopBatchScanNative(client.clientIf); + setBatchAlarm(); } void flushBatchResults(int clientIf) { @@ -319,6 +369,21 @@ public class ScanManager { gattClientReadScanReportsNative(client.clientIf, resultType); } + void cleanup() { + mAlarmManager.cancel(mBatchScanIntervalIntent); + } + + private long getBatchTriggerIntervalMillis() { + long intervalMillis = Long.MAX_VALUE; + for (ScanClient client : mBatchClients) { + if (client.settings != null && client.settings.getReportDelayMillis() > 0) { + intervalMillis = Math.min(intervalMillis, + client.settings.getReportDelayMillis()); + } + } + return intervalMillis; + } + // Add scan filters. The logic is: // If no offload filter can/needs to be set, set ALLOW_ALL filter. // Otherwise offload all filters to hardware and enable all filters. @@ -430,7 +495,6 @@ public class ScanManager { break; case ScanFilterQueue.TYPE_MANUFACTURER_DATA: - { int len = entry.data.length; if (entry.data_mask.length != len) return; @@ -438,7 +502,6 @@ public class ScanManager { entry.company_mask, 0, 0, 0, 0, "", "", (byte) 0, entry.data, entry.data_mask); break; - } } } -- 2.11.0