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