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