• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.bluetooth.le_scan;
18 
19 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
20 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
21 
22 import static java.util.Objects.requireNonNull;
23 import static java.util.Objects.requireNonNullElseGet;
24 
25 import android.annotation.Nullable;
26 import android.bluetooth.BluetoothProtoEnums;
27 import android.bluetooth.le.ScanFilter;
28 import android.bluetooth.le.ScanSettings;
29 import android.os.BatteryStatsManager;
30 import android.os.Binder;
31 import android.os.WorkSource;
32 
33 import com.android.bluetooth.BluetoothStatsLog;
34 import com.android.bluetooth.Utils.TimeProvider;
35 import com.android.bluetooth.btservice.AdapterService;
36 import com.android.bluetooth.btservice.MetricsLogger;
37 import com.android.bluetooth.util.WorkSourceUtil;
38 import com.android.internal.annotations.GuardedBy;
39 import com.android.internal.annotations.VisibleForTesting;
40 
41 import java.text.DateFormat;
42 import java.text.SimpleDateFormat;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.Date;
46 import java.util.HashMap;
47 import java.util.List;
48 import java.util.Locale;
49 import java.util.Map;
50 import java.util.Objects;
51 
52 /** ScanStats class helps keep track of information about scans on a per application basis. */
53 class AppScanStats {
54     private static final String TAG = AppScanStats.class.getSimpleName();
55 
56     // Weight is the duty cycle of the scan mode
57     static final int OPPORTUNISTIC_WEIGHT = 0;
58     static final int SCREEN_OFF_LOW_POWER_WEIGHT = 5;
59     static final int LOW_POWER_WEIGHT = 10;
60     static final int AMBIENT_DISCOVERY_WEIGHT = 25;
61     static final int BALANCED_WEIGHT = 25;
62     static final int LOW_LATENCY_WEIGHT = 100;
63 
64     static final int LARGE_SCAN_TIME_GAP_MS = 24000;
65 
66     static WorkSourceUtil sRadioScanWorkSourceUtil;
67     static int sRadioScanType;
68     static int sRadioScanMode;
69     static int sRadioScanWindowMs;
70     static int sRadioScanIntervalMs;
71     static boolean sIsRadioStarted = false;
72     static boolean sIsScreenOn = false;
73     static int sRadioScanAppImportance = IMPORTANCE_CACHED;
74     @Nullable static String sRadioScanAttributionTag;
75 
76     @GuardedBy("sLock")
77     static long sRadioStartTime = 0;
78 
79     private static final Object sLock = new Object();
80 
81     private static class LastScan {
82         public long duration;
83         public long suspendDuration;
84         public long suspendStartTime;
85         public boolean isSuspended;
86         public final long timestamp;
87         public final long reportDelayMillis;
88         public boolean isOpportunisticScan;
89         public boolean isTimeout;
90         public boolean isDowngraded;
91         public boolean isBackgroundScan;
92         public final boolean isFilterScan;
93         public final boolean isCallbackScan;
94         public boolean isBatchScan;
95         public boolean isAutoBatchScan;
96         public int results;
97         public final int scannerId;
98         public final int scanMode;
99         public final int scanCallbackType;
100         public final StringBuilder filterString;
101         @Nullable public final String attributionTag;
102         public final int appImportanceOnStart;
103 
LastScan( long timestamp, long reportDelayMillis, boolean isFilterScan, boolean isCallbackScan, int scannerId, int scanMode, int scanCallbackType, @Nullable String attributionTag, int appImportanceOnStart)104         LastScan(
105                 long timestamp,
106                 long reportDelayMillis,
107                 boolean isFilterScan,
108                 boolean isCallbackScan,
109                 int scannerId,
110                 int scanMode,
111                 int scanCallbackType,
112                 @Nullable String attributionTag,
113                 int appImportanceOnStart) {
114             this.duration = 0;
115             this.timestamp = timestamp;
116             this.reportDelayMillis = reportDelayMillis;
117             this.isOpportunisticScan = false;
118             this.isTimeout = false;
119             this.isDowngraded = false;
120             this.isBackgroundScan = false;
121             this.isFilterScan = isFilterScan;
122             this.isCallbackScan = isCallbackScan;
123             this.isBatchScan = false;
124             this.isAutoBatchScan = false;
125             this.scanMode = scanMode;
126             this.scanCallbackType = scanCallbackType;
127             this.attributionTag = attributionTag;
128             this.results = 0;
129             this.scannerId = scannerId;
130             this.suspendDuration = 0;
131             this.suspendStartTime = 0;
132             this.isSuspended = false;
133             this.appImportanceOnStart = appImportanceOnStart;
134             this.filterString = new StringBuilder();
135         }
136 
getAttributionTag()137         private String getAttributionTag() {
138             return attributionTag != null ? attributionTag : "";
139         }
140     }
141 
142     private final List<LastScan> mLastScans = new ArrayList<>();
143     private final Map<Integer, LastScan> mOngoingScans = new HashMap<>();
144 
145     final String mAppName;
146     final ScannerMap mScannerMap; // Used to grab Apps
147     final BatteryStatsManager mBatteryStatsManager; // Used to keep track of scans and result stats
148     final ScanController mScanController; // Used to add scan event protos to be dumped later
149 
150     private final WorkSource mWorkSource; // Used for BatteryStatsManager
151     private final WorkSourceUtil mWorkSourceUtil; // Used for BluetoothStatsLog
152     private final AdapterService mAdapterService;
153     private final TimeProvider mTimeProvider;
154 
155     public boolean isAppDead = false;
156     public boolean isRegistered = false;
157     private int mScansStarted = 0;
158     private int mScansStopped = 0;
159     private long mScanStartTime = 0;
160     private long mTotalActiveTime = 0;
161     private long mTotalSuspendTime = 0;
162     private long mTotalScanTime = 0;
163     private long mOppScanTime = 0;
164     private long mLowPowerScanTime = 0;
165     private long mBalancedScanTime = 0;
166     private long mLowLatencyScanTime = 0;
167     private long mAmbientDiscoveryScanTime = 0;
168     private int mOppScan = 0;
169     private int mLowPowerScan = 0;
170     private int mBalancedScan = 0;
171     private int mLowLatencyScan = 0;
172     private int mAmbientDiscoveryScan = 0;
173     private int mAppImportance = IMPORTANCE_CACHED;
174     private long startTime = 0;
175     private int results = 0;
176 
AppScanStats( String name, WorkSource source, ScannerMap map, AdapterService adapterService, ScanController scanController, TimeProvider timeProvider)177     AppScanStats(
178             String name,
179             WorkSource source,
180             ScannerMap map,
181             AdapterService adapterService,
182             ScanController scanController,
183             TimeProvider timeProvider) {
184         mAppName = name;
185         mWorkSource =
186                 requireNonNullElseGet(
187                         // Bill the caller if the work source isn't passed through
188                         source, () -> new WorkSource(Binder.getCallingUid(), mAppName));
189         mWorkSourceUtil = new WorkSourceUtil(mWorkSource);
190         mScannerMap = map;
191         mAdapterService = requireNonNull(adapterService);
192         mBatteryStatsManager = adapterService.getSystemService(BatteryStatsManager.class);
193         mScanController = scanController;
194         mTimeProvider = requireNonNull(timeProvider);
195     }
196 
197     @Nullable
getScanFromScannerId(int scannerId)198     private synchronized LastScan getScanFromScannerId(int scannerId) {
199         return mOngoingScans.get(scannerId);
200     }
201 
addResult(int scannerId)202     synchronized void addResult(int scannerId) {
203         LastScan scan = getScanFromScannerId(scannerId);
204         if (scan != null) {
205             scan.results++;
206 
207             // Only update battery stats after receiving 100 new results in order
208             // to lower the cost of the binder transaction
209             if (scan.results % 100 == 0) {
210                 mBatteryStatsManager.reportBleScanResults(mWorkSource, 100);
211                 BluetoothStatsLog.write(
212                         BluetoothStatsLog.BLE_SCAN_RESULT_RECEIVED,
213                         mWorkSourceUtil.getUids(),
214                         mWorkSourceUtil.getTags(),
215                         100);
216             }
217         }
218 
219         results++;
220     }
221 
isScanning()222     synchronized boolean isScanning() {
223         return !mOngoingScans.isEmpty();
224     }
225 
isScanTimeout(int scannerId)226     synchronized boolean isScanTimeout(int scannerId) {
227         LastScan scan = getScanFromScannerId(scannerId);
228         if (scan == null) {
229             return false;
230         }
231         return scan.isTimeout;
232     }
233 
isScanDowngraded(int scannerId)234     synchronized boolean isScanDowngraded(int scannerId) {
235         LastScan scan = getScanFromScannerId(scannerId);
236         if (scan == null) {
237             return false;
238         }
239         return scan.isDowngraded;
240     }
241 
isAutoBatchScan(int scannerId)242     synchronized boolean isAutoBatchScan(int scannerId) {
243         LastScan scan = getScanFromScannerId(scannerId);
244         if (scan == null) {
245             return false;
246         }
247         return scan.isAutoBatchScan;
248     }
249 
setAppImportance(int importance)250     synchronized void setAppImportance(int importance) {
251         mAppImportance = importance;
252     }
253 
recordScanStart( ScanSettings settings, List<ScanFilter> filters, boolean isFilterScan, boolean isCallbackScan, int scannerId, @Nullable String attributionTag)254     synchronized void recordScanStart(
255             ScanSettings settings,
256             List<ScanFilter> filters,
257             boolean isFilterScan,
258             boolean isCallbackScan,
259             int scannerId,
260             @Nullable String attributionTag) {
261         LastScan existingScan = getScanFromScannerId(scannerId);
262         if (existingScan != null) {
263             return;
264         }
265         this.mScansStarted++;
266         startTime = mTimeProvider.elapsedRealtime();
267 
268         LastScan scan =
269                 new LastScan(
270                         startTime,
271                         settings.getReportDelayMillis(),
272                         isFilterScan,
273                         isCallbackScan,
274                         scannerId,
275                         settings.getScanMode(),
276                         settings.getCallbackType(),
277                         attributionTag,
278                         mAppImportance);
279         if (settings != null) {
280             scan.isOpportunisticScan = scan.scanMode == ScanSettings.SCAN_MODE_OPPORTUNISTIC;
281             scan.isBackgroundScan =
282                     (scan.scanCallbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0;
283             scan.isBatchScan =
284                     settings.getCallbackType() == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
285                             && settings.getReportDelayMillis() != 0;
286             switch (scan.scanMode) {
287                 case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
288                     mOppScan++;
289                     break;
290                 case ScanSettings.SCAN_MODE_LOW_POWER:
291                     mLowPowerScan++;
292                     break;
293                 case ScanSettings.SCAN_MODE_BALANCED:
294                     mBalancedScan++;
295                     break;
296                 case ScanSettings.SCAN_MODE_LOW_LATENCY:
297                     mLowLatencyScan++;
298                     break;
299                 case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
300                     mAmbientDiscoveryScan++;
301                     break;
302             }
303         }
304 
305         if (isFilterScan) {
306             for (ScanFilter filter : filters) {
307                 scan.filterString
308                         .append("\n      └ ")
309                         .append(filterToStringWithoutNullParam(filter));
310             }
311         }
312 
313         if (!isScanning()) {
314             mScanStartTime = startTime;
315         }
316         boolean isUnoptimized =
317                 !(scan.isFilterScan || scan.isBackgroundScan || scan.isOpportunisticScan);
318         mBatteryStatsManager.reportBleScanStarted(mWorkSource, isUnoptimized);
319         BluetoothStatsLog.write(
320                 BluetoothStatsLog.BLE_SCAN_STATE_CHANGED,
321                 mWorkSourceUtil.getUids(),
322                 mWorkSourceUtil.getTags(),
323                 BluetoothStatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON,
324                 scan.isFilterScan,
325                 scan.isBackgroundScan,
326                 scan.isOpportunisticScan);
327         recordScanAppCountMetricsStart(scan);
328 
329         mOngoingScans.put(scannerId, scan);
330     }
331 
recordScanStop(int scannerId)332     synchronized void recordScanStop(int scannerId) {
333         LastScan scan = getScanFromScannerId(scannerId);
334         if (scan == null) {
335             return;
336         }
337         this.mScansStopped++;
338         long stopTime = mTimeProvider.elapsedRealtime();
339         long scanDuration = stopTime - scan.timestamp;
340         scan.duration = scanDuration;
341         if (scan.isSuspended) {
342             long suspendDuration = stopTime - scan.suspendStartTime;
343             scan.suspendDuration += suspendDuration;
344             mTotalSuspendTime += suspendDuration;
345         }
346         mOngoingScans.remove(scannerId);
347         if (mLastScans.size() >= mAdapterService.getScanQuotaCount()) {
348             mLastScans.remove(0);
349         }
350         mLastScans.add(scan);
351 
352         mTotalScanTime += scanDuration;
353         long activeDuration = scanDuration - scan.suspendDuration;
354         mTotalActiveTime += activeDuration;
355         switch (scan.scanMode) {
356             case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
357                 mOppScanTime += activeDuration;
358                 break;
359             case ScanSettings.SCAN_MODE_LOW_POWER:
360                 mLowPowerScanTime += activeDuration;
361                 break;
362             case ScanSettings.SCAN_MODE_BALANCED:
363                 mBalancedScanTime += activeDuration;
364                 break;
365             case ScanSettings.SCAN_MODE_LOW_LATENCY:
366                 mLowLatencyScanTime += activeDuration;
367                 break;
368             case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
369                 mAmbientDiscoveryScanTime += activeDuration;
370                 break;
371         }
372 
373         // Inform battery stats of any results it might be missing on scan stop
374         boolean isUnoptimized =
375                 !(scan.isFilterScan || scan.isBackgroundScan || scan.isOpportunisticScan);
376         mBatteryStatsManager.reportBleScanResults(mWorkSource, scan.results % 100);
377         mBatteryStatsManager.reportBleScanStopped(mWorkSource, isUnoptimized);
378         BluetoothStatsLog.write(
379                 BluetoothStatsLog.BLE_SCAN_RESULT_RECEIVED,
380                 mWorkSourceUtil.getUids(),
381                 mWorkSourceUtil.getTags(),
382                 scan.results % 100);
383         BluetoothStatsLog.write(
384                 BluetoothStatsLog.BLE_SCAN_STATE_CHANGED,
385                 mWorkSourceUtil.getUids(),
386                 mWorkSourceUtil.getTags(),
387                 BluetoothStatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF,
388                 scan.isFilterScan,
389                 scan.isBackgroundScan,
390                 scan.isOpportunisticScan);
391         recordScanAppCountMetricsStop(scan);
392     }
393 
recordScanAppCountMetricsStart(LastScan scan)394     private void recordScanAppCountMetricsStart(LastScan scan) {
395         MetricsLogger logger = MetricsLogger.getInstance();
396         logger.cacheCount(BluetoothProtoEnums.LE_SCAN_COUNT_TOTAL_ENABLE, 1);
397         logger.logAppScanStateChanged(
398                 mWorkSourceUtil.getUids(),
399                 mWorkSourceUtil.getTags(),
400                 true /* enabled */,
401                 scan.isFilterScan,
402                 scan.isCallbackScan,
403                 convertScanCallbackType(scan.scanCallbackType),
404                 convertScanType(scan),
405                 convertScanMode(scan.scanMode),
406                 scan.reportDelayMillis,
407                 0 /* app_scan_duration_ms */,
408                 mOngoingScans.size(),
409                 sIsScreenOn,
410                 isAppDead,
411                 mAppImportance,
412                 scan.getAttributionTag());
413         if (scan.isAutoBatchScan) {
414             logger.cacheCount(BluetoothProtoEnums.LE_SCAN_COUNT_AUTO_BATCH_ENABLE, 1);
415         } else if (scan.isBatchScan) {
416             logger.cacheCount(BluetoothProtoEnums.LE_SCAN_COUNT_BATCH_ENABLE, 1);
417         } else {
418             if (scan.isFilterScan) {
419                 logger.cacheCount(BluetoothProtoEnums.LE_SCAN_COUNT_FILTERED_ENABLE, 1);
420             } else {
421                 logger.cacheCount(BluetoothProtoEnums.LE_SCAN_COUNT_UNFILTERED_ENABLE, 1);
422             }
423         }
424     }
425 
recordScanAppCountMetricsStop(LastScan scan)426     private void recordScanAppCountMetricsStop(LastScan scan) {
427         MetricsLogger logger = MetricsLogger.getInstance();
428         logger.cacheCount(BluetoothProtoEnums.LE_SCAN_COUNT_TOTAL_DISABLE, 1);
429         logger.logAppScanStateChanged(
430                 mWorkSourceUtil.getUids(),
431                 mWorkSourceUtil.getTags(),
432                 false /* enabled */,
433                 scan.isFilterScan,
434                 scan.isCallbackScan,
435                 convertScanCallbackType(scan.scanCallbackType),
436                 convertScanType(scan),
437                 convertScanMode(scan.scanMode),
438                 scan.reportDelayMillis,
439                 scan.duration,
440                 mOngoingScans.size(),
441                 sIsScreenOn,
442                 isAppDead,
443                 mAppImportance,
444                 scan.getAttributionTag());
445         if (scan.isAutoBatchScan) {
446             logger.cacheCount(BluetoothProtoEnums.LE_SCAN_COUNT_AUTO_BATCH_DISABLE, 1);
447         } else if (scan.isBatchScan) {
448             logger.cacheCount(BluetoothProtoEnums.LE_SCAN_COUNT_BATCH_DISABLE, 1);
449         } else {
450             if (scan.isFilterScan) {
451                 logger.cacheCount(BluetoothProtoEnums.LE_SCAN_COUNT_FILTERED_DISABLE, 1);
452             } else {
453                 logger.cacheCount(BluetoothProtoEnums.LE_SCAN_COUNT_UNFILTERED_DISABLE, 1);
454             }
455         }
456     }
457 
convertScanCallbackType(int type)458     private static int convertScanCallbackType(int type) {
459         switch (type) {
460             case ScanSettings.CALLBACK_TYPE_ALL_MATCHES:
461                 return BluetoothStatsLog
462                         .LE_APP_SCAN_STATE_CHANGED__SCAN_CALLBACK_TYPE__TYPE_ALL_MATCHES;
463             case ScanSettings.CALLBACK_TYPE_FIRST_MATCH:
464                 return BluetoothStatsLog
465                         .LE_APP_SCAN_STATE_CHANGED__SCAN_CALLBACK_TYPE__TYPE_FIRST_MATCH;
466             case ScanSettings.CALLBACK_TYPE_MATCH_LOST:
467                 return BluetoothStatsLog
468                         .LE_APP_SCAN_STATE_CHANGED__SCAN_CALLBACK_TYPE__TYPE_MATCH_LOST;
469             case ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH:
470                 return BluetoothStatsLog
471                         .LE_APP_SCAN_STATE_CHANGED__SCAN_CALLBACK_TYPE__TYPE_ALL_MATCHES_AUTO_BATCH;
472             default:
473                 return BluetoothStatsLog
474                         .LE_APP_SCAN_STATE_CHANGED__SCAN_CALLBACK_TYPE__TYPE_UNKNOWN;
475         }
476     }
477 
convertScanType(LastScan scan)478     private static int convertScanType(LastScan scan) {
479         if (scan == null) {
480             return BluetoothStatsLog.LE_APP_SCAN_STATE_CHANGED__LE_SCAN_TYPE__SCAN_TYPE_UNKNOWN;
481         }
482         if (scan.isAutoBatchScan) {
483             return BluetoothStatsLog.LE_APP_SCAN_STATE_CHANGED__LE_SCAN_TYPE__SCAN_TYPE_AUTO_BATCH;
484         } else if (scan.isBatchScan) {
485             return BluetoothStatsLog.LE_APP_SCAN_STATE_CHANGED__LE_SCAN_TYPE__SCAN_TYPE_BATCH;
486         } else {
487             return BluetoothStatsLog.LE_APP_SCAN_STATE_CHANGED__LE_SCAN_TYPE__SCAN_TYPE_REGULAR;
488         }
489     }
490 
491     @VisibleForTesting
convertScanMode(int mode)492     static int convertScanMode(int mode) {
493         switch (mode) {
494             case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
495                 return BluetoothStatsLog
496                         .LE_APP_SCAN_STATE_CHANGED__LE_SCAN_MODE__SCAN_MODE_OPPORTUNISTIC;
497             case ScanSettings.SCAN_MODE_LOW_POWER:
498                 return BluetoothStatsLog
499                         .LE_APP_SCAN_STATE_CHANGED__LE_SCAN_MODE__SCAN_MODE_LOW_POWER;
500             case ScanSettings.SCAN_MODE_BALANCED:
501                 return BluetoothStatsLog
502                         .LE_APP_SCAN_STATE_CHANGED__LE_SCAN_MODE__SCAN_MODE_BALANCED;
503             case ScanSettings.SCAN_MODE_LOW_LATENCY:
504                 return BluetoothStatsLog
505                         .LE_APP_SCAN_STATE_CHANGED__LE_SCAN_MODE__SCAN_MODE_LOW_LATENCY;
506             case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
507                 return BluetoothStatsLog
508                         .LE_APP_SCAN_STATE_CHANGED__LE_SCAN_MODE__SCAN_MODE_AMBIENT_DISCOVERY;
509             case ScanSettings.SCAN_MODE_SCREEN_OFF:
510                 return BluetoothStatsLog
511                         .LE_APP_SCAN_STATE_CHANGED__LE_SCAN_MODE__SCAN_MODE_SCREEN_OFF;
512             case ScanSettings.SCAN_MODE_SCREEN_OFF_BALANCED:
513                 return BluetoothStatsLog
514                         .LE_APP_SCAN_STATE_CHANGED__LE_SCAN_MODE__SCAN_MODE_SCREEN_OFF_BALANCED;
515             default:
516                 return BluetoothStatsLog.LE_APP_SCAN_STATE_CHANGED__LE_SCAN_MODE__SCAN_MODE_UNKNOWN;
517         }
518     }
519 
recordScanTimeoutCountMetrics(int scannerId, long scanTimeoutMillis)520     synchronized void recordScanTimeoutCountMetrics(int scannerId, long scanTimeoutMillis) {
521         BluetoothStatsLog.write(
522                 BluetoothStatsLog.LE_SCAN_ABUSED,
523                 mWorkSourceUtil.getUids(),
524                 mWorkSourceUtil.getTags(),
525                 convertScanType(getScanFromScannerId(scannerId)),
526                 BluetoothStatsLog.LE_SCAN_ABUSED__LE_SCAN_ABUSE_REASON__REASON_SCAN_TIMEOUT,
527                 scanTimeoutMillis,
528                 getAttributionTagFromScannerId(scannerId));
529         MetricsLogger.getInstance()
530                 .cacheCount(BluetoothProtoEnums.LE_SCAN_ABUSE_COUNT_SCAN_TIMEOUT, 1);
531     }
532 
recordHwFilterNotAvailableCountMetrics( int scannerId, long numOfFilterSupported)533     synchronized void recordHwFilterNotAvailableCountMetrics(
534             int scannerId, long numOfFilterSupported) {
535         BluetoothStatsLog.write(
536                 BluetoothStatsLog.LE_SCAN_ABUSED,
537                 mWorkSourceUtil.getUids(),
538                 mWorkSourceUtil.getTags(),
539                 convertScanType(getScanFromScannerId(scannerId)),
540                 BluetoothStatsLog.LE_SCAN_ABUSED__LE_SCAN_ABUSE_REASON__REASON_HW_FILTER_NA,
541                 numOfFilterSupported,
542                 getAttributionTagFromScannerId(scannerId));
543         MetricsLogger.getInstance()
544                 .cacheCount(BluetoothProtoEnums.LE_SCAN_ABUSE_COUNT_HW_FILTER_NOT_AVAILABLE, 1);
545     }
546 
recordTrackingHwFilterNotAvailableCountMetrics( int scannerId, long numOfTrackableAdv)547     synchronized void recordTrackingHwFilterNotAvailableCountMetrics(
548             int scannerId, long numOfTrackableAdv) {
549         BluetoothStatsLog.write(
550                 BluetoothStatsLog.LE_SCAN_ABUSED,
551                 mWorkSourceUtil.getUids(),
552                 mWorkSourceUtil.getTags(),
553                 convertScanType(getScanFromScannerId(scannerId)),
554                 BluetoothStatsLog
555                         .LE_SCAN_ABUSED__LE_SCAN_ABUSE_REASON__REASON_TRACKING_HW_FILTER_NA,
556                 numOfTrackableAdv,
557                 getAttributionTagFromScannerId(scannerId));
558         MetricsLogger.getInstance()
559                 .cacheCount(
560                         BluetoothProtoEnums.LE_SCAN_ABUSE_COUNT_TRACKING_HW_FILTER_NOT_AVAILABLE,
561                         1);
562     }
563 
initScanRadioState()564     static void initScanRadioState() {
565         synchronized (sLock) {
566             sIsRadioStarted = false;
567         }
568     }
569 
recordScanRadioStart( int scanMode, int scannerId, AppScanStats stats, int scanWindowMs, int scanIntervalMs, TimeProvider timeProvider)570     static boolean recordScanRadioStart(
571             int scanMode,
572             int scannerId,
573             AppScanStats stats,
574             int scanWindowMs,
575             int scanIntervalMs,
576             TimeProvider timeProvider) {
577         synchronized (sLock) {
578             if (sIsRadioStarted) {
579                 return false;
580             }
581             sRadioStartTime = timeProvider.elapsedRealtime();
582             sRadioScanWorkSourceUtil = stats.mWorkSourceUtil;
583             sRadioScanType = convertScanType(stats.getScanFromScannerId(scannerId));
584             sRadioScanMode = scanMode;
585             sRadioScanWindowMs = scanWindowMs;
586             sRadioScanIntervalMs = scanIntervalMs;
587             sIsRadioStarted = true;
588             sRadioScanAppImportance = stats.mAppImportance;
589             sRadioScanAttributionTag = stats.getAttributionTagFromScannerId(scannerId);
590         }
591         return true;
592     }
593 
recordScanRadioStop(TimeProvider timeProvider)594     static boolean recordScanRadioStop(TimeProvider timeProvider) {
595         synchronized (sLock) {
596             if (!sIsRadioStarted) {
597                 return false;
598             }
599             recordScanRadioDurationMetrics(timeProvider);
600         }
601         return true;
602     }
603 
604     @GuardedBy("sLock")
recordScanRadioDurationMetrics(TimeProvider timeProvider)605     private static void recordScanRadioDurationMetrics(TimeProvider timeProvider) {
606         if (!sIsRadioStarted) {
607             return;
608         }
609         MetricsLogger logger = MetricsLogger.getInstance();
610         long currentTime = timeProvider.elapsedRealtime();
611         long radioScanDuration = currentTime - sRadioStartTime;
612         double scanWeight = getScanWeight(sRadioScanMode) * 0.01;
613         long weightedDuration = (long) (radioScanDuration * scanWeight);
614 
615         logger.logRadioScanStopped(
616                 getRadioScanUids(),
617                 getRadioScanTags(),
618                 sRadioScanType,
619                 convertScanMode(sRadioScanMode),
620                 sRadioScanIntervalMs,
621                 sRadioScanWindowMs,
622                 sIsScreenOn,
623                 radioScanDuration,
624                 sRadioScanAppImportance,
625                 getRadioScanAttributionTag());
626         sRadioStartTime = 0;
627         sIsRadioStarted = false;
628         if (weightedDuration > 0) {
629             logger.cacheCount(BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR, weightedDuration);
630             if (sIsScreenOn) {
631                 logger.cacheCount(
632                         BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_ON,
633                         weightedDuration);
634             } else {
635                 logger.cacheCount(
636                         BluetoothProtoEnums.LE_SCAN_RADIO_DURATION_REGULAR_SCREEN_OFF,
637                         weightedDuration);
638             }
639         }
640     }
641 
getRadioScanUids()642     private static int[] getRadioScanUids() {
643         synchronized (sLock) {
644             return sRadioScanWorkSourceUtil != null
645                     ? sRadioScanWorkSourceUtil.getUids()
646                     : new int[] {0};
647         }
648     }
649 
getRadioScanTags()650     private static String[] getRadioScanTags() {
651         synchronized (sLock) {
652             return sRadioScanWorkSourceUtil != null
653                     ? sRadioScanWorkSourceUtil.getTags()
654                     : new String[] {""};
655         }
656     }
657 
getRadioScanAttributionTag()658     private static String getRadioScanAttributionTag() {
659         synchronized (sLock) {
660             return sRadioScanAttributionTag != null ? sRadioScanAttributionTag : "";
661         }
662     }
663 
664     @GuardedBy("sLock")
recordScreenOnOffMetrics(boolean isScreenOn)665     private static void recordScreenOnOffMetrics(boolean isScreenOn) {
666         if (isScreenOn) {
667             MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.SCREEN_ON_EVENT, 1);
668         } else {
669             MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.SCREEN_OFF_EVENT, 1);
670         }
671     }
672 
getScanWeight(int scanMode)673     private static int getScanWeight(int scanMode) {
674         switch (scanMode) {
675             case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
676                 return OPPORTUNISTIC_WEIGHT;
677             case ScanSettings.SCAN_MODE_SCREEN_OFF:
678                 return SCREEN_OFF_LOW_POWER_WEIGHT;
679             case ScanSettings.SCAN_MODE_LOW_POWER:
680                 return LOW_POWER_WEIGHT;
681             case ScanSettings.SCAN_MODE_BALANCED:
682             case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
683             case ScanSettings.SCAN_MODE_SCREEN_OFF_BALANCED:
684                 return BALANCED_WEIGHT;
685             case ScanSettings.SCAN_MODE_LOW_LATENCY:
686                 return LOW_LATENCY_WEIGHT;
687             default:
688                 return LOW_POWER_WEIGHT;
689         }
690     }
691 
recordScanRadioResultCount()692     static void recordScanRadioResultCount() {
693         synchronized (sLock) {
694             if (!sIsRadioStarted) {
695                 return;
696             }
697             BluetoothStatsLog.write(
698                     BluetoothStatsLog.LE_SCAN_RESULT_RECEIVED,
699                     getRadioScanUids(),
700                     getRadioScanTags(),
701                     1 /* num_results */,
702                     BluetoothStatsLog.LE_SCAN_RESULT_RECEIVED__LE_SCAN_TYPE__SCAN_TYPE_REGULAR,
703                     sIsScreenOn,
704                     getRadioScanAttributionTag());
705             MetricsLogger logger = MetricsLogger.getInstance();
706             logger.cacheCount(BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_REGULAR, 1);
707             if (sIsScreenOn) {
708                 logger.cacheCount(BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_REGULAR_SCREEN_ON, 1);
709             } else {
710                 logger.cacheCount(BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_REGULAR_SCREEN_OFF, 1);
711             }
712         }
713     }
714 
recordBatchScanRadioResultCount(int numRecords)715     static void recordBatchScanRadioResultCount(int numRecords) {
716         boolean isScreenOn;
717         synchronized (sLock) {
718             isScreenOn = sIsScreenOn;
719         }
720         BluetoothStatsLog.write(
721                 BluetoothStatsLog.LE_SCAN_RESULT_RECEIVED,
722                 getRadioScanUids(),
723                 getRadioScanTags(),
724                 numRecords,
725                 BluetoothStatsLog.LE_SCAN_RESULT_RECEIVED__LE_SCAN_TYPE__SCAN_TYPE_BATCH,
726                 sIsScreenOn,
727                 getRadioScanAttributionTag());
728         MetricsLogger logger = MetricsLogger.getInstance();
729         logger.cacheCount(BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_BATCH_BUNDLE, 1);
730         logger.cacheCount(BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_BATCH, numRecords);
731         if (isScreenOn) {
732             logger.cacheCount(BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_BATCH_BUNDLE_SCREEN_ON, 1);
733             logger.cacheCount(
734                     BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_BATCH_SCREEN_ON, numRecords);
735         } else {
736             logger.cacheCount(BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_BATCH_BUNDLE_SCREEN_OFF, 1);
737             logger.cacheCount(
738                     BluetoothProtoEnums.LE_SCAN_RESULTS_COUNT_BATCH_SCREEN_OFF, numRecords);
739         }
740     }
741 
setScreenState(boolean isScreenOn, TimeProvider timeProvider)742     static void setScreenState(boolean isScreenOn, TimeProvider timeProvider) {
743         synchronized (sLock) {
744             if (sIsScreenOn == isScreenOn) {
745                 return;
746             }
747             if (sIsRadioStarted) {
748                 recordScanRadioDurationMetrics(timeProvider);
749                 sRadioStartTime = timeProvider.elapsedRealtime();
750             }
751             recordScreenOnOffMetrics(isScreenOn);
752             sIsScreenOn = isScreenOn;
753         }
754     }
755 
recordScanSuspend(int scannerId)756     synchronized void recordScanSuspend(int scannerId) {
757         LastScan scan = getScanFromScannerId(scannerId);
758         if (scan == null || scan.isSuspended) {
759             return;
760         }
761         scan.suspendStartTime = mTimeProvider.elapsedRealtime();
762         scan.isSuspended = true;
763     }
764 
recordScanResume(int scannerId)765     synchronized void recordScanResume(int scannerId) {
766         LastScan scan = getScanFromScannerId(scannerId);
767         if (scan == null || !scan.isSuspended) {
768             return;
769         }
770         scan.isSuspended = false;
771         long stopTime = mTimeProvider.elapsedRealtime();
772         long suspendDuration = stopTime - scan.suspendStartTime;
773         scan.suspendDuration += suspendDuration;
774         mTotalSuspendTime += suspendDuration;
775     }
776 
setScanTimeout(int scannerId)777     synchronized void setScanTimeout(int scannerId) {
778         if (!isScanning()) {
779             return;
780         }
781 
782         LastScan scan = getScanFromScannerId(scannerId);
783         if (scan != null) {
784             scan.isTimeout = true;
785         }
786     }
787 
setScanDowngrade(int scannerId, boolean isDowngrade)788     synchronized void setScanDowngrade(int scannerId, boolean isDowngrade) {
789         if (!isScanning()) {
790             return;
791         }
792 
793         LastScan scan = getScanFromScannerId(scannerId);
794         if (scan != null) {
795             scan.isDowngraded = isDowngrade;
796         }
797     }
798 
setAutoBatchScan(int scannerId, boolean isBatchScan)799     synchronized void setAutoBatchScan(int scannerId, boolean isBatchScan) {
800         LastScan scan = getScanFromScannerId(scannerId);
801         if (scan != null) {
802             scan.isAutoBatchScan = isBatchScan;
803         }
804     }
805 
isScanningTooFrequently()806     synchronized boolean isScanningTooFrequently() {
807         if (mLastScans.size() < mAdapterService.getScanQuotaCount()) {
808             return false;
809         }
810 
811         return (mTimeProvider.elapsedRealtime() - mLastScans.get(0).timestamp)
812                 < mAdapterService.getScanQuotaWindowMillis();
813     }
814 
isScanningTooLong()815     synchronized boolean isScanningTooLong() {
816         if (!isScanning()) {
817             return false;
818         }
819         return (mTimeProvider.elapsedRealtime() - mScanStartTime)
820                 >= mAdapterService.getScanTimeoutMillis();
821     }
822 
hasRecentScan()823     synchronized boolean hasRecentScan() {
824         if (!isScanning() || mLastScans.isEmpty()) {
825             return false;
826         }
827         LastScan lastScan = mLastScans.get(mLastScans.size() - 1);
828         return ((mTimeProvider.elapsedRealtime() - lastScan.duration - lastScan.timestamp)
829                 < LARGE_SCAN_TIME_GAP_MS);
830     }
831 
getAttributionTagFromScannerId(int scannerId)832     private String getAttributionTagFromScannerId(int scannerId) {
833         LastScan scan = getScanFromScannerId(scannerId);
834         return scan == null ? "" : scan.getAttributionTag();
835     }
836 
filterToStringWithoutNullParam(ScanFilter filter)837     private static String filterToStringWithoutNullParam(ScanFilter filter) {
838         StringBuilder filterString = new StringBuilder("BluetoothLeScanFilter [");
839         if (filter.getDeviceName() != null) {
840             filterString.append(" DeviceName=").append(filter.getDeviceName());
841         }
842         if (filter.getDeviceAddress() != null) {
843             filterString.append(" DeviceAddress=").append(filter.getDeviceAddress());
844         }
845         if (filter.getServiceUuid() != null) {
846             filterString.append(" ServiceUuid=").append(filter.getServiceUuid());
847         }
848         if (filter.getServiceUuidMask() != null) {
849             filterString.append(" ServiceUuidMask=").append(filter.getServiceUuidMask());
850         }
851         if (filter.getServiceSolicitationUuid() != null) {
852             filterString
853                     .append(" ServiceSolicitationUuid=")
854                     .append(filter.getServiceSolicitationUuid());
855         }
856         if (filter.getServiceSolicitationUuidMask() != null) {
857             filterString
858                     .append(" ServiceSolicitationUuidMask=")
859                     .append(filter.getServiceSolicitationUuidMask());
860         }
861         if (filter.getServiceDataUuid() != null) {
862             filterString
863                     .append(" ServiceDataUuid=")
864                     .append(Objects.toString(filter.getServiceDataUuid()));
865         }
866         if (filter.getServiceData() != null) {
867             filterString.append(" ServiceData=").append(Arrays.toString(filter.getServiceData()));
868         }
869         if (filter.getServiceDataMask() != null) {
870             filterString
871                     .append(" ServiceDataMask=")
872                     .append(Arrays.toString(filter.getServiceDataMask()));
873         }
874         if (filter.getManufacturerId() >= 0) {
875             filterString.append(" ManufacturerId=").append(filter.getManufacturerId());
876         }
877         if (filter.getManufacturerData() != null) {
878             filterString
879                     .append(" ManufacturerData=")
880                     .append(Arrays.toString(filter.getManufacturerData()));
881         }
882         if (filter.getManufacturerDataMask() != null) {
883             filterString
884                     .append(" ManufacturerDataMask=")
885                     .append(Arrays.toString(filter.getManufacturerDataMask()));
886         }
887         filterString.append(" ]");
888         return filterString.toString();
889     }
890 
scanModeToString(int scanMode)891     private static String scanModeToString(int scanMode) {
892         switch (scanMode) {
893             case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
894                 return "OPPORTUNISTIC";
895             case ScanSettings.SCAN_MODE_LOW_LATENCY:
896                 return "LOW_LATENCY";
897             case ScanSettings.SCAN_MODE_BALANCED:
898                 return "BALANCED";
899             case ScanSettings.SCAN_MODE_LOW_POWER:
900                 return "LOW_POWER";
901             case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
902                 return "AMBIENT_DISCOVERY";
903             default:
904                 return "UNKNOWN(" + scanMode + ")";
905         }
906     }
907 
callbackTypeToString(int callbackType)908     private static String callbackTypeToString(int callbackType) {
909         switch (callbackType) {
910             case ScanSettings.CALLBACK_TYPE_ALL_MATCHES:
911                 return "ALL_MATCHES";
912             case ScanSettings.CALLBACK_TYPE_FIRST_MATCH:
913                 return "FIRST_MATCH";
914             case ScanSettings.CALLBACK_TYPE_MATCH_LOST:
915                 return "LOST";
916             case ScanSettings.CALLBACK_TYPE_ALL_MATCHES_AUTO_BATCH:
917                 return "ALL_MATCHES_AUTO_BATCH";
918             default:
919                 return callbackType
920                                 == (ScanSettings.CALLBACK_TYPE_FIRST_MATCH
921                                         | ScanSettings.CALLBACK_TYPE_MATCH_LOST)
922                         ? "[FIRST_MATCH | LOST]"
923                         : "UNKNOWN: " + callbackType;
924         }
925     }
926 
927     @SuppressWarnings("JavaUtilDate") // TODO: b/365629730 -- prefer Instant or LocalDate
dumpToString(StringBuilder sb)928     public synchronized void dumpToString(StringBuilder sb) {
929         DateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm:ss", Locale.ROOT);
930 
931         long currentTime = System.currentTimeMillis();
932         long currTime = mTimeProvider.elapsedRealtime();
933         long scanDuration = 0;
934         long suspendDuration = 0;
935         long activeDuration = 0;
936         long totalActiveTime = mTotalActiveTime;
937         long totalSuspendTime = mTotalSuspendTime;
938         long totalScanTime = mTotalScanTime;
939         long oppScanTime = mOppScanTime;
940         long lowPowerScanTime = mLowPowerScanTime;
941         long balancedScanTime = mBalancedScanTime;
942         long lowLatencyScanTime = mLowLatencyScanTime;
943         long ambientDiscoveryScanTime = mAmbientDiscoveryScanTime;
944         int oppScan = mOppScan;
945         int lowPowerScan = mLowPowerScan;
946         int balancedScan = mBalancedScan;
947         int lowLatencyScan = mLowLatencyScan;
948         long ambientDiscoveryScan = mAmbientDiscoveryScan;
949 
950         for (LastScan scan : mOngoingScans.values()) {
951             scanDuration = currTime - scan.timestamp;
952 
953             if (scan.isSuspended) {
954                 suspendDuration = currTime - scan.suspendStartTime;
955                 totalSuspendTime += suspendDuration;
956             }
957 
958             totalScanTime += scanDuration;
959             totalSuspendTime += suspendDuration;
960             activeDuration = scanDuration - scan.suspendDuration - suspendDuration;
961             totalActiveTime += activeDuration;
962             switch (scan.scanMode) {
963                 case ScanSettings.SCAN_MODE_OPPORTUNISTIC:
964                     oppScanTime += activeDuration;
965                     break;
966                 case ScanSettings.SCAN_MODE_LOW_POWER:
967                     lowPowerScanTime += activeDuration;
968                     break;
969                 case ScanSettings.SCAN_MODE_BALANCED:
970                     balancedScanTime += activeDuration;
971                     break;
972                 case ScanSettings.SCAN_MODE_LOW_LATENCY:
973                     lowLatencyScanTime += activeDuration;
974                     break;
975                 case ScanSettings.SCAN_MODE_AMBIENT_DISCOVERY:
976                     ambientDiscoveryScan += activeDuration;
977                     break;
978             }
979         }
980 
981         long Score =
982                 (oppScanTime * OPPORTUNISTIC_WEIGHT
983                                 + lowPowerScanTime * LOW_POWER_WEIGHT
984                                 + balancedScanTime * BALANCED_WEIGHT
985                                 + lowLatencyScanTime * LOW_LATENCY_WEIGHT
986                                 + ambientDiscoveryScanTime * AMBIENT_DISCOVERY_WEIGHT)
987                         / 100;
988 
989         sb.append("  ").append(mAppName);
990         if (isRegistered) {
991             sb.append(" (Registered)");
992         }
993 
994         sb.append("\n  LE scans (started/stopped)                                  : ")
995                 .append(mScansStarted)
996                 .append(" / ")
997                 .append(mScansStopped);
998         sb.append("\n  Scan time in ms (active/suspend/total)                      : ")
999                 .append(totalActiveTime)
1000                 .append(" / ")
1001                 .append(totalSuspendTime)
1002                 .append(" / ")
1003                 .append(totalScanTime);
1004         sb.append("\n  Scan time with mode in ms ")
1005                 .append("(Opp/LowPower/Balanced/LowLatency/AmbientDiscovery):")
1006                 .append(oppScanTime)
1007                 .append(" / ")
1008                 .append(lowPowerScanTime)
1009                 .append(" / ")
1010                 .append(balancedScanTime)
1011                 .append(" / ")
1012                 .append(lowLatencyScanTime)
1013                 .append(" / ")
1014                 .append(ambientDiscoveryScanTime);
1015         sb.append("\n  Scan mode counter (Opp/LowPower/Balanced/LowLatency/AmbientDiscovery):")
1016                 .append(oppScan)
1017                 .append(" / ")
1018                 .append(lowPowerScan)
1019                 .append(" / ")
1020                 .append(balancedScan)
1021                 .append(" / ")
1022                 .append(lowLatencyScan)
1023                 .append(" / ")
1024                 .append(ambientDiscoveryScan);
1025         sb.append("\n  Score                                                       : ")
1026                 .append(Score);
1027         sb.append("\n  Total number of results                                     : ")
1028                 .append(results);
1029 
1030         if (!mLastScans.isEmpty()) {
1031             sb.append("\n  Last ")
1032                     .append(mLastScans.size())
1033                     .append(" scans                                                :");
1034 
1035             for (int i = 0; i < mLastScans.size(); i++) {
1036                 LastScan scan = mLastScans.get(i);
1037                 Date timestamp = new Date(currentTime - currTime + scan.timestamp);
1038                 sb.append("\n    ").append(dateFormat.format(timestamp)).append(" - ");
1039                 sb.append(scan.duration).append("ms ");
1040                 if (scan.isOpportunisticScan) {
1041                     sb.append("Opp ");
1042                 }
1043                 if (scan.isBackgroundScan) {
1044                     sb.append("Back ");
1045                 }
1046                 if (scan.isTimeout) {
1047                     sb.append("Forced ");
1048                 }
1049                 if (scan.isFilterScan) {
1050                     sb.append("Filter ");
1051                 }
1052                 sb.append(scan.results).append(" results");
1053                 sb.append(" (").append(scan.scannerId).append(") ");
1054                 if (scan.attributionTag != null) {
1055                     sb.append(" [").append(scan.attributionTag).append("] ");
1056                 }
1057                 if (scan.isCallbackScan) {
1058                     sb.append("CB ");
1059                 } else {
1060                     sb.append("PI ");
1061                 }
1062                 if (scan.isBatchScan) {
1063                     sb.append("Batch Scan");
1064                 } else if (scan.isAutoBatchScan) {
1065                     sb.append("Auto Batch Scan");
1066                 } else {
1067                     sb.append("Regular Scan");
1068                 }
1069                 if (scan.appImportanceOnStart < IMPORTANCE_FOREGROUND_SERVICE) {
1070                     sb.append("\n      └ ")
1071                             .append("App Importance: higher than Foreground Service");
1072                 } else if (scan.appImportanceOnStart > IMPORTANCE_FOREGROUND_SERVICE) {
1073                     sb.append("\n      └ ").append("App Importance: lower than Foreground Service");
1074                 } else {
1075                     sb.append("\n      └ ").append("App Importance: Foreground Service");
1076                 }
1077                 if (scan.suspendDuration != 0) {
1078                     activeDuration = scan.duration - scan.suspendDuration;
1079                     sb.append("\n      └ ")
1080                             .append("Suspended Time: ")
1081                             .append(scan.suspendDuration)
1082                             .append("ms, Active Time: ")
1083                             .append(activeDuration);
1084                 }
1085                 sb.append("\n      └ ")
1086                         .append("Scan Config: [ ScanMode=")
1087                         .append(scanModeToString(scan.scanMode))
1088                         .append(", callbackType=")
1089                         .append(callbackTypeToString(scan.scanCallbackType))
1090                         .append(" ]");
1091                 if (scan.isFilterScan) {
1092                     sb.append(scan.filterString);
1093                 }
1094             }
1095         }
1096 
1097         if (!mOngoingScans.isEmpty()) {
1098             sb.append("\n  Ongoing scans                                               :");
1099             for (LastScan scan : mOngoingScans.values()) {
1100                 Date timestamp = new Date(currentTime - currTime + scan.timestamp);
1101                 sb.append("\n    ").append(dateFormat.format(timestamp)).append(" - ");
1102                 sb.append((currTime - scan.timestamp)).append("ms ");
1103                 if (scan.isOpportunisticScan) {
1104                     sb.append("Opp ");
1105                 }
1106                 if (scan.isBackgroundScan) {
1107                     sb.append("Back ");
1108                 }
1109                 if (scan.isTimeout) {
1110                     sb.append("Forced ");
1111                 }
1112                 if (scan.isFilterScan) {
1113                     sb.append("Filter ");
1114                 }
1115                 if (scan.isSuspended) {
1116                     sb.append("Suspended ");
1117                 }
1118                 sb.append(scan.results).append(" results");
1119                 sb.append(" (").append(scan.scannerId).append(") ");
1120                 if (scan.isCallbackScan) {
1121                     sb.append("CB ");
1122                 } else {
1123                     sb.append("PI ");
1124                 }
1125                 if (scan.isBatchScan) {
1126                     sb.append("Batch Scan");
1127                 } else if (scan.isAutoBatchScan) {
1128                     sb.append("Auto Batch Scan");
1129                 } else {
1130                     sb.append("Regular Scan");
1131                 }
1132                 if (scan.suspendStartTime != 0) {
1133                     activeDuration = scan.duration - scan.suspendDuration;
1134                     sb.append("\n      └ ")
1135                             .append("Suspended Time:")
1136                             .append(scan.suspendDuration)
1137                             .append("ms, Active Time:")
1138                             .append(activeDuration);
1139                 }
1140                 sb.append("\n      └ ")
1141                         .append("Scan Config: [ ScanMode=")
1142                         .append(scanModeToString(scan.scanMode))
1143                         .append(", callbackType=")
1144                         .append(callbackTypeToString(scan.scanCallbackType))
1145                         .append(" ]");
1146                 if (scan.isFilterScan) {
1147                     sb.append(scan.filterString);
1148                 }
1149             }
1150         }
1151 
1152         if (isRegistered) {
1153             List<ScannerMap.ScannerApp> appEntries = mScannerMap.getByName(mAppName);
1154             for (ScannerMap.ScannerApp appEntry : appEntries) {
1155                 sb.append("\n  Application ID: ").append(appEntry.mId);
1156                 sb.append(", UUID: ").append(appEntry.mUuid);
1157                 if (appEntry.mAttributionTag != null) {
1158                     sb.append(", Tag: ").append(appEntry.mAttributionTag);
1159                 }
1160             }
1161         }
1162         sb.append("\n\n");
1163     }
1164 }
1165