ScanManager.java revision 86c292ab0f0fed25345a2eaef0fd92ff9c72a9e5
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.bluetooth.gatt; 18 19import android.bluetooth.BluetoothAdapter; 20import android.bluetooth.le.ScanFilter; 21import android.bluetooth.le.ScanSettings; 22import android.os.Handler; 23import android.os.HandlerThread; 24import android.os.Looper; 25import android.os.Message; 26import android.util.Log; 27 28import com.android.bluetooth.Utils; 29import com.android.bluetooth.btservice.AdapterService; 30 31import java.util.ArrayDeque; 32import java.util.ArrayList; 33import java.util.Deque; 34import java.util.HashMap; 35import java.util.HashSet; 36import java.util.List; 37import java.util.Map; 38import java.util.Set; 39import java.util.concurrent.CountDownLatch; 40import java.util.concurrent.TimeUnit; 41 42/** 43 * Class that handles Bluetooth LE scan related operations. 44 * 45 * @hide 46 */ 47public class ScanManager { 48 private static final boolean DBG = GattServiceConfig.DBG; 49 private static final String TAG = GattServiceConfig.TAG_PREFIX + "ScanManager"; 50 51 // Result type defined in bt stack. Need to be accessed by GattService. 52 static final int SCAN_RESULT_TYPE_TRUNCATED = 1; 53 static final int SCAN_RESULT_TYPE_FULL = 2; 54 55 // Internal messages for handling BLE scan operations. 56 private static final int MSG_START_BLE_SCAN = 0; 57 private static final int MSG_STOP_BLE_SCAN = 1; 58 private static final int MSG_FLUSH_BATCH_RESULTS = 2; 59 60 // Timeout for each controller operation. 61 private static final int OPERATION_TIME_OUT_MILLIS = 500; 62 63 private GattService mService; 64 private ScanNative mScanNative; 65 private ClientHandler mHandler; 66 67 private Set<ScanClient> mRegularScanClients; 68 private Set<ScanClient> mBatchClients; 69 70 private CountDownLatch mLatch; 71 72 ScanManager(GattService service) { 73 mRegularScanClients = new HashSet<ScanClient>(); 74 mBatchClients = new HashSet<ScanClient>(); 75 mScanNative = new ScanNative(); 76 mService = service; 77 } 78 79 void start() { 80 HandlerThread thread = new HandlerThread("BluetoothScanManager"); 81 thread.start(); 82 mHandler = new ClientHandler(thread.getLooper()); 83 } 84 85 void cleanup() { 86 mRegularScanClients.clear(); 87 mBatchClients.clear(); 88 } 89 90 /** 91 * Returns the combined scan queue of regular scans and batch scans. 92 */ 93 List<ScanClient> scanQueue() { 94 List<ScanClient> clients = new ArrayList<>(); 95 clients.addAll(mRegularScanClients); 96 clients.addAll(mBatchClients); 97 return clients; 98 } 99 100 void startScan(ScanClient client) { 101 sendMessage(MSG_START_BLE_SCAN, client); 102 } 103 104 void stopScan(ScanClient client) { 105 sendMessage(MSG_STOP_BLE_SCAN, client); 106 } 107 108 void flushBatchScanResults(ScanClient client) { 109 sendMessage(MSG_FLUSH_BATCH_RESULTS, client); 110 } 111 112 void callbackDone(int clientIf, int status) { 113 logd("callback done for clientIf - " + clientIf + " status - " + status); 114 if (status == 0) { 115 mLatch.countDown(); 116 } 117 // TODO: add a callback for scan failure. 118 } 119 120 private void sendMessage(int what, ScanClient client) { 121 Message message = new Message(); 122 message.what = what; 123 message.obj = client; 124 mHandler.sendMessage(message); 125 } 126 127 private boolean isFilteringSupported() { 128 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 129 return adapter.isOffloadedFilteringSupported(); 130 } 131 132 // Handler class that handles BLE scan operations. 133 private class ClientHandler extends Handler { 134 135 ClientHandler(Looper looper) { 136 super(looper); 137 } 138 139 @Override 140 public void handleMessage(Message msg) { 141 ScanClient client = (ScanClient) msg.obj; 142 switch (msg.what) { 143 case MSG_START_BLE_SCAN: 144 handleStartScan(client); 145 break; 146 case MSG_STOP_BLE_SCAN: 147 handleStopScan(client); 148 break; 149 case MSG_FLUSH_BATCH_RESULTS: 150 handleFlushBatchResults(client); 151 break; 152 default: 153 // Shouldn't happen. 154 Log.e(TAG, "received an unkown message : " + msg.what); 155 } 156 } 157 158 void handleStartScan(ScanClient client) { 159 Utils.enforceAdminPermission(mService); 160 logd("handling starting scan"); 161 162 if (!isScanSupported(client)) { 163 Log.e(TAG, "Scan settings not supported"); 164 return; 165 } 166 167 if (mRegularScanClients.contains(client) || mBatchClients.contains(client)) { 168 Log.e(TAG, "Scan already started"); 169 return; 170 } 171 // Begin scan operations. 172 if (isBatchClient(client)) { 173 mBatchClients.add(client); 174 mScanNative.startBatchScan(client); 175 } else { 176 mRegularScanClients.add(client); 177 mScanNative.startRegularScan(client); 178 } 179 } 180 181 void handleStopScan(ScanClient client) { 182 Utils.enforceAdminPermission(mService); 183 if (mRegularScanClients.contains(client)) { 184 mRegularScanClients.remove(client); 185 mScanNative.stopRegularScan(client); 186 } else { 187 mBatchClients.remove(client); 188 mScanNative.stopBatchScan(client); 189 } 190 } 191 192 void handleFlushBatchResults(ScanClient client) { 193 Utils.enforceAdminPermission(mService); 194 if (!mBatchClients.contains(client)) { 195 return; 196 } 197 mScanNative.flushBatchResults(client.clientIf); 198 } 199 200 private boolean isBatchClient(ScanClient client) { 201 if (client == null || client.settings == null) { 202 return false; 203 } 204 ScanSettings settings = client.settings; 205 return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES && 206 settings.getReportDelayMillis() != 0; 207 } 208 209 private boolean isScanSupported(ScanClient client) { 210 if (client == null || client.settings == null) { 211 return true; 212 } 213 ScanSettings settings = client.settings; 214 if (isFilteringSupported()) { 215 return true; 216 } 217 return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES && 218 settings.getReportDelayMillis() == 0; 219 } 220 } 221 222 private class ScanNative { 223 224 // Delivery mode defined in bt stack. 225 private static final int DELIVERY_MODE_IMMEDIATE = 0; 226 private static final int DELIVERY_MODE_ON_FOUND = 1; 227 private static final int DELIVERY_MODE_BATCH = 2; 228 229 private static final int ALLOW_ALL_FILTER_INDEX = 1; 230 private static final int ALLOW_ALL_FILTER_SELECTION = 0; 231 232 // The logic is AND for each filter field. 233 private static final int LIST_LOGIC_TYPE = 0x1111111; 234 private static final int FILTER_LOGIC_TYPE = 1; 235 // Filter indices that are available to user. It's sad we need to maintain filter index. 236 private final Deque<Integer> mFilterIndexStack; 237 // Map of clientIf and Filter indices used by client. 238 private final Map<Integer, Deque<Integer>> mClientFilterIndexMap; 239 240 ScanNative() { 241 mFilterIndexStack = new ArrayDeque<Integer>(); 242 mClientFilterIndexMap = new HashMap<Integer, Deque<Integer>>(); 243 } 244 245 private void resetCountDownLatch() { 246 mLatch = new CountDownLatch(1); 247 } 248 249 // Returns true if mLatch reaches 0, false if timeout or interrupted. 250 private boolean waitForCallback() { 251 try { 252 return mLatch.await(OPERATION_TIME_OUT_MILLIS, TimeUnit.MILLISECONDS); 253 } catch (InterruptedException e) { 254 return false; 255 } 256 } 257 258 void startRegularScan(ScanClient client) { 259 if (mFilterIndexStack.isEmpty() && isFilteringSupported()) { 260 initFilterIndexStack(); 261 } 262 if (isFilteringSupported()) { 263 configureScanFilters(client); 264 } 265 // Start scan native only for the first client. 266 if (mRegularScanClients.size() == 1) { 267 gattClientScanNative(true); 268 } 269 } 270 271 void startBatchScan(ScanClient client) { 272 if (mFilterIndexStack.isEmpty() && isFilteringSupported()) { 273 initFilterIndexStack(); 274 } 275 configureScanFilters(client); 276 int fullScanPercent = 50; 277 int notifyThreshold = 95; 278 resetCountDownLatch(); 279 logd("configuring batch scan storage, appIf " + client.clientIf); 280 gattClientConfigBatchScanStorageNative(client.clientIf, fullScanPercent, 281 100 - fullScanPercent, notifyThreshold); 282 waitForCallback(); 283 int scanMode = getResultType(client.settings); 284 // TODO: configure scan parameters. 285 int scanIntervalUnit = 8; 286 int scanWindowUnit = 8; 287 int discardRule = 2; 288 int addressType = 0; 289 logd("Starting BLE batch scan"); 290 gattClientStartBatchScanNative(client.clientIf, scanMode, scanIntervalUnit, scanWindowUnit, 291 addressType, 292 discardRule); 293 } 294 295 void stopRegularScan(ScanClient client) { 296 // Remove scan filters and recycle filter indices. 297 removeScanFilters(client.clientIf); 298 mRegularScanClients.remove(client); 299 if (mRegularScanClients.isEmpty()) { 300 logd("stop scan"); 301 gattClientScanNative(false); 302 } 303 } 304 305 void stopBatchScan(ScanClient client) { 306 removeScanFilters(client.clientIf); 307 mBatchClients.remove(client); 308 gattClientStopBatchScanNative(client.clientIf); 309 } 310 311 void flushBatchResults(int clientIf) { 312 logd("flushPendingBatchResults - clientIf = " + clientIf); 313 ScanClient client = getBatchScanClient(clientIf); 314 if (client == null) { 315 logd("unknown client : " + clientIf); 316 return; 317 } 318 int resultType = getResultType(client.settings); 319 gattClientReadScanReportsNative(client.clientIf, resultType); 320 } 321 322 // Add scan filters. The logic is: 323 // If no offload filter can/needs to be set, set ALLOW_ALL filter. 324 // Otherwise offload all filters to hardware and enable all filters. 325 private void configureScanFilters(ScanClient client) { 326 int clientIf = client.clientIf; 327 resetCountDownLatch(); 328 gattClientScanFilterEnableNative(clientIf, true); 329 waitForCallback(); 330 331 if (shouldUseAllowAllFilter(client)) { 332 resetCountDownLatch(); 333 configureFilterParamter(clientIf, client, ALLOW_ALL_FILTER_SELECTION, 334 ALLOW_ALL_FILTER_INDEX); 335 waitForCallback(); 336 } else { 337 Deque<Integer> clientFilterIndices = new ArrayDeque<Integer>(); 338 for (ScanFilter filter : client.filters) { 339 ScanFilterQueue queue = new ScanFilterQueue(); 340 queue.addScanFilter(filter); 341 int featureSelection = queue.getFeatureSelection(); 342 int filterIndex = mFilterIndexStack.pop(); 343 while (!queue.isEmpty()) { 344 resetCountDownLatch(); 345 addFilterToController(clientIf, queue.pop(), filterIndex); 346 waitForCallback(); 347 } 348 resetCountDownLatch(); 349 configureFilterParamter(clientIf, client, featureSelection, filterIndex); 350 waitForCallback(); 351 clientFilterIndices.add(filterIndex); 352 } 353 mClientFilterIndexMap.put(clientIf, clientFilterIndices); 354 } 355 } 356 357 private void removeScanFilters(int clientIf) { 358 logd("removeScanFilters, clientIf - " + clientIf); 359 Deque<Integer> filterIndices = mClientFilterIndexMap.remove(clientIf); 360 if (filterIndices != null) { 361 mFilterIndexStack.addAll(filterIndices); 362 for (Integer filterIndex : filterIndices) { 363 resetCountDownLatch(); 364 gattClientScanFilterParamDeleteNative(clientIf, filterIndex); 365 waitForCallback(); 366 } 367 } 368 } 369 370 private ScanClient getBatchScanClient(int clientIf) { 371 for (ScanClient client : mBatchClients) { 372 if (client.clientIf == clientIf) { 373 return client; 374 } 375 } 376 return null; 377 } 378 379 /** 380 * Return batch scan result type value defined in bt stack. 381 */ 382 private int getResultType(ScanSettings settings) { 383 return settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_FULL ? 384 SCAN_RESULT_TYPE_FULL : SCAN_RESULT_TYPE_TRUNCATED; 385 } 386 387 // Check if ALLOW_FILTER should be used for the client. 388 private boolean shouldUseAllowAllFilter(ScanClient client) { 389 if (client == null) { 390 return true; 391 } 392 if (client.filters == null || client.filters.isEmpty()) { 393 return true; 394 } 395 return client.filters.size() < mClientFilterIndexMap.size(); 396 } 397 398 private void addFilterToController(int clientIf, ScanFilterQueue.Entry entry, 399 int filterIndex) { 400 logd("addFilterToController: " + entry.type); 401 switch (entry.type) { 402 case ScanFilterQueue.TYPE_DEVICE_ADDRESS: 403 logd("add address " + entry.address); 404 gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 405 0, 406 "", entry.address, (byte) 0, new byte[0], new byte[0]); 407 break; 408 409 case ScanFilterQueue.TYPE_SERVICE_DATA: 410 gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 411 0, 412 "", "", (byte) 0, entry.data, entry.data_mask); 413 break; 414 415 case ScanFilterQueue.TYPE_SERVICE_UUID: 416 case ScanFilterQueue.TYPE_SOLICIT_UUID: 417 gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 418 entry.uuid.getLeastSignificantBits(), 419 entry.uuid.getMostSignificantBits(), 420 entry.uuid_mask.getLeastSignificantBits(), 421 entry.uuid_mask.getMostSignificantBits(), 422 "", "", (byte) 0, new byte[0], new byte[0]); 423 break; 424 425 case ScanFilterQueue.TYPE_LOCAL_NAME: 426 logd("adding filters: " + entry.name); 427 gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 428 0, 429 entry.name, "", (byte) 0, new byte[0], new byte[0]); 430 break; 431 432 case ScanFilterQueue.TYPE_MANUFACTURER_DATA: 433 { 434 int len = entry.data.length; 435 if (entry.data_mask.length != len) 436 return; 437 gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, entry.company, 438 entry.company_mask, 0, 0, 0, 0, "", "", (byte) 0, 439 entry.data, entry.data_mask); 440 break; 441 } 442 } 443 } 444 445 private void initFilterIndexStack() { 446 int maxFiltersSupported = 447 AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported(); 448 // Start from index 2 as index 0 is reserved for ALLOW_ALL filter in Settings app and 449 // index 1 is reserved for ALLOW_ALL filter for regular apps. 450 for (int i = 2; i < maxFiltersSupported; ++i) { 451 mFilterIndexStack.add(i); 452 } 453 } 454 455 // Configure filter parameters. 456 private void configureFilterParamter(int clientIf, ScanClient client, int featureSelection, 457 int filterIndex) { 458 int deliveryMode = getDeliveryMode(client); 459 int rssiThreshold = Byte.MIN_VALUE; 460 gattClientScanFilterParamAddNative( 461 clientIf, filterIndex, featureSelection, LIST_LOGIC_TYPE, 462 FILTER_LOGIC_TYPE, rssiThreshold, rssiThreshold, deliveryMode, 463 0, 0, 0); 464 } 465 466 // Get delivery mode based on scan settings. 467 private int getDeliveryMode(ScanClient client) { 468 if (client == null) { 469 return DELIVERY_MODE_IMMEDIATE; 470 } 471 ScanSettings settings = client.settings; 472 if (settings == null) { 473 return DELIVERY_MODE_IMMEDIATE; 474 } 475 // TODO: double check whether it makes sense to use the same delivery mode for found and 476 // lost. 477 if ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0 478 || (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { 479 return DELIVERY_MODE_ON_FOUND; 480 } 481 return settings.getReportDelayMillis() == 0 ? DELIVERY_MODE_IMMEDIATE 482 : DELIVERY_MODE_BATCH; 483 } 484 485 /************************** Regular scan related native methods **************************/ 486 private native void gattClientScanNative(boolean start); 487 488 /************************** Filter related native methods ********************************/ 489 private native void gattClientScanFilterAddNative(int client_if, 490 int filter_type, int filter_index, int company_id, 491 int company_id_mask, long uuid_lsb, long uuid_msb, 492 long uuid_mask_lsb, long uuid_mask_msb, String name, 493 String address, byte addr_type, byte[] data, byte[] mask); 494 495 private native void gattClientScanFilterDeleteNative(int client_if, 496 int filter_type, int filter_index, int company_id, 497 int company_id_mask, long uuid_lsb, long uuid_msb, 498 long uuid_mask_lsb, long uuid_mask_msb, String name, 499 String address, byte addr_type, byte[] data, byte[] mask); 500 501 private native void gattClientScanFilterParamAddNative( 502 int client_if, int filt_index, int feat_seln, 503 int list_logic_type, int filt_logic_type, int rssi_high_thres, 504 int rssi_low_thres, int dely_mode, int found_timeout, 505 int lost_timeout, int found_timeout_cnt); 506 507 // Note this effectively remove scan filters for ALL clients. 508 private native void gattClientScanFilterParamClearAllNative( 509 int client_if); 510 511 private native void gattClientScanFilterParamDeleteNative( 512 int client_if, int filt_index); 513 514 private native void gattClientScanFilterClearNative(int client_if, 515 int filter_index); 516 517 private native void gattClientScanFilterEnableNative(int client_if, 518 boolean enable); 519 520 /************************** Batch related native methods *********************************/ 521 private native void gattClientConfigBatchScanStorageNative(int client_if, 522 int max_full_reports_percent, int max_truncated_reports_percent, 523 int notify_threshold_percent); 524 525 private native void gattClientStartBatchScanNative(int client_if, int scan_mode, 526 int scan_interval_unit, int scan_window_unit, int address_type, int discard_rule); 527 528 private native void gattClientStopBatchScanNative(int client_if); 529 530 private native void gattClientReadScanReportsNative(int client_if, int scan_type); 531 } 532 533 private void logd(String s) { 534 Log.d(TAG, s); 535 } 536} 537