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 17 package com.android.bluetooth.gatt; 18 19 import android.app.AlarmManager; 20 import android.app.PendingIntent; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.le.ScanCallback; 23 import android.bluetooth.le.ScanFilter; 24 import android.bluetooth.le.ScanSettings; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.os.Handler; 30 import android.os.HandlerThread; 31 import android.os.Looper; 32 import android.os.Message; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.os.SystemClock; 36 import android.util.Log; 37 38 import com.android.bluetooth.Utils; 39 import com.android.bluetooth.btservice.AdapterService; 40 import com.android.internal.app.IBatteryStats; 41 42 import java.util.ArrayDeque; 43 import java.util.Deque; 44 import java.util.HashMap; 45 import java.util.HashSet; 46 import java.util.Map; 47 import java.util.Set; 48 import java.util.concurrent.CountDownLatch; 49 import java.util.concurrent.TimeUnit; 50 51 /** 52 * Class that handles Bluetooth LE scan related operations. 53 * 54 * @hide 55 */ 56 public class ScanManager { 57 private static final boolean DBG = GattServiceConfig.DBG; 58 private static final String TAG = GattServiceConfig.TAG_PREFIX + "ScanManager"; 59 60 // Result type defined in bt stack. Need to be accessed by GattService. 61 static final int SCAN_RESULT_TYPE_TRUNCATED = 1; 62 static final int SCAN_RESULT_TYPE_FULL = 2; 63 static final int SCAN_RESULT_TYPE_BOTH = 3; 64 65 // Internal messages for handling BLE scan operations. 66 private static final int MSG_START_BLE_SCAN = 0; 67 private static final int MSG_STOP_BLE_SCAN = 1; 68 private static final int MSG_FLUSH_BATCH_RESULTS = 2; 69 private static final int MSG_SCAN_TIMEOUT = 3; 70 71 // Maximum msec before scan gets downgraded to opportunistic 72 private static final int SCAN_TIMEOUT_MS = 30 * 60 * 1000; 73 74 private static final String ACTION_REFRESH_BATCHED_SCAN = 75 "com.android.bluetooth.gatt.REFRESH_BATCHED_SCAN"; 76 77 // Timeout for each controller operation. 78 private static final int OPERATION_TIME_OUT_MILLIS = 500; 79 80 private int mLastConfiguredScanSetting = Integer.MIN_VALUE; 81 // Scan parameters for batch scan. 82 private BatchScanParams mBatchScanParms; 83 84 private Integer curUsedTrackableAdvertisements; 85 private GattService mService; 86 private IBatteryStats mBatteryStats; 87 private BroadcastReceiver mBatchAlarmReceiver; 88 private boolean mBatchAlarmReceiverRegistered; 89 private ScanNative mScanNative; 90 private ClientHandler mHandler; 91 92 private Set<ScanClient> mRegularScanClients; 93 private Set<ScanClient> mBatchClients; 94 95 private CountDownLatch mLatch; 96 97 ScanManager(GattService service) { 98 mRegularScanClients = new HashSet<ScanClient>(); 99 mBatchClients = new HashSet<ScanClient>(); 100 mService = service; 101 mScanNative = new ScanNative(); 102 curUsedTrackableAdvertisements = 0; 103 } 104 105 void start() { 106 mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batterystats")); 107 HandlerThread thread = new HandlerThread("BluetoothScanManager"); 108 thread.start(); 109 mHandler = new ClientHandler(thread.getLooper()); 110 } 111 112 void cleanup() { 113 mRegularScanClients.clear(); 114 mBatchClients.clear(); 115 mScanNative.cleanup(); 116 } 117 118 /** 119 * Returns the regular scan queue. 120 */ 121 Set<ScanClient> getRegularScanQueue() { 122 return mRegularScanClients; 123 } 124 125 /** 126 * Returns batch scan queue. 127 */ 128 Set<ScanClient> getBatchScanQueue() { 129 return mBatchClients; 130 } 131 132 /** 133 * Returns a set of full batch scan clients. 134 */ 135 Set<ScanClient> getFullBatchScanQueue() { 136 // TODO: split full batch scan clients and truncated batch clients so we don't need to 137 // construct this every time. 138 Set<ScanClient> fullBatchClients = new HashSet<ScanClient>(); 139 for (ScanClient client : mBatchClients) { 140 if (client.settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_FULL) { 141 fullBatchClients.add(client); 142 } 143 } 144 return fullBatchClients; 145 } 146 147 void startScan(ScanClient client) { 148 sendMessage(MSG_START_BLE_SCAN, client); 149 } 150 151 void stopScan(ScanClient client) { 152 sendMessage(MSG_STOP_BLE_SCAN, client); 153 } 154 155 void flushBatchScanResults(ScanClient client) { 156 sendMessage(MSG_FLUSH_BATCH_RESULTS, client); 157 } 158 159 void callbackDone(int clientIf, int status) { 160 logd("callback done for clientIf - " + clientIf + " status - " + status); 161 if (status == 0) { 162 mLatch.countDown(); 163 } 164 // TODO: add a callback for scan failure. 165 } 166 167 private void sendMessage(int what, ScanClient client) { 168 Message message = new Message(); 169 message.what = what; 170 message.obj = client; 171 mHandler.sendMessage(message); 172 } 173 174 private boolean isFilteringSupported() { 175 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 176 return adapter.isOffloadedFilteringSupported(); 177 } 178 179 // Handler class that handles BLE scan operations. 180 private class ClientHandler extends Handler { 181 182 ClientHandler(Looper looper) { 183 super(looper); 184 } 185 186 @Override 187 public void handleMessage(Message msg) { 188 ScanClient client = (ScanClient) msg.obj; 189 switch (msg.what) { 190 case MSG_START_BLE_SCAN: 191 handleStartScan(client); 192 break; 193 case MSG_STOP_BLE_SCAN: 194 handleStopScan(client); 195 break; 196 case MSG_FLUSH_BATCH_RESULTS: 197 handleFlushBatchResults(client); 198 break; 199 case MSG_SCAN_TIMEOUT: 200 mScanNative.regularScanTimeout(); 201 break; 202 default: 203 // Shouldn't happen. 204 Log.e(TAG, "received an unkown message : " + msg.what); 205 } 206 } 207 208 void handleStartScan(ScanClient client) { 209 Utils.enforceAdminPermission(mService); 210 logd("handling starting scan"); 211 212 if (!isScanSupported(client)) { 213 Log.e(TAG, "Scan settings not supported"); 214 return; 215 } 216 217 if (mRegularScanClients.contains(client) || mBatchClients.contains(client)) { 218 Log.e(TAG, "Scan already started"); 219 return; 220 } 221 // Begin scan operations. 222 if (isBatchClient(client)) { 223 mBatchClients.add(client); 224 mScanNative.startBatchScan(client); 225 } else { 226 mRegularScanClients.add(client); 227 mScanNative.startRegularScan(client); 228 if (!mScanNative.isOpportunisticScanClient(client)) { 229 mScanNative.configureRegularScanParams(); 230 231 if (!mScanNative.isFirstMatchScanClient(client)) { 232 Message msg = mHandler.obtainMessage(MSG_SCAN_TIMEOUT); 233 msg.obj = client; 234 // Only one timeout message should exist at any time 235 mHandler.removeMessages(MSG_SCAN_TIMEOUT); 236 mHandler.sendMessageDelayed(msg, SCAN_TIMEOUT_MS); 237 } 238 } 239 240 // Update BatteryStats with this workload. 241 try { 242 mBatteryStats.noteBleScanStarted(client.workSource); 243 } catch (RemoteException e) { 244 /* ignore */ 245 } 246 } 247 } 248 249 void handleStopScan(ScanClient client) { 250 Utils.enforceAdminPermission(mService); 251 if (client == null) return; 252 253 if (mRegularScanClients.contains(client)) { 254 // The ScanClient passed in just holds the clientIf. We retrieve the real client, 255 // which may have workSource set. 256 client = mScanNative.getRegularScanClient(client.clientIf); 257 if (client == null) return; 258 259 mScanNative.stopRegularScan(client); 260 261 if (mScanNative.numRegularScanClients() == 0) { 262 mHandler.removeMessages(MSG_SCAN_TIMEOUT); 263 } 264 265 if (!mScanNative.isOpportunisticScanClient(client)) { 266 mScanNative.configureRegularScanParams(); 267 } 268 269 // Update BatteryStats with this workload. 270 try { 271 mBatteryStats.noteBleScanStopped(client.workSource); 272 } catch (RemoteException e) { 273 /* ignore */ 274 } 275 } else { 276 mScanNative.stopBatchScan(client); 277 } 278 if (client.appDied) { 279 logd("app died, unregister client - " + client.clientIf); 280 mService.unregisterClient(client.clientIf); 281 } 282 } 283 284 void handleFlushBatchResults(ScanClient client) { 285 Utils.enforceAdminPermission(mService); 286 if (!mBatchClients.contains(client)) { 287 return; 288 } 289 mScanNative.flushBatchResults(client.clientIf); 290 } 291 292 private boolean isBatchClient(ScanClient client) { 293 if (client == null || client.settings == null) { 294 return false; 295 } 296 ScanSettings settings = client.settings; 297 return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES && 298 settings.getReportDelayMillis() != 0; 299 } 300 301 private boolean isScanSupported(ScanClient client) { 302 if (client == null || client.settings == null) { 303 return true; 304 } 305 ScanSettings settings = client.settings; 306 if (isFilteringSupported()) { 307 return true; 308 } 309 return settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES && 310 settings.getReportDelayMillis() == 0; 311 } 312 } 313 314 /** 315 * Parameters for batch scans. 316 */ 317 class BatchScanParams { 318 int scanMode; 319 int fullScanClientIf; 320 int truncatedScanClientIf; 321 322 BatchScanParams() { 323 scanMode = -1; 324 fullScanClientIf = -1; 325 truncatedScanClientIf = -1; 326 } 327 328 @Override 329 public boolean equals(Object obj) { 330 if (this == obj) { 331 return true; 332 } 333 if (obj == null || getClass() != obj.getClass()) { 334 return false; 335 } 336 BatchScanParams other = (BatchScanParams) obj; 337 return scanMode == other.scanMode && fullScanClientIf == other.fullScanClientIf 338 && truncatedScanClientIf == other.truncatedScanClientIf; 339 340 } 341 } 342 343 public int getCurrentUsedTrackingAdvertisement() { 344 return curUsedTrackableAdvertisements; 345 } 346 347 private class ScanNative { 348 349 // Delivery mode defined in bt stack. 350 private static final int DELIVERY_MODE_IMMEDIATE = 0; 351 private static final int DELIVERY_MODE_ON_FOUND_LOST = 1; 352 private static final int DELIVERY_MODE_BATCH = 2; 353 354 private static final int ONFOUND_SIGHTINGS_AGGRESSIVE = 1; 355 private static final int ONFOUND_SIGHTINGS_STICKY = 4; 356 357 private static final int ALL_PASS_FILTER_INDEX_REGULAR_SCAN = 1; 358 private static final int ALL_PASS_FILTER_INDEX_BATCH_SCAN = 2; 359 private static final int ALL_PASS_FILTER_SELECTION = 0; 360 361 private static final int DISCARD_OLDEST_WHEN_BUFFER_FULL = 0; 362 363 /** 364 * Scan params corresponding to regular scan setting 365 */ 366 private static final int SCAN_MODE_LOW_POWER_WINDOW_MS = 500; 367 private static final int SCAN_MODE_LOW_POWER_INTERVAL_MS = 5000; 368 private static final int SCAN_MODE_BALANCED_WINDOW_MS = 2000; 369 private static final int SCAN_MODE_BALANCED_INTERVAL_MS = 5000; 370 private static final int SCAN_MODE_LOW_LATENCY_WINDOW_MS = 5000; 371 private static final int SCAN_MODE_LOW_LATENCY_INTERVAL_MS = 5000; 372 373 /** 374 * Onfound/onlost for scan settings 375 */ 376 private static final int MATCH_MODE_AGGRESSIVE_TIMEOUT_FACTOR = (1); 377 private static final int MATCH_MODE_STICKY_TIMEOUT_FACTOR = (3); 378 private static final int ONLOST_FACTOR = 2; 379 private static final int ONLOST_ONFOUND_BASE_TIMEOUT_MS = 500; 380 381 /** 382 * Scan params corresponding to batch scan setting 383 */ 384 private static final int SCAN_MODE_BATCH_LOW_POWER_WINDOW_MS = 1500; 385 private static final int SCAN_MODE_BATCH_LOW_POWER_INTERVAL_MS = 150000; 386 private static final int SCAN_MODE_BATCH_BALANCED_WINDOW_MS = 1500; 387 private static final int SCAN_MODE_BATCH_BALANCED_INTERVAL_MS = 15000; 388 private static final int SCAN_MODE_BATCH_LOW_LATENCY_WINDOW_MS = 1500; 389 private static final int SCAN_MODE_BATCH_LOW_LATENCY_INTERVAL_MS = 5000; 390 391 // The logic is AND for each filter field. 392 private static final int LIST_LOGIC_TYPE = 0x1111111; 393 private static final int FILTER_LOGIC_TYPE = 1; 394 // Filter indices that are available to user. It's sad we need to maintain filter index. 395 private final Deque<Integer> mFilterIndexStack; 396 // Map of clientIf and Filter indices used by client. 397 private final Map<Integer, Deque<Integer>> mClientFilterIndexMap; 398 // Keep track of the clients that uses ALL_PASS filters. 399 private final Set<Integer> mAllPassRegularClients = new HashSet<>(); 400 private final Set<Integer> mAllPassBatchClients = new HashSet<>(); 401 402 private AlarmManager mAlarmManager; 403 private PendingIntent mBatchScanIntervalIntent; 404 405 ScanNative() { 406 mFilterIndexStack = new ArrayDeque<Integer>(); 407 mClientFilterIndexMap = new HashMap<Integer, Deque<Integer>>(); 408 409 mAlarmManager = (AlarmManager) mService.getSystemService(Context.ALARM_SERVICE); 410 Intent batchIntent = new Intent(ACTION_REFRESH_BATCHED_SCAN, null); 411 mBatchScanIntervalIntent = PendingIntent.getBroadcast(mService, 0, batchIntent, 0); 412 IntentFilter filter = new IntentFilter(); 413 filter.addAction(ACTION_REFRESH_BATCHED_SCAN); 414 mBatchAlarmReceiver = new BroadcastReceiver() { 415 @Override 416 public void onReceive(Context context, Intent intent) { 417 Log.d(TAG, "awakened up at time " + SystemClock.elapsedRealtime()); 418 String action = intent.getAction(); 419 420 if (action.equals(ACTION_REFRESH_BATCHED_SCAN)) { 421 if (mBatchClients.isEmpty()) { 422 return; 423 } 424 // Note this actually flushes all pending batch data. 425 flushBatchScanResults(mBatchClients.iterator().next()); 426 } 427 } 428 }; 429 mService.registerReceiver(mBatchAlarmReceiver, filter); 430 mBatchAlarmReceiverRegistered = true; 431 } 432 433 private void resetCountDownLatch() { 434 mLatch = new CountDownLatch(1); 435 } 436 437 // Returns true if mLatch reaches 0, false if timeout or interrupted. 438 private boolean waitForCallback() { 439 try { 440 return mLatch.await(OPERATION_TIME_OUT_MILLIS, TimeUnit.MILLISECONDS); 441 } catch (InterruptedException e) { 442 return false; 443 } 444 } 445 446 void configureRegularScanParams() { 447 logd("configureRegularScanParams() - queue=" + mRegularScanClients.size()); 448 int curScanSetting = Integer.MIN_VALUE; 449 ScanClient client = getAggressiveClient(mRegularScanClients); 450 if (client != null) { 451 curScanSetting = client.settings.getScanMode(); 452 } 453 454 logd("configureRegularScanParams() - ScanSetting Scan mode=" + curScanSetting + 455 " mLastConfiguredScanSetting=" + mLastConfiguredScanSetting); 456 457 if (curScanSetting != Integer.MIN_VALUE && 458 curScanSetting != ScanSettings.SCAN_MODE_OPPORTUNISTIC) { 459 if (curScanSetting != mLastConfiguredScanSetting) { 460 int scanWindow = getScanWindowMillis(client.settings); 461 int scanInterval = getScanIntervalMillis(client.settings); 462 // convert scanWindow and scanInterval from ms to LE scan units(0.625ms) 463 scanWindow = Utils.millsToUnit(scanWindow); 464 scanInterval = Utils.millsToUnit(scanInterval); 465 gattClientScanNative(false); 466 logd("configureRegularScanParams - scanInterval = " + scanInterval + 467 "configureRegularScanParams - scanWindow = " + scanWindow); 468 gattSetScanParametersNative(client.clientIf, scanInterval, scanWindow); 469 gattClientScanNative(true); 470 mLastConfiguredScanSetting = curScanSetting; 471 } 472 } else { 473 mLastConfiguredScanSetting = curScanSetting; 474 logd("configureRegularScanParams() - queue emtpy, scan stopped"); 475 } 476 } 477 478 ScanClient getAggressiveClient(Set<ScanClient> cList) { 479 ScanClient result = null; 480 int curScanSetting = Integer.MIN_VALUE; 481 for (ScanClient client : cList) { 482 // ScanClient scan settings are assumed to be monotonically increasing in value for 483 // more power hungry(higher duty cycle) operation. 484 if (client.settings.getScanMode() > curScanSetting) { 485 result = client; 486 curScanSetting = client.settings.getScanMode(); 487 } 488 } 489 return result; 490 } 491 492 void startRegularScan(ScanClient client) { 493 if (isFilteringSupported() && mFilterIndexStack.isEmpty() 494 && mClientFilterIndexMap.isEmpty()) { 495 initFilterIndexStack(); 496 } 497 if (isFilteringSupported()) { 498 configureScanFilters(client); 499 } 500 // Start scan native only for the first client. 501 if (numRegularScanClients() == 1) { 502 gattClientScanNative(true); 503 } 504 } 505 506 private int numRegularScanClients() { 507 int num = 0; 508 for (ScanClient client: mRegularScanClients) { 509 if (client.settings.getScanMode() != ScanSettings.SCAN_MODE_OPPORTUNISTIC) { 510 num++; 511 } 512 } 513 return num; 514 } 515 516 void startBatchScan(ScanClient client) { 517 if (mFilterIndexStack.isEmpty() && isFilteringSupported()) { 518 initFilterIndexStack(); 519 } 520 configureScanFilters(client); 521 if (!isOpportunisticScanClient(client)) { 522 // Reset batch scan. May need to stop the existing batch scan and update scan params. 523 resetBatchScan(client); 524 } 525 } 526 527 private boolean isOpportunisticScanClient(ScanClient client) { 528 return client.settings.getScanMode() == ScanSettings.SCAN_MODE_OPPORTUNISTIC; 529 } 530 531 private boolean isFirstMatchScanClient(ScanClient client) { 532 return (client.settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0; 533 } 534 535 private void resetBatchScan(ScanClient client) { 536 int clientIf = client.clientIf; 537 BatchScanParams batchScanParams = getBatchScanParams(); 538 // Stop batch if batch scan params changed and previous params is not null. 539 if (mBatchScanParms != null && (!mBatchScanParms.equals(batchScanParams))) { 540 logd("stopping BLe Batch"); 541 resetCountDownLatch(); 542 gattClientStopBatchScanNative(clientIf); 543 waitForCallback(); 544 // Clear pending results as it's illegal to config storage if there are still 545 // pending results. 546 flushBatchResults(clientIf); 547 } 548 // Start batch if batchScanParams changed and current params is not null. 549 if (batchScanParams != null && (!batchScanParams.equals(mBatchScanParms))) { 550 int notifyThreshold = 95; 551 logd("Starting BLE batch scan"); 552 int resultType = getResultType(batchScanParams); 553 int fullScanPercent = getFullScanStoragePercent(resultType); 554 resetCountDownLatch(); 555 logd("configuring batch scan storage, appIf " + client.clientIf); 556 gattClientConfigBatchScanStorageNative(client.clientIf, fullScanPercent, 557 100 - fullScanPercent, notifyThreshold); 558 waitForCallback(); 559 resetCountDownLatch(); 560 int scanInterval = 561 Utils.millsToUnit(getBatchScanIntervalMillis(batchScanParams.scanMode)); 562 int scanWindow = 563 Utils.millsToUnit(getBatchScanWindowMillis(batchScanParams.scanMode)); 564 gattClientStartBatchScanNative(clientIf, resultType, scanInterval, 565 scanWindow, 0, DISCARD_OLDEST_WHEN_BUFFER_FULL); 566 waitForCallback(); 567 } 568 mBatchScanParms = batchScanParams; 569 setBatchAlarm(); 570 } 571 572 private int getFullScanStoragePercent(int resultType) { 573 switch (resultType) { 574 case SCAN_RESULT_TYPE_FULL: 575 return 100; 576 case SCAN_RESULT_TYPE_TRUNCATED: 577 return 0; 578 case SCAN_RESULT_TYPE_BOTH: 579 return 50; 580 default: 581 return 50; 582 } 583 } 584 585 private BatchScanParams getBatchScanParams() { 586 if (mBatchClients.isEmpty()) { 587 return null; 588 } 589 BatchScanParams params = new BatchScanParams(); 590 // TODO: split full batch scan results and truncated batch scan results to different 591 // collections. 592 for (ScanClient client : mBatchClients) { 593 params.scanMode = Math.max(params.scanMode, client.settings.getScanMode()); 594 if (client.settings.getScanResultType() == ScanSettings.SCAN_RESULT_TYPE_FULL) { 595 params.fullScanClientIf = client.clientIf; 596 } else { 597 params.truncatedScanClientIf = client.clientIf; 598 } 599 } 600 return params; 601 } 602 603 private int getBatchScanWindowMillis(int scanMode) { 604 switch (scanMode) { 605 case ScanSettings.SCAN_MODE_LOW_LATENCY: 606 return SCAN_MODE_BATCH_LOW_LATENCY_WINDOW_MS; 607 case ScanSettings.SCAN_MODE_BALANCED: 608 return SCAN_MODE_BATCH_BALANCED_WINDOW_MS; 609 case ScanSettings.SCAN_MODE_LOW_POWER: 610 return SCAN_MODE_BATCH_LOW_POWER_WINDOW_MS; 611 default: 612 return SCAN_MODE_BATCH_LOW_POWER_WINDOW_MS; 613 } 614 } 615 616 private int getBatchScanIntervalMillis(int scanMode) { 617 switch (scanMode) { 618 case ScanSettings.SCAN_MODE_LOW_LATENCY: 619 return SCAN_MODE_BATCH_LOW_LATENCY_INTERVAL_MS; 620 case ScanSettings.SCAN_MODE_BALANCED: 621 return SCAN_MODE_BATCH_BALANCED_INTERVAL_MS; 622 case ScanSettings.SCAN_MODE_LOW_POWER: 623 return SCAN_MODE_BATCH_LOW_POWER_INTERVAL_MS; 624 default: 625 return SCAN_MODE_BATCH_LOW_POWER_INTERVAL_MS; 626 } 627 } 628 629 // Set the batch alarm to be triggered within a short window after batch interval. This 630 // allows system to optimize wake up time while still allows a degree of precise control. 631 private void setBatchAlarm() { 632 // Cancel any pending alarm just in case. 633 mAlarmManager.cancel(mBatchScanIntervalIntent); 634 if (mBatchClients.isEmpty()) { 635 return; 636 } 637 long batchTriggerIntervalMillis = getBatchTriggerIntervalMillis(); 638 // Allows the alarm to be triggered within 639 // [batchTriggerIntervalMillis, 1.1 * batchTriggerIntervalMillis] 640 long windowLengthMillis = batchTriggerIntervalMillis / 10; 641 long windowStartMillis = SystemClock.elapsedRealtime() + batchTriggerIntervalMillis; 642 mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, 643 windowStartMillis, windowLengthMillis, 644 mBatchScanIntervalIntent); 645 } 646 647 void stopRegularScan(ScanClient client) { 648 // Remove scan filters and recycle filter indices. 649 if (client == null) return; 650 int deliveryMode = getDeliveryMode(client); 651 if (deliveryMode == DELIVERY_MODE_ON_FOUND_LOST) { 652 for (ScanFilter filter : client.filters) { 653 int entriesToFree = getNumOfTrackingAdvertisements(client.settings); 654 if (!manageAllocationOfTrackingAdvertisement(entriesToFree, false)) { 655 Log.e(TAG, "Error freeing for onfound/onlost filter resources " 656 + entriesToFree); 657 try { 658 mService.onScanManagerErrorCallback(client.clientIf, 659 ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 660 } catch (RemoteException e) { 661 Log.e(TAG, "failed on onScanManagerCallback at freeing", e); 662 } 663 } 664 } 665 } 666 mRegularScanClients.remove(client); 667 if (numRegularScanClients() == 0) { 668 logd("stop scan"); 669 gattClientScanNative(false); 670 } 671 removeScanFilters(client.clientIf); 672 } 673 674 void regularScanTimeout() { 675 for (ScanClient client : mRegularScanClients) { 676 if (!isOpportunisticScanClient(client) && !isFirstMatchScanClient(client)) { 677 logd("clientIf set to scan opportunisticly: " + client.clientIf); 678 setOpportunisticScanClient(client); 679 client.stats.setScanTimeout(); 680 } 681 } 682 683 // The scan should continue for background scans 684 configureRegularScanParams(); 685 if (numRegularScanClients() == 0) { 686 logd("stop scan"); 687 gattClientScanNative(false); 688 } 689 } 690 691 void setOpportunisticScanClient(ScanClient client) { 692 // TODO: Add constructor to ScanSettings.Builder 693 // that can copy values from an existing ScanSettings object 694 ScanSettings.Builder builder = new ScanSettings.Builder(); 695 ScanSettings settings = client.settings; 696 builder.setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC); 697 builder.setCallbackType(settings.getCallbackType()); 698 builder.setScanResultType(settings.getScanResultType()); 699 builder.setReportDelay(settings.getReportDelayMillis()); 700 builder.setNumOfMatches(settings.getNumOfMatches()); 701 client.settings = builder.build(); 702 } 703 704 // Find the regular scan client information. 705 ScanClient getRegularScanClient(int clientIf) { 706 for (ScanClient client : mRegularScanClients) { 707 if (client.clientIf == clientIf) return client; 708 } 709 return null; 710 } 711 712 void stopBatchScan(ScanClient client) { 713 mBatchClients.remove(client); 714 removeScanFilters(client.clientIf); 715 if (!isOpportunisticScanClient(client)) { 716 resetBatchScan(client); 717 } 718 } 719 720 void flushBatchResults(int clientIf) { 721 logd("flushPendingBatchResults - clientIf = " + clientIf); 722 if (mBatchScanParms.fullScanClientIf != -1) { 723 resetCountDownLatch(); 724 gattClientReadScanReportsNative(mBatchScanParms.fullScanClientIf, 725 SCAN_RESULT_TYPE_FULL); 726 waitForCallback(); 727 } 728 if (mBatchScanParms.truncatedScanClientIf != -1) { 729 resetCountDownLatch(); 730 gattClientReadScanReportsNative(mBatchScanParms.truncatedScanClientIf, 731 SCAN_RESULT_TYPE_TRUNCATED); 732 waitForCallback(); 733 } 734 setBatchAlarm(); 735 } 736 737 void cleanup() { 738 mAlarmManager.cancel(mBatchScanIntervalIntent); 739 // Protect against multiple calls of cleanup. 740 if (mBatchAlarmReceiverRegistered) { 741 mService.unregisterReceiver(mBatchAlarmReceiver); 742 } 743 mBatchAlarmReceiverRegistered = false; 744 } 745 746 private long getBatchTriggerIntervalMillis() { 747 long intervalMillis = Long.MAX_VALUE; 748 for (ScanClient client : mBatchClients) { 749 if (client.settings != null && client.settings.getReportDelayMillis() > 0) { 750 intervalMillis = Math.min(intervalMillis, 751 client.settings.getReportDelayMillis()); 752 } 753 } 754 return intervalMillis; 755 } 756 757 // Add scan filters. The logic is: 758 // If no offload filter can/needs to be set, set ALL_PASS filter. 759 // Otherwise offload all filters to hardware and enable all filters. 760 private void configureScanFilters(ScanClient client) { 761 int clientIf = client.clientIf; 762 int deliveryMode = getDeliveryMode(client); 763 int trackEntries = 0; 764 if (!shouldAddAllPassFilterToController(client, deliveryMode)) { 765 return; 766 } 767 768 resetCountDownLatch(); 769 gattClientScanFilterEnableNative(clientIf, true); 770 waitForCallback(); 771 772 if (shouldUseAllPassFilter(client)) { 773 int filterIndex = (deliveryMode == DELIVERY_MODE_BATCH) ? 774 ALL_PASS_FILTER_INDEX_BATCH_SCAN : ALL_PASS_FILTER_INDEX_REGULAR_SCAN; 775 resetCountDownLatch(); 776 // Don't allow Onfound/onlost with all pass 777 configureFilterParamter(clientIf, client, ALL_PASS_FILTER_SELECTION, 778 filterIndex, 0); 779 waitForCallback(); 780 } else { 781 Deque<Integer> clientFilterIndices = new ArrayDeque<Integer>(); 782 for (ScanFilter filter : client.filters) { 783 ScanFilterQueue queue = new ScanFilterQueue(); 784 queue.addScanFilter(filter); 785 int featureSelection = queue.getFeatureSelection(); 786 int filterIndex = mFilterIndexStack.pop(); 787 while (!queue.isEmpty()) { 788 resetCountDownLatch(); 789 addFilterToController(clientIf, queue.pop(), filterIndex); 790 waitForCallback(); 791 } 792 resetCountDownLatch(); 793 if (deliveryMode == DELIVERY_MODE_ON_FOUND_LOST) { 794 trackEntries = getNumOfTrackingAdvertisements(client.settings); 795 if (!manageAllocationOfTrackingAdvertisement(trackEntries, true)) { 796 Log.e(TAG, "No hardware resources for onfound/onlost filter " + 797 trackEntries); 798 try { 799 mService.onScanManagerErrorCallback(clientIf, 800 ScanCallback.SCAN_FAILED_INTERNAL_ERROR); 801 } catch (RemoteException e) { 802 Log.e(TAG, "failed on onScanManagerCallback", e); 803 } 804 } 805 } 806 configureFilterParamter(clientIf, client, featureSelection, filterIndex, 807 trackEntries); 808 waitForCallback(); 809 clientFilterIndices.add(filterIndex); 810 } 811 mClientFilterIndexMap.put(clientIf, clientFilterIndices); 812 } 813 } 814 815 // Check whether the filter should be added to controller. 816 // Note only on ALL_PASS filter should be added. 817 private boolean shouldAddAllPassFilterToController(ScanClient client, int deliveryMode) { 818 // Not an ALL_PASS client, need to add filter. 819 if (!shouldUseAllPassFilter(client)) { 820 return true; 821 } 822 823 if (deliveryMode == DELIVERY_MODE_BATCH) { 824 mAllPassBatchClients.add(client.clientIf); 825 return mAllPassBatchClients.size() == 1; 826 } else { 827 mAllPassRegularClients.add(client.clientIf); 828 return mAllPassRegularClients.size() == 1; 829 } 830 } 831 832 private void removeScanFilters(int clientIf) { 833 Deque<Integer> filterIndices = mClientFilterIndexMap.remove(clientIf); 834 if (filterIndices != null) { 835 mFilterIndexStack.addAll(filterIndices); 836 for (Integer filterIndex : filterIndices) { 837 resetCountDownLatch(); 838 gattClientScanFilterParamDeleteNative(clientIf, filterIndex); 839 waitForCallback(); 840 } 841 } 842 // Remove if ALL_PASS filters are used. 843 removeFilterIfExisits(mAllPassRegularClients, clientIf, 844 ALL_PASS_FILTER_INDEX_REGULAR_SCAN); 845 removeFilterIfExisits(mAllPassBatchClients, clientIf, 846 ALL_PASS_FILTER_INDEX_BATCH_SCAN); 847 } 848 849 private void removeFilterIfExisits(Set<Integer> clients, int clientIf, int filterIndex) { 850 if (!clients.contains(clientIf)) { 851 return; 852 } 853 clients.remove(clientIf); 854 // Remove ALL_PASS filter iff no app is using it. 855 if (clients.isEmpty()) { 856 resetCountDownLatch(); 857 gattClientScanFilterParamDeleteNative(clientIf, filterIndex); 858 waitForCallback(); 859 } 860 } 861 862 private ScanClient getBatchScanClient(int clientIf) { 863 for (ScanClient client : mBatchClients) { 864 if (client.clientIf == clientIf) { 865 return client; 866 } 867 } 868 return null; 869 } 870 871 /** 872 * Return batch scan result type value defined in bt stack. 873 */ 874 private int getResultType(BatchScanParams params) { 875 if (params.fullScanClientIf != -1 && params.truncatedScanClientIf != -1) { 876 return SCAN_RESULT_TYPE_BOTH; 877 } 878 if (params.truncatedScanClientIf != -1) { 879 return SCAN_RESULT_TYPE_TRUNCATED; 880 } 881 if (params.fullScanClientIf != -1) { 882 return SCAN_RESULT_TYPE_FULL; 883 } 884 return -1; 885 } 886 887 // Check if ALL_PASS filter should be used for the client. 888 private boolean shouldUseAllPassFilter(ScanClient client) { 889 if (client == null) { 890 return true; 891 } 892 if (client.filters == null || client.filters.isEmpty()) { 893 return true; 894 } 895 return client.filters.size() > mFilterIndexStack.size(); 896 } 897 898 private void addFilterToController(int clientIf, ScanFilterQueue.Entry entry, 899 int filterIndex) { 900 logd("addFilterToController: " + entry.type); 901 switch (entry.type) { 902 case ScanFilterQueue.TYPE_DEVICE_ADDRESS: 903 logd("add address " + entry.address); 904 gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 905 0, 906 "", entry.address, (byte) entry.addr_type, new byte[0], new byte[0]); 907 break; 908 909 case ScanFilterQueue.TYPE_SERVICE_DATA: 910 gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 911 0, 912 "", "", (byte) 0, entry.data, entry.data_mask); 913 break; 914 915 case ScanFilterQueue.TYPE_SERVICE_UUID: 916 case ScanFilterQueue.TYPE_SOLICIT_UUID: 917 gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 918 entry.uuid.getLeastSignificantBits(), 919 entry.uuid.getMostSignificantBits(), 920 entry.uuid_mask.getLeastSignificantBits(), 921 entry.uuid_mask.getMostSignificantBits(), 922 "", "", (byte) 0, new byte[0], new byte[0]); 923 break; 924 925 case ScanFilterQueue.TYPE_LOCAL_NAME: 926 logd("adding filters: " + entry.name); 927 gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, 0, 0, 0, 0, 0, 928 0, 929 entry.name, "", (byte) 0, new byte[0], new byte[0]); 930 break; 931 932 case ScanFilterQueue.TYPE_MANUFACTURER_DATA: 933 int len = entry.data.length; 934 if (entry.data_mask.length != len) 935 return; 936 gattClientScanFilterAddNative(clientIf, entry.type, filterIndex, entry.company, 937 entry.company_mask, 0, 0, 0, 0, "", "", (byte) 0, 938 entry.data, entry.data_mask); 939 break; 940 } 941 } 942 943 private void initFilterIndexStack() { 944 int maxFiltersSupported = 945 AdapterService.getAdapterService().getNumOfOffloadedScanFilterSupported(); 946 // Start from index 3 as: 947 // index 0 is reserved for ALL_PASS filter in Settings app. 948 // index 1 is reserved for ALL_PASS filter for regular scan apps. 949 // index 2 is reserved for ALL_PASS filter for batch scan apps. 950 for (int i = 3; i < maxFiltersSupported; ++i) { 951 mFilterIndexStack.add(i); 952 } 953 } 954 955 // Configure filter parameters. 956 private void configureFilterParamter(int clientIf, ScanClient client, int featureSelection, 957 int filterIndex, int numOfTrackingEntries) { 958 int deliveryMode = getDeliveryMode(client); 959 int rssiThreshold = Byte.MIN_VALUE; 960 ScanSettings settings = client.settings; 961 int onFoundTimeout = getOnFoundOnLostTimeoutMillis(settings, true); 962 int onLostTimeout = getOnFoundOnLostTimeoutMillis(settings, false); 963 int onFoundCount = getOnFoundOnLostSightings(settings); 964 onLostTimeout = 10000; 965 logd("configureFilterParamter " + onFoundTimeout + " " + onLostTimeout + " " 966 + onFoundCount + " " + numOfTrackingEntries); 967 FilterParams FiltValue = new FilterParams(clientIf, filterIndex, featureSelection, 968 LIST_LOGIC_TYPE, FILTER_LOGIC_TYPE, rssiThreshold, rssiThreshold, deliveryMode, 969 onFoundTimeout, onLostTimeout, onFoundCount, numOfTrackingEntries); 970 gattClientScanFilterParamAddNative(FiltValue); 971 } 972 973 // Get delivery mode based on scan settings. 974 private int getDeliveryMode(ScanClient client) { 975 if (client == null) { 976 return DELIVERY_MODE_IMMEDIATE; 977 } 978 ScanSettings settings = client.settings; 979 if (settings == null) { 980 return DELIVERY_MODE_IMMEDIATE; 981 } 982 if ((settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0 983 || (settings.getCallbackType() & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) { 984 return DELIVERY_MODE_ON_FOUND_LOST; 985 } 986 return settings.getReportDelayMillis() == 0 ? DELIVERY_MODE_IMMEDIATE 987 : DELIVERY_MODE_BATCH; 988 } 989 990 private int getScanWindowMillis(ScanSettings settings) { 991 if (settings == null) { 992 return SCAN_MODE_LOW_POWER_WINDOW_MS; 993 } 994 switch (settings.getScanMode()) { 995 case ScanSettings.SCAN_MODE_LOW_LATENCY: 996 return SCAN_MODE_LOW_LATENCY_WINDOW_MS; 997 case ScanSettings.SCAN_MODE_BALANCED: 998 return SCAN_MODE_BALANCED_WINDOW_MS; 999 case ScanSettings.SCAN_MODE_LOW_POWER: 1000 return SCAN_MODE_LOW_POWER_WINDOW_MS; 1001 default: 1002 return SCAN_MODE_LOW_POWER_WINDOW_MS; 1003 } 1004 } 1005 1006 private int getScanIntervalMillis(ScanSettings settings) { 1007 if (settings == null) 1008 return SCAN_MODE_LOW_POWER_INTERVAL_MS; 1009 switch (settings.getScanMode()) { 1010 case ScanSettings.SCAN_MODE_LOW_LATENCY: 1011 return SCAN_MODE_LOW_LATENCY_INTERVAL_MS; 1012 case ScanSettings.SCAN_MODE_BALANCED: 1013 return SCAN_MODE_BALANCED_INTERVAL_MS; 1014 case ScanSettings.SCAN_MODE_LOW_POWER: 1015 return SCAN_MODE_LOW_POWER_INTERVAL_MS; 1016 default: 1017 return SCAN_MODE_LOW_POWER_INTERVAL_MS; 1018 } 1019 } 1020 1021 private int getOnFoundOnLostTimeoutMillis(ScanSettings settings, boolean onFound) { 1022 int factor; 1023 int timeout = ONLOST_ONFOUND_BASE_TIMEOUT_MS; 1024 1025 if (settings.getMatchMode() == ScanSettings.MATCH_MODE_AGGRESSIVE) { 1026 factor = MATCH_MODE_AGGRESSIVE_TIMEOUT_FACTOR; 1027 } else { 1028 factor = MATCH_MODE_STICKY_TIMEOUT_FACTOR; 1029 } 1030 if (!onFound) factor = factor * ONLOST_FACTOR; 1031 return (timeout*factor); 1032 } 1033 1034 private int getOnFoundOnLostSightings(ScanSettings settings) { 1035 if (settings == null) 1036 return ONFOUND_SIGHTINGS_AGGRESSIVE; 1037 if (settings.getMatchMode() == ScanSettings.MATCH_MODE_AGGRESSIVE) { 1038 return ONFOUND_SIGHTINGS_AGGRESSIVE; 1039 } else { 1040 return ONFOUND_SIGHTINGS_STICKY; 1041 } 1042 } 1043 1044 private int getNumOfTrackingAdvertisements(ScanSettings settings) { 1045 if (settings == null) 1046 return 0; 1047 int val=0; 1048 int maxTotalTrackableAdvertisements = 1049 AdapterService.getAdapterService().getTotalNumOfTrackableAdvertisements(); 1050 // controller based onfound onlost resources are scarce commodity; the 1051 // assignment of filters to num of beacons to track is configurable based 1052 // on hw capabilities. Apps give an intent and allocation of onfound 1053 // resources or failure there of is done based on availibility - FCFS model 1054 switch (settings.getNumOfMatches()) { 1055 case ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT: 1056 val = 1; 1057 break; 1058 case ScanSettings.MATCH_NUM_FEW_ADVERTISEMENT: 1059 val = 2; 1060 break; 1061 case ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT: 1062 val = maxTotalTrackableAdvertisements/2; 1063 break; 1064 default: 1065 val = 1; 1066 logd("Invalid setting for getNumOfMatches() " + settings.getNumOfMatches()); 1067 } 1068 return val; 1069 } 1070 1071 private boolean manageAllocationOfTrackingAdvertisement(int numOfTrackableAdvertisement, 1072 boolean allocate) { 1073 int maxTotalTrackableAdvertisements = 1074 AdapterService.getAdapterService().getTotalNumOfTrackableAdvertisements(); 1075 synchronized(curUsedTrackableAdvertisements) { 1076 int availableEntries = maxTotalTrackableAdvertisements 1077 - curUsedTrackableAdvertisements; 1078 if (allocate) { 1079 if (availableEntries >= numOfTrackableAdvertisement) { 1080 curUsedTrackableAdvertisements += numOfTrackableAdvertisement; 1081 return true; 1082 } else { 1083 return false; 1084 } 1085 } else { 1086 if (numOfTrackableAdvertisement > curUsedTrackableAdvertisements) { 1087 return false; 1088 } else { 1089 curUsedTrackableAdvertisements -= numOfTrackableAdvertisement; 1090 return true; 1091 } 1092 } 1093 } 1094 } 1095 1096 1097 /************************** Regular scan related native methods **************************/ 1098 private native void gattClientScanNative(boolean start); 1099 1100 private native void gattSetScanParametersNative(int client_if, int scan_interval, 1101 int scan_window); 1102 1103 /************************** Filter related native methods ********************************/ 1104 private native void gattClientScanFilterAddNative(int client_if, 1105 int filter_type, int filter_index, int company_id, 1106 int company_id_mask, long uuid_lsb, long uuid_msb, 1107 long uuid_mask_lsb, long uuid_mask_msb, String name, 1108 String address, byte addr_type, byte[] data, byte[] mask); 1109 1110 private native void gattClientScanFilterDeleteNative(int client_if, 1111 int filter_type, int filter_index, int company_id, 1112 int company_id_mask, long uuid_lsb, long uuid_msb, 1113 long uuid_mask_lsb, long uuid_mask_msb, String name, 1114 String address, byte addr_type, byte[] data, byte[] mask); 1115 1116 private native void gattClientScanFilterParamAddNative(FilterParams FiltValue); 1117 1118 // Note this effectively remove scan filters for ALL clients. 1119 private native void gattClientScanFilterParamClearAllNative( 1120 int client_if); 1121 1122 private native void gattClientScanFilterParamDeleteNative( 1123 int client_if, int filt_index); 1124 1125 private native void gattClientScanFilterClearNative(int client_if, 1126 int filter_index); 1127 1128 private native void gattClientScanFilterEnableNative(int client_if, 1129 boolean enable); 1130 1131 /************************** Batch related native methods *********************************/ 1132 private native void gattClientConfigBatchScanStorageNative(int client_if, 1133 int max_full_reports_percent, int max_truncated_reports_percent, 1134 int notify_threshold_percent); 1135 1136 private native void gattClientStartBatchScanNative(int client_if, int scan_mode, 1137 int scan_interval_unit, int scan_window_unit, int address_type, int discard_rule); 1138 1139 private native void gattClientStopBatchScanNative(int client_if); 1140 1141 private native void gattClientReadScanReportsNative(int client_if, int scan_type); 1142 } 1143 1144 private void logd(String s) { 1145 if (DBG) Log.d(TAG, s); 1146 } 1147 } 1148