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