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