1 /* 2 * Copyright (C) 2021 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 static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; 19 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; 20 21 import static com.android.bluetooth.util.AttributionSourceUtil.getLastAttributionTag; 22 23 import android.annotation.Nullable; 24 import android.bluetooth.BluetoothDevice; 25 import android.bluetooth.BluetoothProtoEnums; 26 import android.bluetooth.le.AdvertiseData; 27 import android.bluetooth.le.AdvertisingSetCallback; 28 import android.bluetooth.le.AdvertisingSetParameters; 29 import android.bluetooth.le.PeriodicAdvertisingParameters; 30 import android.content.AttributionSource; 31 import android.os.ParcelUuid; 32 import android.util.SparseArray; 33 34 import androidx.annotation.VisibleForTesting; 35 36 import com.android.bluetooth.BluetoothStatsLog; 37 import com.android.bluetooth.btservice.MetricsLogger; 38 39 import java.time.Duration; 40 import java.time.Instant; 41 import java.time.ZoneId; 42 import java.time.format.DateTimeFormatter; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.Map; 46 47 /** AdvStats class helps keep track of information about advertising on a per application basis. */ 48 class AppAdvertiseStats { 49 private static final String TAG = AppAdvertiseStats.class.getSimpleName(); 50 51 private static final DateTimeFormatter sDateFormat = 52 DateTimeFormatter.ofPattern("MM-dd HH:mm:ss").withZone(ZoneId.systemDefault()); 53 54 static final String[] PHY_LE_STRINGS = {"LE_1M", "LE_2M", "LE_CODED"}; 55 static final int UUID_STRING_FILTER_LEN = 8; 56 57 static class AppAdvertiserData { 58 public boolean includeDeviceName = false; 59 public boolean includeTxPowerLevel = false; 60 public SparseArray<byte[]> manufacturerData; 61 public Map<ParcelUuid, byte[]> serviceData; 62 public List<ParcelUuid> serviceUuids; 63 AppAdvertiserData( boolean includeDeviceName, boolean includeTxPowerLevel, SparseArray<byte[]> manufacturerData, Map<ParcelUuid, byte[]> serviceData, List<ParcelUuid> serviceUuids)64 AppAdvertiserData( 65 boolean includeDeviceName, 66 boolean includeTxPowerLevel, 67 SparseArray<byte[]> manufacturerData, 68 Map<ParcelUuid, byte[]> serviceData, 69 List<ParcelUuid> serviceUuids) { 70 this.includeDeviceName = includeDeviceName; 71 this.includeTxPowerLevel = includeTxPowerLevel; 72 this.manufacturerData = manufacturerData; 73 this.serviceData = serviceData; 74 this.serviceUuids = serviceUuids; 75 } 76 } 77 78 static class AppAdvertiserRecord { 79 public Instant startTime = null; 80 public Instant stopTime = null; 81 public int duration = 0; 82 public int maxExtendedAdvertisingEvents = 0; 83 public int appImportanceOnStart; 84 AppAdvertiserRecord(Instant startTime, int appImportanceOnStart)85 AppAdvertiserRecord(Instant startTime, int appImportanceOnStart) { 86 this.startTime = startTime; 87 this.appImportanceOnStart = appImportanceOnStart; 88 } 89 } 90 91 private final int mAppUid; 92 @VisibleForTesting String mAppName; 93 private final @Nullable String mAttributionTag; 94 private int mId; 95 private boolean mAdvertisingEnabled = false; 96 private boolean mPeriodicAdvertisingEnabled = false; 97 private int mPrimaryPhy = BluetoothDevice.PHY_LE_1M; 98 private int mSecondaryPhy = BluetoothDevice.PHY_LE_1M; 99 private int mInterval = 0; 100 private int mTxPowerLevel = 0; 101 private boolean mLegacy = false; 102 private boolean mAnonymous = false; 103 private boolean mConnectable = false; 104 private boolean mScannable = false; 105 private @Nullable AppAdvertiserData mAdvertisingData = null; 106 private @Nullable AppAdvertiserData mScanResponseData = null; 107 private @Nullable AppAdvertiserData mPeriodicAdvertisingData = null; 108 private boolean mPeriodicIncludeTxPower = false; 109 private int mPeriodicInterval = 0; 110 private int mAppImportance = IMPORTANCE_CACHED; 111 public ArrayList<AppAdvertiserRecord> mAdvertiserRecords = new ArrayList<AppAdvertiserRecord>(); 112 AppAdvertiseStats(int appUid, int id, String name, AttributionSource attrSource)113 AppAdvertiseStats(int appUid, int id, String name, AttributionSource attrSource) { 114 this.mAppUid = appUid; 115 this.mId = id; 116 this.mAppName = name; 117 this.mAttributionTag = getLastAttributionTag(attrSource); 118 } 119 recordAdvertiseStart( AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, int duration, int maxExtAdvEvents, int instanceCount)120 void recordAdvertiseStart( 121 AdvertisingSetParameters parameters, 122 AdvertiseData advertiseData, 123 AdvertiseData scanResponse, 124 PeriodicAdvertisingParameters periodicParameters, 125 AdvertiseData periodicData, 126 int duration, 127 int maxExtAdvEvents, 128 int instanceCount) { 129 mAdvertisingEnabled = true; 130 AppAdvertiserRecord record = new AppAdvertiserRecord(Instant.now(), mAppImportance); 131 record.duration = duration; 132 record.maxExtendedAdvertisingEvents = maxExtAdvEvents; 133 mAdvertiserRecords.add(record); 134 if (mAdvertiserRecords.size() > 5) { 135 mAdvertiserRecords.remove(0); 136 } 137 138 if (parameters != null) { 139 mPrimaryPhy = parameters.getPrimaryPhy(); 140 mSecondaryPhy = parameters.getSecondaryPhy(); 141 mInterval = parameters.getInterval(); 142 mTxPowerLevel = parameters.getTxPowerLevel(); 143 mLegacy = parameters.isLegacy(); 144 mAnonymous = parameters.isAnonymous(); 145 mConnectable = parameters.isConnectable(); 146 mScannable = parameters.isScannable(); 147 } 148 149 if (advertiseData != null) { 150 mAdvertisingData = 151 new AppAdvertiserData( 152 advertiseData.getIncludeDeviceName(), 153 advertiseData.getIncludeTxPowerLevel(), 154 advertiseData.getManufacturerSpecificData(), 155 advertiseData.getServiceData(), 156 advertiseData.getServiceUuids()); 157 } 158 159 if (scanResponse != null) { 160 mScanResponseData = 161 new AppAdvertiserData( 162 scanResponse.getIncludeDeviceName(), 163 scanResponse.getIncludeTxPowerLevel(), 164 scanResponse.getManufacturerSpecificData(), 165 scanResponse.getServiceData(), 166 scanResponse.getServiceUuids()); 167 } 168 169 if (periodicData != null) { 170 mPeriodicAdvertisingData = 171 new AppAdvertiserData( 172 periodicData.getIncludeDeviceName(), 173 periodicData.getIncludeTxPowerLevel(), 174 periodicData.getManufacturerSpecificData(), 175 periodicData.getServiceData(), 176 periodicData.getServiceUuids()); 177 } 178 179 if (periodicParameters != null) { 180 mPeriodicAdvertisingEnabled = true; 181 mPeriodicIncludeTxPower = periodicParameters.getIncludeTxPower(); 182 mPeriodicInterval = periodicParameters.getInterval(); 183 } 184 recordAdvertiseEnableCount(true, instanceCount, 0 /* durationMs */); 185 } 186 recordAdvertiseStart(int duration, int maxExtAdvEvents, int instanceCount)187 void recordAdvertiseStart(int duration, int maxExtAdvEvents, int instanceCount) { 188 recordAdvertiseStart( 189 null, null, null, null, null, duration, maxExtAdvEvents, instanceCount); 190 } 191 recordAdvertiseStop(int instanceCount)192 void recordAdvertiseStop(int instanceCount) { 193 if (!mAdvertiserRecords.isEmpty()) { 194 AppAdvertiserRecord record = mAdvertiserRecords.get(mAdvertiserRecords.size() - 1); 195 record.stopTime = Instant.now(); 196 Duration duration = Duration.between(record.startTime, record.stopTime); 197 recordAdvertiseDurationCount(duration, mConnectable, mPeriodicAdvertisingEnabled); 198 recordAdvertiseEnableCount( 199 false, 200 instanceCount, 201 record.stopTime.toEpochMilli() - record.startTime.toEpochMilli()); 202 } 203 mAdvertisingEnabled = false; 204 mPeriodicAdvertisingEnabled = false; 205 } 206 recordAdvertiseInstanceCount(int instanceCount)207 static void recordAdvertiseInstanceCount(int instanceCount) { 208 if (instanceCount < 5) { 209 MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.LE_ADV_INSTANCE_COUNT_5, 1); 210 } else if (instanceCount < 10) { 211 MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.LE_ADV_INSTANCE_COUNT_10, 1); 212 } else if (instanceCount < 15) { 213 MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.LE_ADV_INSTANCE_COUNT_15, 1); 214 } else { 215 MetricsLogger.getInstance() 216 .cacheCount(BluetoothProtoEnums.LE_ADV_INSTANCE_COUNT_15P, 1); 217 } 218 } 219 recordAdvertiseErrorCount(int status)220 void recordAdvertiseErrorCount(int status) { 221 BluetoothStatsLog.write( 222 BluetoothStatsLog.LE_ADV_ERROR_REPORTED, 223 new int[] {mAppUid}, 224 new String[] {mAppName}, 225 BluetoothStatsLog.LE_ADV_ERROR_REPORTED__LE_ADV_OP_CODE__ERROR_CODE_ON_START, 226 convertStatusCode(status), 227 getAttributionTag()); 228 MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.LE_ADV_ERROR_ON_START_COUNT, 1); 229 } 230 convertStatusCode(int status)231 private static int convertStatusCode(int status) { 232 switch (status) { 233 case AdvertisingSetCallback.ADVERTISE_SUCCESS: 234 return BluetoothStatsLog.LE_ADV_ERROR_REPORTED__STATUS_CODE__ADV_STATUS_SUCCESS; 235 case AdvertisingSetCallback.ADVERTISE_FAILED_DATA_TOO_LARGE: 236 return BluetoothStatsLog 237 .LE_ADV_ERROR_REPORTED__STATUS_CODE__ADV_STATUS_FAILED_DATA_TOO_LARGE; 238 case AdvertisingSetCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS: 239 return BluetoothStatsLog 240 .LE_ADV_ERROR_REPORTED__STATUS_CODE__ADV_STATUS_FAILED_TOO_MANY_ADVERTISERS; 241 case AdvertisingSetCallback.ADVERTISE_FAILED_ALREADY_STARTED: 242 return BluetoothStatsLog 243 .LE_ADV_ERROR_REPORTED__STATUS_CODE__ADV_STATUS_FAILED_ALREADY_STARTED; 244 case AdvertisingSetCallback.ADVERTISE_FAILED_INTERNAL_ERROR: 245 return BluetoothStatsLog 246 .LE_ADV_ERROR_REPORTED__STATUS_CODE__ADV_STATUS_FAILED_INTERNAL_ERROR; 247 case AdvertisingSetCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED: 248 return BluetoothStatsLog 249 .LE_ADV_ERROR_REPORTED__STATUS_CODE__ADV_STATUS_FAILED_FEATURE_UNSUPPORTED; 250 default: 251 return BluetoothStatsLog.LE_ADV_ERROR_REPORTED__STATUS_CODE__ADV_STATUS_UNKNOWN; 252 } 253 } 254 enableAdvertisingSet( boolean enable, int duration, int maxExtAdvEvents, int instanceCount)255 void enableAdvertisingSet( 256 boolean enable, int duration, int maxExtAdvEvents, int instanceCount) { 257 if (enable) { 258 // if the advertisingSet have not been disabled, skip enabling. 259 if (!mAdvertisingEnabled) { 260 recordAdvertiseStart(duration, maxExtAdvEvents, instanceCount); 261 } 262 } else { 263 // if the advertisingSet have not been enabled, skip disabling. 264 if (mAdvertisingEnabled) { 265 recordAdvertiseStop(instanceCount); 266 } 267 } 268 } 269 setAdvertisingData(AdvertiseData data)270 void setAdvertisingData(AdvertiseData data) { 271 if (mAdvertisingData == null) { 272 mAdvertisingData = 273 new AppAdvertiserData( 274 data.getIncludeDeviceName(), 275 data.getIncludeTxPowerLevel(), 276 data.getManufacturerSpecificData(), 277 data.getServiceData(), 278 data.getServiceUuids()); 279 } else if (data != null) { 280 mAdvertisingData.includeDeviceName = data.getIncludeDeviceName(); 281 mAdvertisingData.includeTxPowerLevel = data.getIncludeTxPowerLevel(); 282 mAdvertisingData.manufacturerData = data.getManufacturerSpecificData(); 283 mAdvertisingData.serviceData = data.getServiceData(); 284 mAdvertisingData.serviceUuids = data.getServiceUuids(); 285 } 286 } 287 setScanResponseData(AdvertiseData data)288 void setScanResponseData(AdvertiseData data) { 289 if (mScanResponseData == null) { 290 mScanResponseData = 291 new AppAdvertiserData( 292 data.getIncludeDeviceName(), 293 data.getIncludeTxPowerLevel(), 294 data.getManufacturerSpecificData(), 295 data.getServiceData(), 296 data.getServiceUuids()); 297 } else if (data != null) { 298 mScanResponseData.includeDeviceName = data.getIncludeDeviceName(); 299 mScanResponseData.includeTxPowerLevel = data.getIncludeTxPowerLevel(); 300 mScanResponseData.manufacturerData = data.getManufacturerSpecificData(); 301 mScanResponseData.serviceData = data.getServiceData(); 302 mScanResponseData.serviceUuids = data.getServiceUuids(); 303 } 304 } 305 setAdvertisingParameters(AdvertisingSetParameters parameters)306 void setAdvertisingParameters(AdvertisingSetParameters parameters) { 307 if (parameters != null) { 308 mPrimaryPhy = parameters.getPrimaryPhy(); 309 mSecondaryPhy = parameters.getSecondaryPhy(); 310 mInterval = parameters.getInterval(); 311 mTxPowerLevel = parameters.getTxPowerLevel(); 312 mLegacy = parameters.isLegacy(); 313 mAnonymous = parameters.isAnonymous(); 314 mConnectable = parameters.isConnectable(); 315 mScannable = parameters.isScannable(); 316 } 317 } 318 setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters)319 void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) { 320 if (parameters != null) { 321 mPeriodicIncludeTxPower = parameters.getIncludeTxPower(); 322 mPeriodicInterval = parameters.getInterval(); 323 } 324 } 325 setPeriodicAdvertisingData(AdvertiseData data)326 void setPeriodicAdvertisingData(AdvertiseData data) { 327 if (mPeriodicAdvertisingData == null) { 328 mPeriodicAdvertisingData = 329 new AppAdvertiserData( 330 data.getIncludeDeviceName(), 331 data.getIncludeTxPowerLevel(), 332 data.getManufacturerSpecificData(), 333 data.getServiceData(), 334 data.getServiceUuids()); 335 } else if (data != null) { 336 mPeriodicAdvertisingData.includeDeviceName = data.getIncludeDeviceName(); 337 mPeriodicAdvertisingData.includeTxPowerLevel = data.getIncludeTxPowerLevel(); 338 mPeriodicAdvertisingData.manufacturerData = data.getManufacturerSpecificData(); 339 mPeriodicAdvertisingData.serviceData = data.getServiceData(); 340 mPeriodicAdvertisingData.serviceUuids = data.getServiceUuids(); 341 } 342 } 343 onPeriodicAdvertiseEnabled(boolean enable)344 void onPeriodicAdvertiseEnabled(boolean enable) { 345 mPeriodicAdvertisingEnabled = enable; 346 } 347 setId(int id)348 void setId(int id) { 349 this.mId = id; 350 } 351 setAppImportance(int importance)352 void setAppImportance(int importance) { 353 mAppImportance = importance; 354 } 355 getAttributionTag()356 private String getAttributionTag() { 357 return mAttributionTag != null ? mAttributionTag : ""; 358 } 359 recordAdvertiseDurationCount( Duration duration, boolean isConnectable, boolean inPeriodic)360 private static void recordAdvertiseDurationCount( 361 Duration duration, boolean isConnectable, boolean inPeriodic) { 362 if (duration.compareTo(Duration.ofMinutes(1)) < 0) { 363 MetricsLogger.getInstance() 364 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_TOTAL_1M, 1); 365 if (isConnectable) { 366 MetricsLogger.getInstance() 367 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_CONNECTABLE_1M, 1); 368 } 369 if (inPeriodic) { 370 MetricsLogger.getInstance() 371 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_PERIODIC_1M, 1); 372 } 373 } else if (duration.compareTo(Duration.ofMinutes(30)) < 0) { 374 MetricsLogger.getInstance() 375 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_TOTAL_30M, 1); 376 if (isConnectable) { 377 MetricsLogger.getInstance() 378 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_CONNECTABLE_30M, 1); 379 } 380 if (inPeriodic) { 381 MetricsLogger.getInstance() 382 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_PERIODIC_30M, 1); 383 } 384 } else if (duration.compareTo(Duration.ofHours(1)) < 0) { 385 MetricsLogger.getInstance() 386 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_TOTAL_1H, 1); 387 if (isConnectable) { 388 MetricsLogger.getInstance() 389 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_CONNECTABLE_1H, 1); 390 } 391 if (inPeriodic) { 392 MetricsLogger.getInstance() 393 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_PERIODIC_1H, 1); 394 } 395 } else if (duration.compareTo(Duration.ofHours(3)) < 0) { 396 MetricsLogger.getInstance() 397 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_TOTAL_3H, 1); 398 if (isConnectable) { 399 MetricsLogger.getInstance() 400 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_CONNECTABLE_3H, 1); 401 } 402 if (inPeriodic) { 403 MetricsLogger.getInstance() 404 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_PERIODIC_3H, 1); 405 } 406 } else { 407 MetricsLogger.getInstance() 408 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_TOTAL_3HP, 1); 409 if (isConnectable) { 410 MetricsLogger.getInstance() 411 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_CONNECTABLE_3HP, 1); 412 } 413 if (inPeriodic) { 414 MetricsLogger.getInstance() 415 .cacheCount(BluetoothProtoEnums.LE_ADV_DURATION_COUNT_PERIODIC_3HP, 1); 416 } 417 } 418 } 419 recordAdvertiseEnableCount(boolean enable, int instanceCount, long durationMs)420 private void recordAdvertiseEnableCount(boolean enable, int instanceCount, long durationMs) { 421 MetricsLogger.getInstance() 422 .logAdvStateChanged( 423 new int[] {mAppUid}, 424 new String[] {mAppName}, 425 enable /* enabled */, 426 convertAdvInterval(mInterval), 427 convertTxPowerLevel(mTxPowerLevel), 428 mConnectable, 429 mPeriodicAdvertisingEnabled, 430 mScanResponseData != null && mScannable /* hasScanResponse */, 431 !mLegacy /* isExtendedAdv */, 432 instanceCount, 433 durationMs, 434 mAppImportance, 435 getAttributionTag()); 436 if (enable) { 437 MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.LE_ADV_COUNT_ENABLE, 1); 438 if (mConnectable) { 439 MetricsLogger.getInstance() 440 .cacheCount(BluetoothProtoEnums.LE_ADV_COUNT_CONNECTABLE_ENABLE, 1); 441 } 442 if (mPeriodicAdvertisingEnabled) { 443 MetricsLogger.getInstance() 444 .cacheCount(BluetoothProtoEnums.LE_ADV_COUNT_PERIODIC_ENABLE, 1); 445 } 446 } else { 447 MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.LE_ADV_COUNT_DISABLE, 1); 448 if (mConnectable) { 449 MetricsLogger.getInstance() 450 .cacheCount(BluetoothProtoEnums.LE_ADV_COUNT_CONNECTABLE_DISABLE, 1); 451 } 452 if (mPeriodicAdvertisingEnabled) { 453 MetricsLogger.getInstance() 454 .cacheCount(BluetoothProtoEnums.LE_ADV_COUNT_PERIODIC_DISABLE, 1); 455 } 456 } 457 } 458 convertAdvInterval(int interval)459 private static int convertAdvInterval(int interval) { 460 switch (interval) { 461 case AdvertisingSetParameters.INTERVAL_HIGH: 462 return BluetoothStatsLog.LE_ADV_STATE_CHANGED__ADV_INTERVAL__INTERVAL_HIGH; 463 case AdvertisingSetParameters.INTERVAL_MEDIUM: 464 return BluetoothStatsLog.LE_ADV_STATE_CHANGED__ADV_INTERVAL__INTERVAL_MEDIUM; 465 case AdvertisingSetParameters.INTERVAL_LOW: 466 return BluetoothStatsLog.LE_ADV_STATE_CHANGED__ADV_INTERVAL__INTERVAL_LOW; 467 default: 468 return BluetoothStatsLog.LE_ADV_STATE_CHANGED__ADV_INTERVAL__INTERVAL_UNKNOWN; 469 } 470 } 471 convertTxPowerLevel(int level)472 private static int convertTxPowerLevel(int level) { 473 switch (level) { 474 case AdvertisingSetParameters.TX_POWER_ULTRA_LOW: 475 return BluetoothStatsLog.LE_ADV_STATE_CHANGED__ADV_TX_POWER__TX_POWER_ULTRA_LOW; 476 case AdvertisingSetParameters.TX_POWER_LOW: 477 return BluetoothStatsLog.LE_ADV_STATE_CHANGED__ADV_TX_POWER__TX_POWER_LOW; 478 case AdvertisingSetParameters.TX_POWER_MEDIUM: 479 return BluetoothStatsLog.LE_ADV_STATE_CHANGED__ADV_TX_POWER__TX_POWER_MEDIUM; 480 case AdvertisingSetParameters.TX_POWER_HIGH: 481 return BluetoothStatsLog.LE_ADV_STATE_CHANGED__ADV_TX_POWER__TX_POWER_HIGH; 482 default: 483 return BluetoothStatsLog.LE_ADV_STATE_CHANGED__ADV_TX_POWER__TX_POWER_UNKNOWN; 484 } 485 } 486 dumpAppAdvertiserData(StringBuilder sb, AppAdvertiserData advData)487 private static void dumpAppAdvertiserData(StringBuilder sb, AppAdvertiserData advData) { 488 sb.append("\n └Include Device Name : ") 489 .append(advData.includeDeviceName); 490 sb.append("\n └Include Tx Power Level : ") 491 .append(advData.includeTxPowerLevel); 492 493 if (advData.manufacturerData.size() > 0) { 494 sb.append("\n └Manufacturer Data (length of data) : ") 495 .append(advData.manufacturerData.size()); 496 } 497 498 if (!advData.serviceData.isEmpty()) { 499 sb.append("\n └Service Data(UUID, length of data) : "); 500 for (ParcelUuid uuid : advData.serviceData.keySet()) { 501 sb.append("\n [") 502 .append(uuid.toString().substring(0, UUID_STRING_FILTER_LEN)) 503 .append("-xxxx-xxxx-xxxx-xxxxxxxxxxxx, ") 504 .append(advData.serviceData.get(uuid).length) 505 .append("]"); 506 } 507 } 508 509 if (!advData.serviceUuids.isEmpty()) { 510 sb.append("\n └Service Uuids : \n ") 511 .append(advData.serviceUuids.toString().substring(0, UUID_STRING_FILTER_LEN)) 512 .append("-xxxx-xxxx-xxxx-xxxxxxxxxxxx"); 513 } 514 } 515 dumpPhyString(int phy)516 private static String dumpPhyString(int phy) { 517 if (phy > PHY_LE_STRINGS.length) { 518 return Integer.toString(phy); 519 } else { 520 return PHY_LE_STRINGS[phy - 1]; 521 } 522 } 523 dumpAppAdvertiseStats(StringBuilder sb, AppAdvertiseStats stats)524 private static void dumpAppAdvertiseStats(StringBuilder sb, AppAdvertiseStats stats) { 525 sb.append("\n └Advertising:"); 526 sb.append("\n └Interval(0.625ms) : ") 527 .append(stats.mInterval); 528 sb.append("\n └TX POWER(dbm) : ") 529 .append(stats.mTxPowerLevel); 530 sb.append("\n └Primary Phy : ") 531 .append(dumpPhyString(stats.mPrimaryPhy)); 532 sb.append("\n └Secondary Phy : ") 533 .append(dumpPhyString(stats.mSecondaryPhy)); 534 sb.append("\n └Legacy : ") 535 .append(stats.mLegacy); 536 sb.append("\n └Anonymous : ") 537 .append(stats.mAnonymous); 538 sb.append("\n └Connectable : ") 539 .append(stats.mConnectable); 540 sb.append("\n └Scannable : ") 541 .append(stats.mScannable); 542 543 if (stats.mAdvertisingData != null) { 544 sb.append("\n └Advertise Data:"); 545 dumpAppAdvertiserData(sb, stats.mAdvertisingData); 546 } 547 548 if (stats.mScanResponseData != null) { 549 sb.append("\n └Scan Response:"); 550 dumpAppAdvertiserData(sb, stats.mScanResponseData); 551 } 552 553 if (stats.mPeriodicInterval > 0) { 554 sb.append("\n └Periodic Advertising Enabled : ") 555 .append(stats.mPeriodicAdvertisingEnabled); 556 sb.append("\n └Periodic Include TxPower : ") 557 .append(stats.mPeriodicIncludeTxPower); 558 sb.append("\n └Periodic Interval(1.25ms) : ") 559 .append(stats.mPeriodicInterval); 560 } 561 562 if (stats.mPeriodicAdvertisingData != null) { 563 sb.append("\n └Periodic Advertise Data:"); 564 dumpAppAdvertiserData(sb, stats.mPeriodicAdvertisingData); 565 } 566 567 sb.append("\n"); 568 } 569 dumpToString(StringBuilder sb, AppAdvertiseStats stats)570 static void dumpToString(StringBuilder sb, AppAdvertiseStats stats) { 571 Instant currentTime = Instant.now(); 572 573 sb.append("\n ").append(stats.mAppName); 574 if (stats.mAttributionTag != null) { 575 sb.append("\n Tag : ") 576 .append(stats.mAttributionTag); 577 } 578 sb.append("\n Advertising ID : ").append(stats.mId); 579 for (int i = 0; i < stats.mAdvertiserRecords.size(); i++) { 580 AppAdvertiserRecord record = stats.mAdvertiserRecords.get(i); 581 582 sb.append("\n ").append((i + 1)).append(":"); 583 sb.append("\n └Start time : ") 584 .append(sDateFormat.format(record.startTime)); 585 if (record.stopTime == null) { 586 Duration timeElapsed = Duration.between(record.startTime, currentTime); 587 sb.append("\n └Elapsed time : ") 588 .append(timeElapsed.toMillis()) 589 .append("ms"); 590 } else { 591 sb.append("\n └Stop time : ") 592 .append(sDateFormat.format(record.stopTime)); 593 } 594 sb.append("\n └Duration(10ms unit) : ") 595 .append(record.duration); 596 sb.append("\n └Maximum number of extended advertising events : ") 597 .append(record.maxExtendedAdvertisingEvents); 598 if (record.appImportanceOnStart < IMPORTANCE_FOREGROUND_SERVICE) { 599 sb.append( 600 "\n" 601 + " └App Importance : higher" 602 + " than Foreground Service"); 603 } else if (record.appImportanceOnStart > IMPORTANCE_FOREGROUND_SERVICE) { 604 sb.append( 605 "\n" 606 + " └App Importance : lower than" 607 + " Foreground Service"); 608 } else { 609 sb.append( 610 "\n" 611 + " └App Importance : Foreground" 612 + " Service"); 613 } 614 } 615 616 dumpAppAdvertiseStats(sb, stats); 617 } 618 } 619