• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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