Loading android/app/jni/com_android_bluetooth_gatt.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -1706,7 +1706,6 @@ static JNINativeMethod sStateMachineMethods[] = { {"gattClientConfigBatchScanStorageNative", "(IIII)V",(void *) gattClientConfigBatchScanStorageNative}, {"gattClientStartBatchScanNative", "(IIIIII)V", (void *) gattClientStartBatchScanNative}, {"gattClientStopBatchScanNative", "(I)V", (void *) gattClientStopBatchScanNative}, {"gattClientReadScanReportsNative", "(II)V", (void *) gattClientReadScanReportsNative}, {"gattClientScanFilterParamAddNative", "(IIIIIIIIIII)V", (void *) gattClientScanFilterParamAddNative}, {"gattClientScanFilterParamDeleteNative", "(II)V", (void *) gattClientScanFilterParamDeleteNative}, {"gattClientScanFilterParamClearAllNative", "(I)V", (void *) gattClientScanFilterParamClearAllNative}, Loading Loading @@ -1761,6 +1760,7 @@ static JNINativeMethod sMethods[] = { {"gattSetAdvDataNative", "(IZZZIII[B[B[B)V", (void *) gattSetAdvDataNative}, {"gattSetScanParametersNative", "(II)V", (void *) gattSetScanParametersNative}, {"gattClientReadScanReportsNative", "(II)V", (void *) gattClientReadScanReportsNative}, {"gattTestNative", "(IJJLjava/lang/String;IIIII)V", (void *) gattTestNative}, }; Loading android/app/src/com/android/bluetooth/gatt/GattService.java +121 −11 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import com.android.bluetooth.btservice.ProfileService; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; Loading Loading @@ -97,6 +98,12 @@ public class GattService extends ProfileService { private static final int SCAN_MODE_LOW_LATENCY_WINDOW_MS = 5000; private static final int SCAN_MODE_LOW_LATENCY_INTERVAL_MS = 5000; private static final int MAC_ADDRESS_LENGTH = 6; // Batch scan related constants. private static final int TRUNCATED_RESULT_SIZE = 11; private static final int TIME_STAMP_LENGTH = 2; /** * Search queue to serialize remote onbject inspection. */ Loading Loading @@ -351,6 +358,13 @@ public class GattService extends ProfileService { service.stopScan(appIf, isServer); } @Override public void flushPendingBatchResults(int appIf, boolean isServer) { GattService service = getService(); if (service == null) return; service.flushPendingBatchResults(appIf, isServer); } public void clientConnect(int clientIf, String address, boolean isDirect, int transport) { GattService service = getService(); if (service == null) return; Loading Loading @@ -662,9 +676,9 @@ public class GattService extends ProfileService { if (app != null) { BluetoothDevice device = BluetoothAdapter.getDefaultAdapter() .getRemoteDevice(address); long scanTimeMicros = long scanTimeNanos = TimeUnit.NANOSECONDS.toMicros(SystemClock.elapsedRealtimeNanos()); ScanResult result = new ScanResult(device, adv_data, rssi, scanTimeMicros); ScanResult result = new ScanResult(device, adv_data, rssi, scanTimeNanos); if (matchesFilters(client, result)) { try { app.callback.onScanResult(address, rssi, adv_data); Loading Loading @@ -1037,7 +1051,7 @@ public class GattService extends ProfileService { if (DBG) { Log.d(TAG, "onBatchScanStorageConfigured() - clientIf="+ clientIf + ", status=" + status); } mStateMachine.callbackDone(); } // TODO: split into two different callbacks : onBatchScanStarted and onBatchScanStopped. Loading @@ -1046,20 +1060,100 @@ public class GattService extends ProfileService { Log.d(TAG, "onBatchScanStartStopped() - clientIf=" + clientIf + ", status=" + status + ", startStopAction=" + startStopAction); } mStateMachine.callbackDone(); } void onBatchScanReports(int status, int clientIf, int reportType, int numRecords, byte[] recordData) { byte[] recordData) throws RemoteException { if (DBG) { Log.d(TAG, "onBatchScanReports() - clientIf=" + clientIf + ", status=" + status + ", reportType=" + reportType + ", numRecords=" + numRecords); } ClientMap.App app = mClientMap.getById(clientIf); if (app == null) return; Set<ScanResult> results = parseBatchScanResults(numRecords, reportType, recordData); app.callback.onBatchScanResults(new ArrayList<ScanResult>(results)); } private Set<ScanResult> parseBatchScanResults(int numRecords, int reportType, byte[] batchRecord) { if (numRecords == 0) { return Collections.emptySet(); } if (DBG) Log.d(TAG, "current time is " + SystemClock.elapsedRealtimeNanos()); if (reportType == GattServiceStateMachine.SCAN_RESULT_TYPE_TRUNCATED) { return parseTruncatedResults(numRecords, batchRecord); } else { return parseFullResults(numRecords, batchRecord); } } private Set<ScanResult> parseTruncatedResults(int numRecords, byte[] batchRecord) { Set<ScanResult> results = new HashSet<ScanResult>(numRecords); for (int i = 0; i < numRecords; ++i) { byte[] record = extractBytes(batchRecord, i * TRUNCATED_RESULT_SIZE, TRUNCATED_RESULT_SIZE); byte[] address = extractBytes(batchRecord, 0, 6); BluetoothDevice device = mAdapter.getRemoteDevice(address); int rssi = record[8]; // Timestamp is in every 50 ms. long timestampNanos = parseTimestampNanos(extractBytes(record, 9, 2)); results.add(new ScanResult(device, new byte[0], rssi, timestampNanos)); } return results; } private long parseTimestampNanos(byte[] data) { long timestampUnit = data[1] & 0xFF << 8 + data[0]; long timestampNanos = SystemClock.elapsedRealtimeNanos() - TimeUnit.MILLISECONDS.toNanos(timestampUnit * 50); return timestampNanos; } private Set<ScanResult> parseFullResults(int numRecords, byte[] batchRecord) { Set<ScanResult> results = new HashSet<ScanResult>(numRecords); int position = 0; while (position < batchRecord.length) { byte[] address = extractBytes(batchRecord, position, 6); BluetoothDevice device = mAdapter.getRemoteDevice(address); position += 6; // Skip address type. position++; // Skip tx power level. position++; int rssi = batchRecord[position++]; long timestampNanos = parseTimestampNanos(extractBytes(batchRecord, position, 2)); position += 2; // Combine advertise packet and scan response packet. int advertisePacketLen = batchRecord[position++]; byte[] advertiseBytes = extractBytes(batchRecord, position, advertisePacketLen); position += advertisePacketLen; int scanResponsePacketLen = batchRecord[position++]; byte[] scanResponseBytes = extractBytes(batchRecord, position, scanResponsePacketLen); position += scanResponsePacketLen; byte[] scanRecord = new byte[advertisePacketLen + scanResponsePacketLen]; System.arraycopy(advertiseBytes, 0, scanRecord, 0, advertisePacketLen); System.arraycopy(scanResponseBytes, 0, scanRecord, advertisePacketLen, scanResponsePacketLen); results.add(new ScanResult(device, scanRecord, rssi, timestampNanos)); } return results; } // Helper method to extract bytes from byte array. private static byte[] extractBytes(byte[] scanRecord, int start, int length) { byte[] bytes = new byte[length]; System.arraycopy(scanRecord, start, bytes, 0, length); return bytes; } void onBatchScanThresholdCrossed(int clientIf) { if (DBG) { Log.d(TAG, "onBatchScanThresholdCrossed() - clientIf=" + clientIf); } boolean isServer = false; flushPendingBatchResults(clientIf, isServer); } void onTrackAdvFoundLost(int filterIndex, int addrType, String address, int advState, Loading Loading @@ -1289,10 +1383,24 @@ public class GattService extends ProfileService { if (DBG) Log.d(TAG, "startScan() - adding client=" + appIf); mScanQueue.add(new ScanClient(appIf, isServer, settings, filters)); } sendStartScanMessage(appIf, new HashSet<ScanFilter>(filters)); sendStartScanMessage(appIf, getScanClient(appIf, isServer)); } } void flushPendingBatchResults(int clientIf, boolean isServer) { if (DBG) Log.d(TAG, "flushPendingBatchResults - clientIf=" + clientIf + ", isServer=" + isServer); ScanClient scanClient = getScanClient(clientIf, isServer); if (scanClient == null || scanClient.settings == null || scanClient.settings.getReportDelayNanos() == 0) { // Not a batch scan client. Log.e(TAG, "called flushPendingBatchResults without a proper app!"); return; } int resultType = mStateMachine.getResultType(scanClient.settings); gattClientReadScanReportsNative(clientIf, resultType); } void configureScanParams(int appIf) { if (DBG) Log.d(TAG, "configureScanParams() - queue=" + mScanQueue.size()); int curScanSetting = Integer.MIN_VALUE; Loading Loading @@ -1345,10 +1453,10 @@ public class GattService extends ProfileService { } } private void sendStartScanMessage(int clientIf, Set<ScanFilter> filters) { private void sendStartScanMessage(int clientIf, ScanClient client) { Message message = mStateMachine.obtainMessage(GattServiceStateMachine.START_BLE_SCAN); message.arg1 = clientIf; message.obj = filters; message.obj = client; mStateMachine.sendMessage(message); } Loading Loading @@ -2471,4 +2579,6 @@ public class GattService extends ProfileService { private native void gattServerSendResponseNative (int server_if, int conn_id, int trans_id, int status, int handle, int offset, byte[] val, int auth_req); private native void gattClientReadScanReportsNative(int client_if, int scan_type); } android/app/src/com/android/bluetooth/gatt/GattServiceStateMachine.java +94 −30 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.bluetooth.le.AdvertiseCallback; import android.bluetooth.le.AdvertiseSettings; import android.bluetooth.le.AdvertisementData; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanSettings; import android.os.Message; import android.os.ParcelUuid; import android.os.RemoteException; Loading @@ -34,6 +35,7 @@ import java.util.ArrayDeque; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; Loading Loading @@ -105,6 +107,15 @@ public class GattServiceStateMachine extends StateMachine { private static final int ADVERTISING_EVENT_TYPE_SCANNABLE = 2; private static final int ADVERTISING_EVENT_TYPE_NON_CONNECTABLE = 3; // Result type defined in bt stack. static final int SCAN_RESULT_TYPE_TRUNCATED = 1; static final int SCAN_RESULT_TYPE_FULL = 2; // Delivery mode defined in bt stack. private static final int DELIVERY_MODE_IMMEDIATE = 0; private static final int DELIVERY_MODE_ON_FOUND = 1; private static final int DELIVERY_MODE_BATCH = 2; private final GattService mService; private final Map<Integer, AdvertiseClient> mAdvertiseClients; // Keep track of whether scan filters exist. Loading @@ -114,6 +125,7 @@ public class GattServiceStateMachine extends StateMachine { private final Idle mIdle; private final ScanStarting mScanStarting; private final AdvertiseStarting mAdvertiseStarting; // A count down latch used to block on stack callback. MUST reset before use. private CountDownLatch mCallbackLatch; // It's sad we need to maintain this. Loading Loading @@ -160,9 +172,8 @@ public class GattServiceStateMachine extends StateMachine { } void initFilterIndexStack() { int maxFiltersSupported = 16; // AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported(); log("maxFiltersSupported = " + maxFiltersSupported); int maxFiltersSupported = AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported(); for (int i = 1; i < maxFiltersSupported; ++i) { mFilterIndexStack.add(i); } Loading Loading @@ -198,7 +209,6 @@ public class GattServiceStateMachine extends StateMachine { transitionTo(mScanStarting); break; case STOP_BLE_SCAN: // Note this should only happen no client is doing scans any more. int clientIf = message.arg1; resetCallbackLatch(); gattClientScanFilterParamClearAllNative(clientIf); Loading Loading @@ -295,8 +305,12 @@ public class GattServiceStateMachine extends StateMachine { resetCallbackLatch(); gattClientScanFilterEnableNative(clientIf, true); waitForCallback(); Set<ScanFilter> filters = (Set<ScanFilter>) message.obj; if (filters != null && filters.size() <= mFilterIndexStack.size()) { ScanClient client = (ScanClient) message.obj; if (client != null) { Set<ScanFilter> filters = new HashSet<ScanFilter>(client.filters); // TODO: add ALLOW_ALL filter. if (filters != null && !filters.isEmpty() && filters.size() <= mFilterIndexStack.size()) { Deque<Integer> clientFilterIndices = new ArrayDeque<Integer>(); for (ScanFilter filter : filters) { ScanFilterQueue queue = new ScanFilterQueue(); Loading @@ -309,18 +323,26 @@ public class GattServiceStateMachine extends StateMachine { waitForCallback(); } resetCallbackLatch(); int deliveryMode = getDeliveryMode(client.settings); logd("deliveryMode : " + deliveryMode); int listLogicType = 0x1111111; int filterLogicType = 1; int rssiThreshold = Byte.MIN_VALUE; gattClientScanFilterParamAddNative( clientIf, filterIndex, featureSelection, 0x1111111, 1, -127, -127, 0, 0, 0, 0); clientIf, filterIndex, featureSelection, listLogicType, filterLogicType, rssiThreshold, rssiThreshold, deliveryMode, 0, 0, 0); waitForCallback(); clientFilterIndices.add(filterIndex); } mClientFilterIndexMap.put(clientIf, clientFilterIndices); } sendMessage(ENABLE_BLE_SCAN); } sendMessage(ENABLE_BLE_SCAN, client); break; case ENABLE_BLE_SCAN: gattClientScanNative(true); client = (ScanClient) message.obj; enableBleScan(client); removeMessages(OPERATION_TIMEOUT); transitionTo(mIdle); break; Loading @@ -335,6 +357,33 @@ public class GattServiceStateMachine extends StateMachine { } private void enableBleScan(ScanClient client) { if (client == null || client.settings == null || client.settings.getReportDelayNanos() == 0) { logd("enabling ble scan, appIf " + client.appIf); gattClientScanNative(true); return; } int fullScanPercent = 20; int notifyThreshold = 95; resetCallbackLatch(); if (DBG) logd("configuring batch scan storage, appIf " + client.appIf); gattClientConfigBatchScanStorageNative(client.appIf, fullScanPercent, 100 - fullScanPercent, notifyThreshold); waitForCallback(); int scanMode = getResultType(client.settings); // TODO: configure scan parameters. int scanIntervalUnit = 8; int scanWindowUnit = 8; int discardRule = 2; int addressType = 0; logd("Starting BLE batch scan"); gattClientStartBatchScanNative(client.appIf, scanMode, scanIntervalUnit, scanWindowUnit, addressType, discardRule); } private void resetCallbackLatch() { mCallbackLatch = new CountDownLatch(1); } Loading @@ -355,21 +404,18 @@ public class GattServiceStateMachine extends StateMachine { log("addFilterToController: " + entry.type); switch (entry.type) { case ScanFilterQueue.TYPE_DEVICE_ADDRESS: // TBD appropriate params need to be passed here log("add address " + entry.address); gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 0, "", entry.address, (byte) 0, new byte[0], new byte[0]); break; case ScanFilterQueue.TYPE_SERVICE_DATA: // TBD appropriate params need to be passed here gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 0, "", "", (byte) 0, entry.data, entry.data_mask); break; case ScanFilterQueue.TYPE_SERVICE_UUID: case ScanFilterQueue.TYPE_SOLICIT_UUID: // TBD appropriate params need to be passed here gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, entry.uuid.getLeastSignificantBits(), entry.uuid.getMostSignificantBits(), Loading @@ -379,7 +425,6 @@ public class GattServiceStateMachine extends StateMachine { break; case ScanFilterQueue.TYPE_LOCAL_NAME: // TBD appropriate params need to be passed here loge("adding filters: " + entry.name); gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 0, entry.name, "", (byte) 0, new byte[0], new byte[0]); Loading @@ -390,7 +435,6 @@ public class GattServiceStateMachine extends StateMachine { int len = entry.data.length; if (entry.data_mask.length != len) return; // TBD appropriate params need to be passed here gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, entry.company, entry.company_mask, 0, 0, 0, 0, "", "", (byte) 0, entry.data, entry.data_mask); Loading Loading @@ -545,6 +589,28 @@ public class GattServiceStateMachine extends StateMachine { } } /** * Return batch scan result type value defined in bt stack. */ int getResultType(ScanSettings settings) { return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_FULL ? SCAN_RESULT_TYPE_FULL : SCAN_RESULT_TYPE_TRUNCATED; } // Get delivery mode based on scan settings. private int getDeliveryMode(ScanSettings settings) { if (settings == null) { return DELIVERY_MODE_IMMEDIATE; } // TODO: double check whether it makes sense to use the same delivery mode for found and // lost. if (settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ON_FOUND || settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ON_LOST) { return DELIVERY_MODE_ON_FOUND; } return settings.getReportDelayNanos() == 0 ? DELIVERY_MODE_IMMEDIATE : DELIVERY_MODE_BATCH; } private long millsToUnit(int millisecond) { return TimeUnit.MILLISECONDS.toMicros(millisecond) / ADVERTISING_INTERVAL_MICROS_PER_UNIT; } Loading Loading @@ -605,6 +671,4 @@ public class GattServiceStateMachine extends StateMachine { int scan_interval_unit, int scan_window_unit, int address_type, int discard_rule); private native void gattClientStopBatchScanNative(int client_if); private native void gattClientReadScanReportsNative(int client_if, int scan_type); } Loading
android/app/jni/com_android_bluetooth_gatt.cpp +1 −1 Original line number Diff line number Diff line Loading @@ -1706,7 +1706,6 @@ static JNINativeMethod sStateMachineMethods[] = { {"gattClientConfigBatchScanStorageNative", "(IIII)V",(void *) gattClientConfigBatchScanStorageNative}, {"gattClientStartBatchScanNative", "(IIIIII)V", (void *) gattClientStartBatchScanNative}, {"gattClientStopBatchScanNative", "(I)V", (void *) gattClientStopBatchScanNative}, {"gattClientReadScanReportsNative", "(II)V", (void *) gattClientReadScanReportsNative}, {"gattClientScanFilterParamAddNative", "(IIIIIIIIIII)V", (void *) gattClientScanFilterParamAddNative}, {"gattClientScanFilterParamDeleteNative", "(II)V", (void *) gattClientScanFilterParamDeleteNative}, {"gattClientScanFilterParamClearAllNative", "(I)V", (void *) gattClientScanFilterParamClearAllNative}, Loading Loading @@ -1761,6 +1760,7 @@ static JNINativeMethod sMethods[] = { {"gattSetAdvDataNative", "(IZZZIII[B[B[B)V", (void *) gattSetAdvDataNative}, {"gattSetScanParametersNative", "(II)V", (void *) gattSetScanParametersNative}, {"gattClientReadScanReportsNative", "(II)V", (void *) gattClientReadScanReportsNative}, {"gattTestNative", "(IJJLjava/lang/String;IIIII)V", (void *) gattTestNative}, }; Loading
android/app/src/com/android/bluetooth/gatt/GattService.java +121 −11 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import com.android.bluetooth.btservice.ProfileService; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; Loading Loading @@ -97,6 +98,12 @@ public class GattService extends ProfileService { private static final int SCAN_MODE_LOW_LATENCY_WINDOW_MS = 5000; private static final int SCAN_MODE_LOW_LATENCY_INTERVAL_MS = 5000; private static final int MAC_ADDRESS_LENGTH = 6; // Batch scan related constants. private static final int TRUNCATED_RESULT_SIZE = 11; private static final int TIME_STAMP_LENGTH = 2; /** * Search queue to serialize remote onbject inspection. */ Loading Loading @@ -351,6 +358,13 @@ public class GattService extends ProfileService { service.stopScan(appIf, isServer); } @Override public void flushPendingBatchResults(int appIf, boolean isServer) { GattService service = getService(); if (service == null) return; service.flushPendingBatchResults(appIf, isServer); } public void clientConnect(int clientIf, String address, boolean isDirect, int transport) { GattService service = getService(); if (service == null) return; Loading Loading @@ -662,9 +676,9 @@ public class GattService extends ProfileService { if (app != null) { BluetoothDevice device = BluetoothAdapter.getDefaultAdapter() .getRemoteDevice(address); long scanTimeMicros = long scanTimeNanos = TimeUnit.NANOSECONDS.toMicros(SystemClock.elapsedRealtimeNanos()); ScanResult result = new ScanResult(device, adv_data, rssi, scanTimeMicros); ScanResult result = new ScanResult(device, adv_data, rssi, scanTimeNanos); if (matchesFilters(client, result)) { try { app.callback.onScanResult(address, rssi, adv_data); Loading Loading @@ -1037,7 +1051,7 @@ public class GattService extends ProfileService { if (DBG) { Log.d(TAG, "onBatchScanStorageConfigured() - clientIf="+ clientIf + ", status=" + status); } mStateMachine.callbackDone(); } // TODO: split into two different callbacks : onBatchScanStarted and onBatchScanStopped. Loading @@ -1046,20 +1060,100 @@ public class GattService extends ProfileService { Log.d(TAG, "onBatchScanStartStopped() - clientIf=" + clientIf + ", status=" + status + ", startStopAction=" + startStopAction); } mStateMachine.callbackDone(); } void onBatchScanReports(int status, int clientIf, int reportType, int numRecords, byte[] recordData) { byte[] recordData) throws RemoteException { if (DBG) { Log.d(TAG, "onBatchScanReports() - clientIf=" + clientIf + ", status=" + status + ", reportType=" + reportType + ", numRecords=" + numRecords); } ClientMap.App app = mClientMap.getById(clientIf); if (app == null) return; Set<ScanResult> results = parseBatchScanResults(numRecords, reportType, recordData); app.callback.onBatchScanResults(new ArrayList<ScanResult>(results)); } private Set<ScanResult> parseBatchScanResults(int numRecords, int reportType, byte[] batchRecord) { if (numRecords == 0) { return Collections.emptySet(); } if (DBG) Log.d(TAG, "current time is " + SystemClock.elapsedRealtimeNanos()); if (reportType == GattServiceStateMachine.SCAN_RESULT_TYPE_TRUNCATED) { return parseTruncatedResults(numRecords, batchRecord); } else { return parseFullResults(numRecords, batchRecord); } } private Set<ScanResult> parseTruncatedResults(int numRecords, byte[] batchRecord) { Set<ScanResult> results = new HashSet<ScanResult>(numRecords); for (int i = 0; i < numRecords; ++i) { byte[] record = extractBytes(batchRecord, i * TRUNCATED_RESULT_SIZE, TRUNCATED_RESULT_SIZE); byte[] address = extractBytes(batchRecord, 0, 6); BluetoothDevice device = mAdapter.getRemoteDevice(address); int rssi = record[8]; // Timestamp is in every 50 ms. long timestampNanos = parseTimestampNanos(extractBytes(record, 9, 2)); results.add(new ScanResult(device, new byte[0], rssi, timestampNanos)); } return results; } private long parseTimestampNanos(byte[] data) { long timestampUnit = data[1] & 0xFF << 8 + data[0]; long timestampNanos = SystemClock.elapsedRealtimeNanos() - TimeUnit.MILLISECONDS.toNanos(timestampUnit * 50); return timestampNanos; } private Set<ScanResult> parseFullResults(int numRecords, byte[] batchRecord) { Set<ScanResult> results = new HashSet<ScanResult>(numRecords); int position = 0; while (position < batchRecord.length) { byte[] address = extractBytes(batchRecord, position, 6); BluetoothDevice device = mAdapter.getRemoteDevice(address); position += 6; // Skip address type. position++; // Skip tx power level. position++; int rssi = batchRecord[position++]; long timestampNanos = parseTimestampNanos(extractBytes(batchRecord, position, 2)); position += 2; // Combine advertise packet and scan response packet. int advertisePacketLen = batchRecord[position++]; byte[] advertiseBytes = extractBytes(batchRecord, position, advertisePacketLen); position += advertisePacketLen; int scanResponsePacketLen = batchRecord[position++]; byte[] scanResponseBytes = extractBytes(batchRecord, position, scanResponsePacketLen); position += scanResponsePacketLen; byte[] scanRecord = new byte[advertisePacketLen + scanResponsePacketLen]; System.arraycopy(advertiseBytes, 0, scanRecord, 0, advertisePacketLen); System.arraycopy(scanResponseBytes, 0, scanRecord, advertisePacketLen, scanResponsePacketLen); results.add(new ScanResult(device, scanRecord, rssi, timestampNanos)); } return results; } // Helper method to extract bytes from byte array. private static byte[] extractBytes(byte[] scanRecord, int start, int length) { byte[] bytes = new byte[length]; System.arraycopy(scanRecord, start, bytes, 0, length); return bytes; } void onBatchScanThresholdCrossed(int clientIf) { if (DBG) { Log.d(TAG, "onBatchScanThresholdCrossed() - clientIf=" + clientIf); } boolean isServer = false; flushPendingBatchResults(clientIf, isServer); } void onTrackAdvFoundLost(int filterIndex, int addrType, String address, int advState, Loading Loading @@ -1289,10 +1383,24 @@ public class GattService extends ProfileService { if (DBG) Log.d(TAG, "startScan() - adding client=" + appIf); mScanQueue.add(new ScanClient(appIf, isServer, settings, filters)); } sendStartScanMessage(appIf, new HashSet<ScanFilter>(filters)); sendStartScanMessage(appIf, getScanClient(appIf, isServer)); } } void flushPendingBatchResults(int clientIf, boolean isServer) { if (DBG) Log.d(TAG, "flushPendingBatchResults - clientIf=" + clientIf + ", isServer=" + isServer); ScanClient scanClient = getScanClient(clientIf, isServer); if (scanClient == null || scanClient.settings == null || scanClient.settings.getReportDelayNanos() == 0) { // Not a batch scan client. Log.e(TAG, "called flushPendingBatchResults without a proper app!"); return; } int resultType = mStateMachine.getResultType(scanClient.settings); gattClientReadScanReportsNative(clientIf, resultType); } void configureScanParams(int appIf) { if (DBG) Log.d(TAG, "configureScanParams() - queue=" + mScanQueue.size()); int curScanSetting = Integer.MIN_VALUE; Loading Loading @@ -1345,10 +1453,10 @@ public class GattService extends ProfileService { } } private void sendStartScanMessage(int clientIf, Set<ScanFilter> filters) { private void sendStartScanMessage(int clientIf, ScanClient client) { Message message = mStateMachine.obtainMessage(GattServiceStateMachine.START_BLE_SCAN); message.arg1 = clientIf; message.obj = filters; message.obj = client; mStateMachine.sendMessage(message); } Loading Loading @@ -2471,4 +2579,6 @@ public class GattService extends ProfileService { private native void gattServerSendResponseNative (int server_if, int conn_id, int trans_id, int status, int handle, int offset, byte[] val, int auth_req); private native void gattClientReadScanReportsNative(int client_if, int scan_type); }
android/app/src/com/android/bluetooth/gatt/GattServiceStateMachine.java +94 −30 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.bluetooth.le.AdvertiseCallback; import android.bluetooth.le.AdvertiseSettings; import android.bluetooth.le.AdvertisementData; import android.bluetooth.le.ScanFilter; import android.bluetooth.le.ScanSettings; import android.os.Message; import android.os.ParcelUuid; import android.os.RemoteException; Loading @@ -34,6 +35,7 @@ import java.util.ArrayDeque; import java.util.Arrays; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.UUID; Loading Loading @@ -105,6 +107,15 @@ public class GattServiceStateMachine extends StateMachine { private static final int ADVERTISING_EVENT_TYPE_SCANNABLE = 2; private static final int ADVERTISING_EVENT_TYPE_NON_CONNECTABLE = 3; // Result type defined in bt stack. static final int SCAN_RESULT_TYPE_TRUNCATED = 1; static final int SCAN_RESULT_TYPE_FULL = 2; // Delivery mode defined in bt stack. private static final int DELIVERY_MODE_IMMEDIATE = 0; private static final int DELIVERY_MODE_ON_FOUND = 1; private static final int DELIVERY_MODE_BATCH = 2; private final GattService mService; private final Map<Integer, AdvertiseClient> mAdvertiseClients; // Keep track of whether scan filters exist. Loading @@ -114,6 +125,7 @@ public class GattServiceStateMachine extends StateMachine { private final Idle mIdle; private final ScanStarting mScanStarting; private final AdvertiseStarting mAdvertiseStarting; // A count down latch used to block on stack callback. MUST reset before use. private CountDownLatch mCallbackLatch; // It's sad we need to maintain this. Loading Loading @@ -160,9 +172,8 @@ public class GattServiceStateMachine extends StateMachine { } void initFilterIndexStack() { int maxFiltersSupported = 16; // AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported(); log("maxFiltersSupported = " + maxFiltersSupported); int maxFiltersSupported = AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported(); for (int i = 1; i < maxFiltersSupported; ++i) { mFilterIndexStack.add(i); } Loading Loading @@ -198,7 +209,6 @@ public class GattServiceStateMachine extends StateMachine { transitionTo(mScanStarting); break; case STOP_BLE_SCAN: // Note this should only happen no client is doing scans any more. int clientIf = message.arg1; resetCallbackLatch(); gattClientScanFilterParamClearAllNative(clientIf); Loading Loading @@ -295,8 +305,12 @@ public class GattServiceStateMachine extends StateMachine { resetCallbackLatch(); gattClientScanFilterEnableNative(clientIf, true); waitForCallback(); Set<ScanFilter> filters = (Set<ScanFilter>) message.obj; if (filters != null && filters.size() <= mFilterIndexStack.size()) { ScanClient client = (ScanClient) message.obj; if (client != null) { Set<ScanFilter> filters = new HashSet<ScanFilter>(client.filters); // TODO: add ALLOW_ALL filter. if (filters != null && !filters.isEmpty() && filters.size() <= mFilterIndexStack.size()) { Deque<Integer> clientFilterIndices = new ArrayDeque<Integer>(); for (ScanFilter filter : filters) { ScanFilterQueue queue = new ScanFilterQueue(); Loading @@ -309,18 +323,26 @@ public class GattServiceStateMachine extends StateMachine { waitForCallback(); } resetCallbackLatch(); int deliveryMode = getDeliveryMode(client.settings); logd("deliveryMode : " + deliveryMode); int listLogicType = 0x1111111; int filterLogicType = 1; int rssiThreshold = Byte.MIN_VALUE; gattClientScanFilterParamAddNative( clientIf, filterIndex, featureSelection, 0x1111111, 1, -127, -127, 0, 0, 0, 0); clientIf, filterIndex, featureSelection, listLogicType, filterLogicType, rssiThreshold, rssiThreshold, deliveryMode, 0, 0, 0); waitForCallback(); clientFilterIndices.add(filterIndex); } mClientFilterIndexMap.put(clientIf, clientFilterIndices); } sendMessage(ENABLE_BLE_SCAN); } sendMessage(ENABLE_BLE_SCAN, client); break; case ENABLE_BLE_SCAN: gattClientScanNative(true); client = (ScanClient) message.obj; enableBleScan(client); removeMessages(OPERATION_TIMEOUT); transitionTo(mIdle); break; Loading @@ -335,6 +357,33 @@ public class GattServiceStateMachine extends StateMachine { } private void enableBleScan(ScanClient client) { if (client == null || client.settings == null || client.settings.getReportDelayNanos() == 0) { logd("enabling ble scan, appIf " + client.appIf); gattClientScanNative(true); return; } int fullScanPercent = 20; int notifyThreshold = 95; resetCallbackLatch(); if (DBG) logd("configuring batch scan storage, appIf " + client.appIf); gattClientConfigBatchScanStorageNative(client.appIf, fullScanPercent, 100 - fullScanPercent, notifyThreshold); waitForCallback(); int scanMode = getResultType(client.settings); // TODO: configure scan parameters. int scanIntervalUnit = 8; int scanWindowUnit = 8; int discardRule = 2; int addressType = 0; logd("Starting BLE batch scan"); gattClientStartBatchScanNative(client.appIf, scanMode, scanIntervalUnit, scanWindowUnit, addressType, discardRule); } private void resetCallbackLatch() { mCallbackLatch = new CountDownLatch(1); } Loading @@ -355,21 +404,18 @@ public class GattServiceStateMachine extends StateMachine { log("addFilterToController: " + entry.type); switch (entry.type) { case ScanFilterQueue.TYPE_DEVICE_ADDRESS: // TBD appropriate params need to be passed here log("add address " + entry.address); gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 0, "", entry.address, (byte) 0, new byte[0], new byte[0]); break; case ScanFilterQueue.TYPE_SERVICE_DATA: // TBD appropriate params need to be passed here gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 0, "", "", (byte) 0, entry.data, entry.data_mask); break; case ScanFilterQueue.TYPE_SERVICE_UUID: case ScanFilterQueue.TYPE_SOLICIT_UUID: // TBD appropriate params need to be passed here gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, entry.uuid.getLeastSignificantBits(), entry.uuid.getMostSignificantBits(), Loading @@ -379,7 +425,6 @@ public class GattServiceStateMachine extends StateMachine { break; case ScanFilterQueue.TYPE_LOCAL_NAME: // TBD appropriate params need to be passed here loge("adding filters: " + entry.name); gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 0, entry.name, "", (byte) 0, new byte[0], new byte[0]); Loading @@ -390,7 +435,6 @@ public class GattServiceStateMachine extends StateMachine { int len = entry.data.length; if (entry.data_mask.length != len) return; // TBD appropriate params need to be passed here gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, entry.company, entry.company_mask, 0, 0, 0, 0, "", "", (byte) 0, entry.data, entry.data_mask); Loading Loading @@ -545,6 +589,28 @@ public class GattServiceStateMachine extends StateMachine { } } /** * Return batch scan result type value defined in bt stack. */ int getResultType(ScanSettings settings) { return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_FULL ? SCAN_RESULT_TYPE_FULL : SCAN_RESULT_TYPE_TRUNCATED; } // Get delivery mode based on scan settings. private int getDeliveryMode(ScanSettings settings) { if (settings == null) { return DELIVERY_MODE_IMMEDIATE; } // TODO: double check whether it makes sense to use the same delivery mode for found and // lost. if (settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ON_FOUND || settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ON_LOST) { return DELIVERY_MODE_ON_FOUND; } return settings.getReportDelayNanos() == 0 ? DELIVERY_MODE_IMMEDIATE : DELIVERY_MODE_BATCH; } private long millsToUnit(int millisecond) { return TimeUnit.MILLISECONDS.toMicros(millisecond) / ADVERTISING_INTERVAL_MICROS_PER_UNIT; } Loading Loading @@ -605,6 +671,4 @@ public class GattServiceStateMachine extends StateMachine { int scan_interval_unit, int scan_window_unit, int address_type, int discard_rule); private native void gattClientStopBatchScanNative(int client_if); private native void gattClientReadScanReportsNative(int client_if, int scan_type); }