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