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