1 /* 2 * Copyright (C) 2016 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 package com.android.bluetooth.gatt; 17 18 import android.bluetooth.BluetoothProtoEnums; 19 import android.bluetooth.le.ScanFilter; 20 import android.bluetooth.le.ScanSettings; 21 import android.os.BatteryStatsManager; 22 import android.os.Binder; 23 import android.os.SystemClock; 24 import android.os.WorkSource; 25 26 import com.android.bluetooth.BluetoothMetricsProto; 27 import com.android.bluetooth.BluetoothStatsLog; 28 import com.android.bluetooth.btservice.AdapterService; 29 import com.android.bluetooth.btservice.MetricsLogger; 30 import com.android.bluetooth.util.WorkSourceUtil; 31 import com.android.internal.annotations.GuardedBy; 32 33 import java.text.DateFormat; 34 import java.text.SimpleDateFormat; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.Date; 38 import java.util.HashMap; 39 import java.util.Iterator; 40 import java.util.List; 41 import java.util.Objects; 42 43 /** 44 * ScanStats class helps keep track of information about scans 45 * on a per application basis. 46 * @hide 47 */ 48 /*package*/ class AppScanStats { 49 private static final String TAG = AppScanStats.class.getSimpleName(); 50 51 static final DateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss"); 52 53 // Weight is the duty cycle of the scan mode 54 static final int OPPORTUNISTIC_WEIGHT = 0; 55 static final int SCREEN_OFF_LOW_POWER_WEIGHT = 5; 56 static final int LOW_POWER_WEIGHT = 10; 57 static final int AMBIENT_DISCOVERY_WEIGHT = 25; 58 static final int BALANCED_WEIGHT = 25; 59 static final int LOW_LATENCY_WEIGHT = 100; 60 61 static final int LARGE_SCAN_TIME_GAP_MS = 24000; 62 63 // ContextMap here is needed to grab Apps and Connections 64 ContextMap mContextMap; 65 66 // GattService is needed to add scan event protos to be dumped later 67 final GattService mGattService; 68 69 // Battery stats is used to keep track of scans and result stats 70 BatteryStatsManager mBatteryStatsManager; 71 72 private final AdapterService mAdapterService; 73 74 private static Object sLock = new Object(); 75 @GuardedBy("sLock") 76 static long sRadioStartTime = 0; 77 static int sRadioScanMode; 78 static boolean sIsRadioStarted = false; 79 static boolean sIsScreenOn = false; 80 81 class LastScan { 82 public long duration; 83 public long suspendDuration; 84 public long suspendStartTime; 85 public boolean isSuspended; 86 public long timestamp; 87 public boolean isOpportunisticScan; 88 public boolean isTimeout; 89 public boolean isDowngraded; 90 public boolean isBackgroundScan; 91 public boolean isFilterScan; 92 public boolean isCallbackScan; 93 public boolean isBatchScan; 94 public boolean isAutoBatchScan; 95 public int results; 96 public int scannerId; 97 public int scanMode; 98 public int scanCallbackType; 99 public String filterString; 100 LastScan(long timestamp, boolean isFilterScan, boolean isCallbackScan, int scannerId, int scanMode, int scanCallbackType)101 LastScan(long timestamp, boolean isFilterScan, boolean isCallbackScan, int scannerId, 102 int scanMode, int scanCallbackType) { 103 this.duration = 0; 104 this.timestamp = timestamp; 105 this.isOpportunisticScan = false; 106 this.isTimeout = false; 107 this.isDowngraded = false; 108 this.isBackgroundScan = false; 109 this.isFilterScan = isFilterScan; 110 this.isCallbackScan = isCallbackScan; 111 this.isBatchScan = false; 112 this.isAutoBatchScan = false; 113 this.scanMode = scanMode; 114 this.scanCallbackType = scanCallbackType; 115 this.results = 0; 116 this.scannerId = scannerId; 117 this.suspendDuration = 0; 118 this.suspendStartTime = 0; 119 this.isSuspended = false; 120 this.filterString = ""; 121 } 122 } 123 public String appName; 124 public WorkSource mWorkSource; // Used for BatteryStatsManager 125 public final WorkSourceUtil mWorkSourceUtil; // Used for BluetoothStatsLog 126 private int mScansStarted = 0; 127 private int mScansStopped = 0; 128 public boolean isRegistered = false; 129 private long mScanStartTime = 0; 130 private long mTotalActiveTime = 0; 131 private long mTotalSuspendTime = 0; 132 private long mTotalScanTime = 0; 133 private long mOppScanTime = 0; 134 private long mLowPowerScanTime = 0; 135 private long mBalancedScanTime = 0; 136 private long mLowLantencyScanTime = 0; 137 private long mAmbientDiscoveryScanTime = 0; 138 private int mOppScan = 0; 139 private int mLowPowerScan = 0; 140 private int mBalancedScan = 0; 141 private int mLowLantencyScan = 0; 142 private int mAmbientDiscoveryScan = 0; 143 private List<LastScan> mLastScans = new ArrayList<LastScan>(); 144 private HashMap<Integer, LastScan> mOngoingScans = new HashMap<Integer, LastScan>(); 145 public long startTime = 0; 146 public long stopTime = 0; 147 public int results = 0; 148 AppScanStats(String name, WorkSource source, ContextMap map, GattService service)149 AppScanStats(String name, WorkSource source, ContextMap map, GattService service) { 150 appName = name; 151 mContextMap = map; 152 mGattService = service; 153 mBatteryStatsManager = service.getSystemService(BatteryStatsManager.class); 154 155 if (source == null) { 156 // Bill the caller if the work source isn't passed through 157 source = new WorkSource(Binder.getCallingUid(), appName); 158 } 159 mWorkSource = source; 160 mWorkSourceUtil = new WorkSourceUtil(source); 161 mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService()); 162 } 163 addResult(int scannerId)164 synchronized void addResult(int scannerId) { 165 LastScan scan = getScanFromScannerId(scannerId); 166 if (scan != null) { 167 scan.results++; 168 169 // Only update battery stats after receiving 100 new results in order 170 // to lower the cost of the binder transaction 171 if (scan.results % 100 == 0) { 172 mBatteryStatsManager.reportBleScanResults(mWorkSource, 100); 173 BluetoothStatsLog.write(BluetoothStatsLog.BLE_SCAN_RESULT_RECEIVED, 174 mWorkSourceUtil.getUids(), mWorkSourceUtil.getTags(), 100); 175 } 176 } 177 178 results++; 179 } 180 isScanning()181 synchronized boolean isScanning() { 182 return !mOngoingScans.isEmpty(); 183 } 184 getScanFromScannerId(int scannerId)185 synchronized LastScan getScanFromScannerId(int scannerId) { 186 return mOngoingScans.get(scannerId); 187 } 188 isScanTimeout(int scannerId)189 synchronized boolean isScanTimeout(int scannerId) { 190 LastScan scan = getScanFromScannerId(scannerId); 191 if (scan == null) { 192 return false; 193 } 194 return scan.isTimeout; 195 } 196 isScanDowngraded(int scannerId)197 synchronized boolean isScanDowngraded(int scannerId) { 198 LastScan scan = getScanFromScannerId(scannerId); 199 if (scan == null) { 200 return false; 201 } 202 return scan.isDowngraded; 203 } 204 isAutoBatchScan(int scannerId)205 synchronized boolean isAutoBatchScan(int scannerId) { 206 LastScan scan = getScanFromScannerId(scannerId); 207 if (scan == null) { 208 return false; 209 } 210 return scan.isAutoBatchScan; 211 } 212 recordScanStart(ScanSettings settings, List<ScanFilter> filters, boolean isFilterScan, boolean isCallbackScan, int scannerId)213 synchronized void recordScanStart(ScanSettings settings, List<ScanFilter> filters, 214 boolean isFilterScan, boolean isCallbackScan, int scannerId) { 215 LastScan existingScan = getScanFromScannerId(scannerId); 216 if (existingScan != null) { 217 return; 218 } 219 this.mScansStarted++; 220 startTime = SystemClock.elapsedRealtime(); 221 222 LastScan scan = new LastScan(startTime, isFilterScan, isCallbackScan, scannerId, 223 settings.getScanMode(), settings.getCallbackType()); 224 if (settings != null) { 225 scan.isOpportunisticScan = scan.scanMode == ScanSettings.SCAN_MODE_OPPORTUNISTIC; 226 scan.isBackgroundScan = 227 (scan.scanCallbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0; 228 scan.isBatchScan = 229 settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES 230 && settings.getReportDelayMillis() != 0; 231 switch (scan.scanMode) { 232 case ScanSettings.SCAN_MODE_OPPORTUNISTIC: 233 mOppScan++; 234 break; 235 case ScanSettings.SCAN_MODE_LOW_POWER: 236 mLowPowerScan++; 237 break; 238 case ScanSettings.SCAN_MODE_BALANCED: 239 mBalancedScan++; 240 break; 241 case ScanSettings.SCAN_MODE_LOW_LATENCY: 242 mLowLantencyScan++; 243 break; 244 case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY: 245 mAmbientDiscoveryScan++; 246 break; 247 } 248 } 249 250 if (isFilterScan) { 251 for (ScanFilter filter : filters) { 252 scan.filterString += 253 "\n └ " + filterToStringWithoutNullParam(filter); 254 } 255 } 256 257 BluetoothMetricsProto.ScanEvent scanEvent = BluetoothMetricsProto.ScanEvent.newBuilder() 258 .setScanEventType(BluetoothMetricsProto.ScanEvent.ScanEventType.SCAN_EVENT_START) 259 .setScanTechnologyType( 260 BluetoothMetricsProto.ScanEvent.ScanTechnologyType.SCAN_TECH_TYPE_LE) 261 .setEventTimeMillis(System.currentTimeMillis()) 262 .setInitiator(truncateAppName(appName)).build(); 263 mGattService.addScanEvent(scanEvent); 264 265 if (!isScanning()) { 266 mScanStartTime = startTime; 267 } 268 boolean isUnoptimized = 269 !(scan.isFilterScan || scan.isBackgroundScan || scan.isOpportunisticScan); 270 mBatteryStatsManager.reportBleScanStarted(mWorkSource, isUnoptimized); 271 BluetoothStatsLog.write(BluetoothStatsLog.BLE_SCAN_STATE_CHANGED, 272 mWorkSourceUtil.getUids(), mWorkSourceUtil.getTags(), 273 BluetoothStatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON, 274 scan.isFilterScan, scan.isBackgroundScan, scan.isOpportunisticScan); 275 276 mOngoingScans.put(scannerId, scan); 277 } 278 recordScanStop(int scannerId)279 synchronized void recordScanStop(int scannerId) { 280 LastScan scan = getScanFromScannerId(scannerId); 281 if (scan == null) { 282 return; 283 } 284 this.mScansStopped++; 285 stopTime = SystemClock.elapsedRealtime(); 286 long scanDuration = stopTime - scan.timestamp; 287 scan.duration = scanDuration; 288 if (scan.isSuspended) { 289 long suspendDuration = stopTime - scan.suspendStartTime; 290 scan.suspendDuration += suspendDuration; 291 mTotalSuspendTime += suspendDuration; 292 } 293 mOngoingScans.remove(scannerId); 294 if (mLastScans.size() >= mAdapterService.getScanQuotaCount()) { 295 mLastScans.remove(0); 296 } 297 mLastScans.add(scan); 298 299 BluetoothMetricsProto.ScanEvent scanEvent = BluetoothMetricsProto.ScanEvent.newBuilder() 300 .setScanEventType(BluetoothMetricsProto.ScanEvent.ScanEventType.SCAN_EVENT_STOP) 301 .setScanTechnologyType( 302 BluetoothMetricsProto.ScanEvent.ScanTechnologyType.SCAN_TECH_TYPE_LE) 303 .setEventTimeMillis(System.currentTimeMillis()) 304 .setInitiator(truncateAppName(appName)) 305 .setNumberResults(scan.results) 306 .build(); 307 mGattService.addScanEvent(scanEvent); 308 309 mTotalScanTime += scanDuration; 310 long activeDuration = scanDuration - scan.suspendDuration; 311 mTotalActiveTime += activeDuration; 312 switch (scan.scanMode) { 313 case ScanSettings.SCAN_MODE_OPPORTUNISTIC: 314 mOppScanTime += activeDuration; 315 break; 316 case ScanSettings.SCAN_MODE_LOW_POWER: 317 mLowPowerScanTime += activeDuration; 318 break; 319 case ScanSettings.SCAN_MODE_BALANCED: 320 mBalancedScanTime += activeDuration; 321 break; 322 case ScanSettings.SCAN_MODE_LOW_LATENCY: 323 mLowLantencyScanTime += activeDuration; 324 break; 325 case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY: 326 mAmbientDiscoveryScanTime += activeDuration; 327 break; 328 } 329 330 // Inform battery stats of any results it might be missing on scan stop 331 boolean isUnoptimized = 332 !(scan.isFilterScan || scan.isBackgroundScan || scan.isOpportunisticScan); 333 mBatteryStatsManager.reportBleScanResults(mWorkSource, scan.results % 100); 334 mBatteryStatsManager.reportBleScanStopped(mWorkSource, isUnoptimized); 335 BluetoothStatsLog.write(BluetoothStatsLog.BLE_SCAN_RESULT_RECEIVED, 336 mWorkSourceUtil.getUids(), mWorkSourceUtil.getTags(), scan.results % 100); 337 BluetoothStatsLog.write(BluetoothStatsLog.BLE_SCAN_STATE_CHANGED, 338 mWorkSourceUtil.getUids(), mWorkSourceUtil.getTags(), 339 BluetoothStatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF, 340 scan.isFilterScan, scan.isBackgroundScan, scan.isOpportunisticScan); 341 } 342 initScanRadioState()343 static void initScanRadioState() { 344 synchronized (sLock) { 345 sIsRadioStarted = false; 346 } 347 } recordScanRadioStart(int scanMode)348 static boolean recordScanRadioStart(int scanMode) { 349 synchronized (sLock) { 350 if (sIsRadioStarted) { 351 return false; 352 } 353 sRadioStartTime = SystemClock.elapsedRealtime(); 354 sRadioScanMode = scanMode; 355 sIsRadioStarted = true; 356 } 357 return true; 358 } 359 recordScanRadioStop()360 static boolean recordScanRadioStop() { 361 synchronized (sLock) { 362 if (!sIsRadioStarted) { 363 return false; 364 } 365 recordScanRadioDurationMetrics(); 366 sRadioStartTime = 0; 367 sIsRadioStarted = false; 368 } 369 return true; 370 } 371 372 @GuardedBy("sLock") recordScanRadioDurationMetrics()373 private static void recordScanRadioDurationMetrics() { 374 if (!sIsRadioStarted) { 375 return; 376 } 377 long currentTime = SystemClock.elapsedRealtime(); 378 long radioScanDuration = currentTime - sRadioStartTime; 379 double scanWeight = getScanWeight(sRadioScanMode) * 0.01; 380 long weightedDuration = (long) (radioScanDuration * scanWeight); 381 382 if (weightedDuration > 0) { 383 MetricsLogger.getInstance().cacheCount( 384 BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR, weightedDuration); 385 if (sIsScreenOn) { 386 MetricsLogger.getInstance().cacheCount( 387 BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON, 388 weightedDuration); 389 } else { 390 MetricsLogger.getInstance().cacheCount( 391 BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF, 392 weightedDuration); 393 } 394 } 395 } 396 397 @GuardedBy("sLock") recordScreenOnOffMetrics(boolean isScreenOn)398 private static void recordScreenOnOffMetrics(boolean isScreenOn) { 399 if (isScreenOn) { 400 MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.SCREEN_ON_EVENT, 1); 401 } else { 402 MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.SCREEN_OFF_EVENT, 1); 403 } 404 } 405 getScanWeight(int scanMode)406 private static int getScanWeight(int scanMode) { 407 switch (scanMode) { 408 case ScanSettings.SCAN_MODE_OPPORTUNISTIC: 409 return OPPORTUNISTIC_WEIGHT; 410 case ScanSettings.SCAN_MODE_SCREEN_OFF: 411 return SCREEN_OFF_LOW_POWER_WEIGHT; 412 case ScanSettings.SCAN_MODE_LOW_POWER: 413 return LOW_POWER_WEIGHT; 414 case ScanSettings.SCAN_MODE_BALANCED: 415 case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY: 416 case ScanSettings.SCAN_MODE_SCREEN_OFF_BALANCED: 417 return BALANCED_WEIGHT; 418 case ScanSettings.SCAN_MODE_LOW_LATENCY: 419 return LOW_LATENCY_WEIGHT; 420 default: 421 return LOW_POWER_WEIGHT; 422 } 423 } 424 recordScanRadioResultCount()425 static void recordScanRadioResultCount() { 426 synchronized (sLock) { 427 if (!sIsRadioStarted) { 428 return; 429 } 430 MetricsLogger.getInstance().cacheCount( 431 BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_REGULAR, 1); 432 if (sIsScreenOn) { 433 MetricsLogger.getInstance().cacheCount( 434 BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_REGULAR_SCREEN_ON, 1); 435 } else { 436 MetricsLogger.getInstance().cacheCount( 437 BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_REGULAR_SCREEN_OFF, 1); 438 } 439 } 440 } 441 setScreenState(boolean isScreenOn)442 static void setScreenState(boolean isScreenOn) { 443 synchronized (sLock) { 444 if (sIsScreenOn == isScreenOn) { 445 return; 446 } 447 if (sIsRadioStarted) { 448 recordScanRadioDurationMetrics(); 449 sRadioStartTime = SystemClock.elapsedRealtime(); 450 } 451 recordScreenOnOffMetrics(isScreenOn); 452 sIsScreenOn = isScreenOn; 453 } 454 } 455 recordScanSuspend(int scannerId)456 synchronized void recordScanSuspend(int scannerId) { 457 LastScan scan = getScanFromScannerId(scannerId); 458 if (scan == null || scan.isSuspended) { 459 return; 460 } 461 scan.suspendStartTime = SystemClock.elapsedRealtime(); 462 scan.isSuspended = true; 463 } 464 recordScanResume(int scannerId)465 synchronized void recordScanResume(int scannerId) { 466 LastScan scan = getScanFromScannerId(scannerId); 467 long suspendDuration = 0; 468 if (scan == null || !scan.isSuspended) { 469 return; 470 } 471 scan.isSuspended = false; 472 stopTime = SystemClock.elapsedRealtime(); 473 suspendDuration = stopTime - scan.suspendStartTime; 474 scan.suspendDuration += suspendDuration; 475 mTotalSuspendTime += suspendDuration; 476 } 477 setScanTimeout(int scannerId)478 synchronized void setScanTimeout(int scannerId) { 479 if (!isScanning()) { 480 return; 481 } 482 483 LastScan scan = getScanFromScannerId(scannerId); 484 if (scan != null) { 485 scan.isTimeout = true; 486 } 487 } 488 setScanDowngrade(int scannerId, boolean isDowngrade)489 synchronized void setScanDowngrade(int scannerId, boolean isDowngrade) { 490 if (!isScanning()) { 491 return; 492 } 493 494 LastScan scan = getScanFromScannerId(scannerId); 495 if (scan != null) { 496 scan.isDowngraded = isDowngrade; 497 } 498 } 499 setAutoBatchScan(int scannerId, boolean isBatchScan)500 synchronized void setAutoBatchScan(int scannerId, boolean isBatchScan) { 501 LastScan scan = getScanFromScannerId(scannerId); 502 if (scan != null) { 503 scan.isAutoBatchScan = isBatchScan; 504 } 505 } 506 isScanningTooFrequently()507 synchronized boolean isScanningTooFrequently() { 508 if (mLastScans.size() < mAdapterService.getScanQuotaCount()) { 509 return false; 510 } 511 512 return (SystemClock.elapsedRealtime() - mLastScans.get(0).timestamp) 513 < mAdapterService.getScanQuotaWindowMillis(); 514 } 515 isScanningTooLong()516 synchronized boolean isScanningTooLong() { 517 if (!isScanning()) { 518 return false; 519 } 520 return (SystemClock.elapsedRealtime() - mScanStartTime) 521 > mAdapterService.getScanTimeoutMillis(); 522 } 523 hasRecentScan()524 synchronized boolean hasRecentScan() { 525 if (!isScanning() || mLastScans.isEmpty()) { 526 return false; 527 } 528 LastScan lastScan = mLastScans.get(mLastScans.size() - 1); 529 return ((SystemClock.elapsedRealtime() - lastScan.duration - lastScan.timestamp) 530 < LARGE_SCAN_TIME_GAP_MS); 531 } 532 533 // This function truncates the app name for privacy reasons. Apps with 534 // four part package names or more get truncated to three parts, and apps 535 // with three part package names names get truncated to two. Apps with two 536 // or less package names names are untouched. 537 // Examples: one.two.three.four => one.two.three 538 // one.two.three => one.two truncateAppName(String name)539 private String truncateAppName(String name) { 540 String initiator = name; 541 String[] nameSplit = initiator.split("\\."); 542 if (nameSplit.length > 3) { 543 initiator = nameSplit[0] + "." + nameSplit[1] + "." + nameSplit[2]; 544 } else if (nameSplit.length == 3) { 545 initiator = nameSplit[0] + "." + nameSplit[1]; 546 } 547 548 return initiator; 549 } 550 filterToStringWithoutNullParam(ScanFilter filter)551 private static String filterToStringWithoutNullParam(ScanFilter filter) { 552 String filterString = "BluetoothLeScanFilter ["; 553 if (filter.getDeviceName() != null) { 554 filterString += " DeviceName=" + filter.getDeviceName(); 555 } 556 if (filter.getDeviceAddress() != null) { 557 filterString += " DeviceAddress=" + filter.getDeviceAddress(); 558 } 559 if (filter.getServiceUuid() != null) { 560 filterString += " ServiceUuid=" + filter.getServiceUuid(); 561 } 562 if (filter.getServiceUuidMask() != null) { 563 filterString += " ServiceUuidMask=" + filter.getServiceUuidMask(); 564 } 565 if (filter.getServiceSolicitationUuid() != null) { 566 filterString += " ServiceSolicitationUuid=" + filter.getServiceSolicitationUuid(); 567 } 568 if (filter.getServiceSolicitationUuidMask() != null) { 569 filterString += 570 " ServiceSolicitationUuidMask=" + filter.getServiceSolicitationUuidMask(); 571 } 572 if (filter.getServiceDataUuid() != null) { 573 filterString += " ServiceDataUuid=" + Objects.toString(filter.getServiceDataUuid()); 574 } 575 if (filter.getServiceData() != null) { 576 filterString += " ServiceData=" + Arrays.toString(filter.getServiceData()); 577 } 578 if (filter.getServiceDataMask() != null) { 579 filterString += " ServiceDataMask=" + Arrays.toString(filter.getServiceDataMask()); 580 } 581 if (filter.getManufacturerId() >= 0) { 582 filterString += " ManufacturerId=" + filter.getManufacturerId(); 583 } 584 if (filter.getManufacturerData() != null) { 585 filterString += " ManufacturerData=" + Arrays.toString(filter.getManufacturerData()); 586 } 587 if (filter.getManufacturerDataMask() != null) { 588 filterString += 589 " ManufacturerDataMask=" + Arrays.toString(filter.getManufacturerDataMask()); 590 } 591 filterString += " ]"; 592 return filterString; 593 } 594 595 scanModeToString(int scanMode)596 private static String scanModeToString(int scanMode) { 597 switch (scanMode) { 598 case ScanSettings.SCAN_MODE_OPPORTUNISTIC: 599 return "OPPORTUNISTIC"; 600 case ScanSettings.SCAN_MODE_LOW_LATENCY: 601 return "LOW_LATENCY"; 602 case ScanSettings.SCAN_MODE_BALANCED: 603 return "BALANCED"; 604 case ScanSettings.SCAN_MODE_LOW_POWER: 605 return "LOW_POWER"; 606 case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY: 607 return "AMBIENT_DISCOVERY"; 608 default: 609 return "UNKNOWN(" + scanMode + ")"; 610 } 611 } 612 callbackTypeToString(int callbackType)613 private static String callbackTypeToString(int callbackType) { 614 switch (callbackType) { 615 case ScanSettings.CALLBACK_TYPE_ALL_MATCHES: 616 return "ALL_MATCHES"; 617 case ScanSettings.CALLBACK_TYPE_FIRST_MATCH: 618 return "FIRST_MATCH"; 619 case ScanSettings.CALLBACK_TYPE_MATCH_LOST: 620 return "LOST"; 621 case ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH: 622 return "ALL_MATCHES_AUTO_BATCH"; 623 default: 624 return callbackType == (ScanSettings.CALLBACK_TYPE_FIRST_MATCH 625 | ScanSettings.CALLBACK_TYPE_MATCH_LOST) ? "[FIRST_MATCH | LOST]" : "UNKNOWN: " 626 + callbackType; 627 } 628 } 629 dumpToString(StringBuilder sb)630 synchronized void dumpToString(StringBuilder sb) { 631 long currentTime = System.currentTimeMillis(); 632 long currTime = SystemClock.elapsedRealtime(); 633 long Score = 0; 634 long scanDuration = 0; 635 long suspendDuration = 0; 636 long activeDuration = 0; 637 long totalActiveTime = mTotalActiveTime; 638 long totalSuspendTime = mTotalSuspendTime; 639 long totalScanTime = mTotalScanTime; 640 long oppScanTime = mOppScanTime; 641 long lowPowerScanTime = mLowPowerScanTime; 642 long balancedScanTime = mBalancedScanTime; 643 long lowLatencyScanTime = mLowLantencyScanTime; 644 long ambientDiscoveryScanTime = mAmbientDiscoveryScanTime; 645 int oppScan = mOppScan; 646 int lowPowerScan = mLowPowerScan; 647 int balancedScan = mBalancedScan; 648 int lowLatencyScan = mLowLantencyScan; 649 int ambientDiscoveryScan = mAmbientDiscoveryScan; 650 651 if (!mOngoingScans.isEmpty()) { 652 for (Integer key : mOngoingScans.keySet()) { 653 LastScan scan = mOngoingScans.get(key); 654 scanDuration = currTime - scan.timestamp; 655 656 if (scan.isSuspended) { 657 suspendDuration = currTime - scan.suspendStartTime; 658 totalSuspendTime += suspendDuration; 659 } 660 661 totalScanTime += scanDuration; 662 totalSuspendTime += suspendDuration; 663 activeDuration = scanDuration - scan.suspendDuration - suspendDuration; 664 totalActiveTime += activeDuration; 665 switch (scan.scanMode) { 666 case ScanSettings.SCAN_MODE_OPPORTUNISTIC: 667 oppScanTime += activeDuration; 668 break; 669 case ScanSettings.SCAN_MODE_LOW_POWER: 670 lowPowerScanTime += activeDuration; 671 break; 672 case ScanSettings.SCAN_MODE_BALANCED: 673 balancedScanTime += activeDuration; 674 break; 675 case ScanSettings.SCAN_MODE_LOW_LATENCY: 676 lowLatencyScanTime += activeDuration; 677 break; 678 case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY: 679 ambientDiscoveryScan += activeDuration; 680 break; 681 } 682 } 683 } 684 Score = (oppScanTime * OPPORTUNISTIC_WEIGHT + lowPowerScanTime * LOW_POWER_WEIGHT 685 + balancedScanTime * BALANCED_WEIGHT + lowLatencyScanTime * LOW_LATENCY_WEIGHT 686 + ambientDiscoveryScanTime * AMBIENT_DISCOVERY_WEIGHT) / 100; 687 688 sb.append(" " + appName); 689 if (isRegistered) { 690 sb.append(" (Registered)"); 691 } 692 693 sb.append("\n LE scans (started/stopped) : " 694 + mScansStarted + " / " + mScansStopped); 695 sb.append("\n Scan time in ms (active/suspend/total) : " 696 + totalActiveTime + " / " + totalSuspendTime + " / " + totalScanTime); 697 sb.append("\n Scan time with mode in ms " 698 + "(Opp/LowPower/Balanced/LowLatency/AmbientDiscovery):" 699 + oppScanTime + " / " + lowPowerScanTime + " / " + balancedScanTime + " / " 700 + lowLatencyScanTime + " / " + ambientDiscoveryScanTime); 701 sb.append("\n Scan mode counter (Opp/LowPower/Balanced/LowLatency/AmbientDiscovery):" 702 + oppScan + " / " + lowPowerScan + " / " + balancedScan + " / " + lowLatencyScan 703 + " / " + ambientDiscoveryScan); 704 sb.append("\n Score : " + Score); 705 sb.append("\n Total number of results : " + results); 706 707 if (!mLastScans.isEmpty()) { 708 sb.append("\n Last " + mLastScans.size() 709 + " scans :"); 710 711 for (int i = 0; i < mLastScans.size(); i++) { 712 LastScan scan = mLastScans.get(i); 713 Date timestamp = new Date(currentTime - currTime + scan.timestamp); 714 sb.append("\n " + DATE_FORMAT.format(timestamp) + " - "); 715 sb.append(scan.duration + "ms "); 716 if (scan.isOpportunisticScan) { 717 sb.append("Opp "); 718 } 719 if (scan.isBackgroundScan) { 720 sb.append("Back "); 721 } 722 if (scan.isTimeout) { 723 sb.append("Forced "); 724 } 725 if (scan.isFilterScan) { 726 sb.append("Filter "); 727 } 728 sb.append(scan.results + " results"); 729 sb.append(" (" + scan.scannerId + ") "); 730 if (scan.isCallbackScan) { 731 sb.append("CB "); 732 } else { 733 sb.append("PI "); 734 } 735 if (scan.isBatchScan) { 736 sb.append("Batch Scan"); 737 } else if (scan.isAutoBatchScan) { 738 sb.append("Auto Batch Scan"); 739 } else { 740 sb.append("Regular Scan"); 741 } 742 if (scan.suspendDuration != 0) { 743 activeDuration = scan.duration - scan.suspendDuration; 744 sb.append("\n └ " + "Suspended Time: " + scan.suspendDuration 745 + "ms, Active Time: " + activeDuration); 746 } 747 sb.append("\n └ " + "Scan Config: [ ScanMode=" 748 + scanModeToString(scan.scanMode) + ", callbackType=" 749 + callbackTypeToString(scan.scanCallbackType) + " ]"); 750 if (scan.isFilterScan) { 751 sb.append(scan.filterString); 752 } 753 } 754 } 755 756 if (!mOngoingScans.isEmpty()) { 757 sb.append("\n Ongoing scans :"); 758 for (Integer key : mOngoingScans.keySet()) { 759 LastScan scan = mOngoingScans.get(key); 760 Date timestamp = new Date(currentTime - currTime + scan.timestamp); 761 sb.append("\n " + DATE_FORMAT.format(timestamp) + " - "); 762 sb.append((currTime - scan.timestamp) + "ms "); 763 if (scan.isOpportunisticScan) { 764 sb.append("Opp "); 765 } 766 if (scan.isBackgroundScan) { 767 sb.append("Back "); 768 } 769 if (scan.isTimeout) { 770 sb.append("Forced "); 771 } 772 if (scan.isFilterScan) { 773 sb.append("Filter "); 774 } 775 if (scan.isSuspended) { 776 sb.append("Suspended "); 777 } 778 sb.append(scan.results + " results"); 779 sb.append(" (" + scan.scannerId + ") "); 780 if (scan.isCallbackScan) { 781 sb.append("CB "); 782 } else { 783 sb.append("PI "); 784 } 785 if (scan.isBatchScan) { 786 sb.append("Batch Scan"); 787 } else if (scan.isAutoBatchScan) { 788 sb.append("Auto Batch Scan"); 789 } else { 790 sb.append("Regular Scan"); 791 } 792 if (scan.suspendStartTime != 0) { 793 long duration = scan.suspendDuration + (scan.isSuspended ? (currTime 794 - scan.suspendStartTime) : 0); 795 activeDuration = scan.duration - scan.suspendDuration; 796 sb.append("\n └ " + "Suspended Time:" + scan.suspendDuration 797 + "ms, Active Time:" + activeDuration); 798 } 799 sb.append("\n └ " + "Scan Config: [ ScanMode=" 800 + scanModeToString(scan.scanMode) + ", callbackType=" 801 + callbackTypeToString(scan.scanCallbackType) + " ]"); 802 if (scan.isFilterScan) { 803 sb.append(scan.filterString); 804 } 805 } 806 } 807 808 ContextMap.App appEntry = mContextMap.getByName(appName); 809 if (appEntry != null && isRegistered) { 810 sb.append("\n Application ID : " + appEntry.id); 811 sb.append("\n UUID : " + appEntry.uuid); 812 813 List<ContextMap.Connection> connections = mContextMap.getConnectionByApp(appEntry.id); 814 815 sb.append("\n Connections: " + connections.size()); 816 817 Iterator<ContextMap.Connection> ii = connections.iterator(); 818 while (ii.hasNext()) { 819 ContextMap.Connection connection = ii.next(); 820 long connectionTime = currTime - connection.startTime; 821 Date timestamp = new Date(currentTime - currTime + connection.startTime); 822 sb.append("\n " + DATE_FORMAT.format(timestamp) + " - "); 823 sb.append((connectionTime) + "ms "); 824 sb.append(": " + connection.address + " (" + connection.connId + ")"); 825 } 826 } 827 sb.append("\n\n"); 828 } 829 } 830