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.server.wifi;
18 
19 import android.net.wifi.ScanResult;
20 import android.net.wifi.WifiConfiguration;
21 import android.util.Base64;
22 import android.util.Log;
23 import android.util.SparseIntArray;
24 
25 import com.android.server.wifi.hotspot2.NetworkDetail;
26 import com.android.server.wifi.util.InformationElementUtil;
27 
28 import java.io.FileDescriptor;
29 import java.io.PrintWriter;
30 import java.util.ArrayList;
31 import java.util.Calendar;
32 import java.util.List;
33 
34 /**
35  * Provides storage for wireless connectivity metrics, as they are generated.
36  * Metrics logged by this class include:
37  *   Aggregated connection stats (num of connections, num of failures, ...)
38  *   Discrete connection event stats (time, duration, failure codes, ...)
39  *   Router details (technology type, authentication type, ...)
40  *   Scan stats
41  */
42 public class WifiMetrics {
43     private static final String TAG = "WifiMetrics";
44     private static final boolean DBG = false;
45     private final Object mLock = new Object();
46     private static final int MAX_CONNECTION_EVENTS = 256;
47     private Clock mClock;
48     private boolean mScreenOn;
49     private int mWifiState;
50     /**
51      * Metrics are stored within an instance of the WifiLog proto during runtime,
52      * The ConnectionEvent, SystemStateEntries & ScanReturnEntries metrics are stored during
53      * runtime in member lists of this WifiMetrics class, with the final WifiLog proto being pieced
54      * together at dump-time
55      */
56     private final WifiMetricsProto.WifiLog mWifiLogProto;
57     /**
58      * Session information that gets logged for every Wifi connection attempt.
59      */
60     private final List<ConnectionEvent> mConnectionEventList;
61     /**
62      * The latest started (but un-ended) connection attempt
63      */
64     private ConnectionEvent mCurrentConnectionEvent;
65     /**
66      * Count of number of times each scan return code, indexed by WifiLog.ScanReturnCode
67      */
68     private SparseIntArray mScanReturnEntries;
69     /**
70      * Mapping of system state to the counts of scans requested in that wifi state * screenOn
71      * combination. Indexed by WifiLog.WifiState * (1 + screenOn)
72      */
73     private SparseIntArray mWifiSystemStateEntries;
74     /**
75      * Records the elapsedRealtime (in seconds) that represents the beginning of data
76      * capture for for this WifiMetricsProto
77      */
78     private long mRecordStartTimeSec;
79 
80     class RouterFingerPrint {
81         private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
RouterFingerPrint()82         RouterFingerPrint() {
83             mRouterFingerPrintProto = new WifiMetricsProto.RouterFingerPrint();
84         }
85 
toString()86         public String toString() {
87             StringBuilder sb = new StringBuilder();
88             synchronized (mLock) {
89                 sb.append("mConnectionEvent.roamType=" + mRouterFingerPrintProto.roamType);
90                 sb.append(", mChannelInfo=" + mRouterFingerPrintProto.channelInfo);
91                 sb.append(", mDtim=" + mRouterFingerPrintProto.dtim);
92                 sb.append(", mAuthentication=" + mRouterFingerPrintProto.authentication);
93                 sb.append(", mHidden=" + mRouterFingerPrintProto.hidden);
94                 sb.append(", mRouterTechnology=" + mRouterFingerPrintProto.routerTechnology);
95                 sb.append(", mSupportsIpv6=" + mRouterFingerPrintProto.supportsIpv6);
96             }
97             return sb.toString();
98         }
updateFromWifiConfiguration(WifiConfiguration config)99         public void updateFromWifiConfiguration(WifiConfiguration config) {
100             synchronized (mLock) {
101                 if (config != null) {
102                     // Is this a hidden network
103                     mRouterFingerPrintProto.hidden = config.hiddenSSID;
104                     // Config may not have a valid dtimInterval set yet, in which case dtim will be zero
105                     // (These are only populated from beacon frame scan results, which are returned as
106                     // scan results from the chip far less frequently than Probe-responses)
107                     if (config.dtimInterval > 0) {
108                         mRouterFingerPrintProto.dtim = config.dtimInterval;
109                     }
110                     mCurrentConnectionEvent.mConfigSsid = config.SSID;
111                     // Get AuthType information from config (We do this again from ScanResult after
112                     // associating with BSSID)
113                     if (config.allowedKeyManagement != null
114                             && config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
115                         mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
116                                 .authentication = WifiMetricsProto.RouterFingerPrint.AUTH_OPEN;
117                     } else if (config.isEnterprise()) {
118                         mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
119                                 .authentication = WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE;
120                     } else {
121                         mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
122                                 .authentication = WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL;
123                     }
124                     // If there's a ScanResult candidate associated with this config already, get it and
125                     // log (more accurate) metrics from it
126                     ScanResult candidate = config.getNetworkSelectionStatus().getCandidate();
127                     if (candidate != null) {
128                         updateMetricsFromScanResult(candidate);
129                     }
130                 }
131             }
132         }
133     }
134 
135     /**
136      * Log event, tracking the start time, end time and result of a wireless connection attempt.
137      */
138     class ConnectionEvent {
139         WifiMetricsProto.ConnectionEvent mConnectionEvent;
140         //<TODO> Move these constants into a wifi.proto Enum, and create a new Failure Type field
141         //covering more than just l2 failures. see b/27652362
142         /**
143          * Failure codes, used for the 'level_2_failure_code' Connection event field (covers a lot
144          * more failures than just l2 though, since the proto does not have a place to log
145          * framework failures)
146          */
147         // Failure is unknown
148         public static final int FAILURE_UNKNOWN = 0;
149         // NONE
150         public static final int FAILURE_NONE = 1;
151         // ASSOCIATION_REJECTION_EVENT
152         public static final int FAILURE_ASSOCIATION_REJECTION = 2;
153         // AUTHENTICATION_FAILURE_EVENT
154         public static final int FAILURE_AUTHENTICATION_FAILURE = 3;
155         // SSID_TEMP_DISABLED (Also Auth failure)
156         public static final int FAILURE_SSID_TEMP_DISABLED = 4;
157         // reconnect() or reassociate() call to WifiNative failed
158         public static final int FAILURE_CONNECT_NETWORK_FAILED = 5;
159         // NETWORK_DISCONNECTION_EVENT
160         public static final int FAILURE_NETWORK_DISCONNECTION = 6;
161         // NEW_CONNECTION_ATTEMPT before previous finished
162         public static final int FAILURE_NEW_CONNECTION_ATTEMPT = 7;
163         // New connection attempt to the same network & bssid
164         public static final int FAILURE_REDUNDANT_CONNECTION_ATTEMPT = 8;
165         // Roam Watchdog timer triggered (Roaming timed out)
166         public static final int FAILURE_ROAM_TIMEOUT = 9;
167         // DHCP failure
168         public static final int FAILURE_DHCP = 10;
169 
170         RouterFingerPrint mRouterFingerPrint;
171         private long mRealStartTime;
172         private long mRealEndTime;
173         private String mConfigSsid;
174         private String mConfigBssid;
175         private int mWifiState;
176         private boolean mScreenOn;
177 
ConnectionEvent()178         private ConnectionEvent() {
179             mConnectionEvent = new WifiMetricsProto.ConnectionEvent();
180             mRealEndTime = 0;
181             mRealStartTime = 0;
182             mRouterFingerPrint = new RouterFingerPrint();
183             mConnectionEvent.routerFingerprint = mRouterFingerPrint.mRouterFingerPrintProto;
184             mConfigSsid = "<NULL>";
185             mConfigBssid = "<NULL>";
186             mWifiState = WifiMetricsProto.WifiLog.WIFI_UNKNOWN;
187             mScreenOn = false;
188         }
189 
toString()190         public String toString() {
191             StringBuilder sb = new StringBuilder();
192             sb.append("startTime=");
193             Calendar c = Calendar.getInstance();
194             synchronized (mLock) {
195                 c.setTimeInMillis(mConnectionEvent.startTimeMillis);
196                 sb.append(mConnectionEvent.startTimeMillis == 0 ? "            <null>" :
197                         String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
198                 sb.append(", SSID=");
199                 sb.append(mConfigSsid);
200                 sb.append(", BSSID=");
201                 sb.append(mConfigBssid);
202                 sb.append(", durationMillis=");
203                 sb.append(mConnectionEvent.durationTakenToConnectMillis);
204                 sb.append(", roamType=");
205                 switch(mConnectionEvent.roamType) {
206                     case 1:
207                         sb.append("ROAM_NONE");
208                         break;
209                     case 2:
210                         sb.append("ROAM_DBDC");
211                         break;
212                     case 3:
213                         sb.append("ROAM_ENTERPRISE");
214                         break;
215                     case 4:
216                         sb.append("ROAM_USER_SELECTED");
217                         break;
218                     case 5:
219                         sb.append("ROAM_UNRELATED");
220                         break;
221                     default:
222                         sb.append("ROAM_UNKNOWN");
223                 }
224                 sb.append(", connectionResult=");
225                 sb.append(mConnectionEvent.connectionResult);
226                 sb.append(", level2FailureCode=");
227                 switch(mConnectionEvent.level2FailureCode) {
228                     case FAILURE_NONE:
229                         sb.append("NONE");
230                         break;
231                     case FAILURE_ASSOCIATION_REJECTION:
232                         sb.append("ASSOCIATION_REJECTION");
233                         break;
234                     case FAILURE_AUTHENTICATION_FAILURE:
235                         sb.append("AUTHENTICATION_FAILURE");
236                         break;
237                     case FAILURE_SSID_TEMP_DISABLED:
238                         sb.append("SSID_TEMP_DISABLED");
239                         break;
240                     case FAILURE_CONNECT_NETWORK_FAILED:
241                         sb.append("CONNECT_NETWORK_FAILED");
242                         break;
243                     case FAILURE_NETWORK_DISCONNECTION:
244                         sb.append("NETWORK_DISCONNECTION");
245                         break;
246                     case FAILURE_NEW_CONNECTION_ATTEMPT:
247                         sb.append("NEW_CONNECTION_ATTEMPT");
248                         break;
249                     case FAILURE_REDUNDANT_CONNECTION_ATTEMPT:
250                         sb.append("REDUNDANT_CONNECTION_ATTEMPT");
251                         break;
252                     case FAILURE_ROAM_TIMEOUT:
253                         sb.append("ROAM_TIMEOUT");
254                         break;
255                     case FAILURE_DHCP:
256                         sb.append("DHCP");
257                     default:
258                         sb.append("UNKNOWN");
259                         break;
260                 }
261                 sb.append(", connectivityLevelFailureCode=");
262                 switch(mConnectionEvent.connectivityLevelFailureCode) {
263                     case WifiMetricsProto.ConnectionEvent.HLF_NONE:
264                         sb.append("NONE");
265                         break;
266                     case WifiMetricsProto.ConnectionEvent.HLF_DHCP:
267                         sb.append("DHCP");
268                         break;
269                     case WifiMetricsProto.ConnectionEvent.HLF_NO_INTERNET:
270                         sb.append("NO_INTERNET");
271                         break;
272                     case WifiMetricsProto.ConnectionEvent.HLF_UNWANTED:
273                         sb.append("UNWANTED");
274                         break;
275                     default:
276                         sb.append("UNKNOWN");
277                         break;
278                 }
279                 sb.append(", signalStrength=");
280                 sb.append(mConnectionEvent.signalStrength);
281                 sb.append(", wifiState=");
282                 switch(mWifiState) {
283                     case WifiMetricsProto.WifiLog.WIFI_DISABLED:
284                         sb.append("WIFI_DISABLED");
285                         break;
286                     case WifiMetricsProto.WifiLog.WIFI_DISCONNECTED:
287                         sb.append("WIFI_DISCONNECTED");
288                         break;
289                     case WifiMetricsProto.WifiLog.WIFI_ASSOCIATED:
290                         sb.append("WIFI_ASSOCIATED");
291                         break;
292                     default:
293                         sb.append("WIFI_UNKNOWN");
294                         break;
295                 }
296                 sb.append(", screenOn=");
297                 sb.append(mScreenOn);
298                 sb.append(". mRouterFingerprint: ");
299                 sb.append(mRouterFingerPrint.toString());
300             }
301             return sb.toString();
302         }
303     }
304 
WifiMetrics(Clock clock)305     public WifiMetrics(Clock clock) {
306         mClock = clock;
307         mWifiLogProto = new WifiMetricsProto.WifiLog();
308         mConnectionEventList = new ArrayList<>();
309         mScanReturnEntries = new SparseIntArray();
310         mWifiSystemStateEntries = new SparseIntArray();
311         mCurrentConnectionEvent = null;
312         mScreenOn = true;
313         mWifiState = WifiMetricsProto.WifiLog.WIFI_DISABLED;
314         mRecordStartTimeSec = mClock.elapsedRealtime() / 1000;
315     }
316 
317     // Values used for indexing SystemStateEntries
318     private static final int SCREEN_ON = 1;
319     private static final int SCREEN_OFF = 0;
320 
321     /**
322      * Create a new connection event. Call when wifi attempts to make a new network connection
323      * If there is a current 'un-ended' connection event, it will be ended with UNKNOWN connectivity
324      * failure code.
325      * Gathers and sets the RouterFingerPrint data as well
326      *
327      * @param config WifiConfiguration of the config used for the current connection attempt
328      * @param roamType Roam type that caused connection attempt, see WifiMetricsProto.WifiLog.ROAM_X
329      */
startConnectionEvent(WifiConfiguration config, String targetBSSID, int roamType)330     public void startConnectionEvent(WifiConfiguration config, String targetBSSID, int roamType) {
331         synchronized (mLock) {
332             // Check if this is overlapping another current connection event
333             if (mCurrentConnectionEvent != null) {
334                 //Is this new Connection Event the same as the current one
335                 if (mCurrentConnectionEvent.mConfigSsid != null
336                         && mCurrentConnectionEvent.mConfigBssid != null
337                         && config != null
338                         && mCurrentConnectionEvent.mConfigSsid.equals(config.SSID)
339                         && (mCurrentConnectionEvent.mConfigBssid.equals("any")
340                         || mCurrentConnectionEvent.mConfigBssid.equals(targetBSSID))) {
341                     mCurrentConnectionEvent.mConfigBssid = targetBSSID;
342                     // End Connection Event due to new connection attempt to the same network
343                     endConnectionEvent(ConnectionEvent.FAILURE_REDUNDANT_CONNECTION_ATTEMPT,
344                             WifiMetricsProto.ConnectionEvent.HLF_NONE);
345                 } else {
346                     // End Connection Event due to new connection attempt to different network
347                     endConnectionEvent(ConnectionEvent.FAILURE_NEW_CONNECTION_ATTEMPT,
348                             WifiMetricsProto.ConnectionEvent.HLF_NONE);
349                 }
350             }
351             //If past maximum connection events, start removing the oldest
352             while(mConnectionEventList.size() >= MAX_CONNECTION_EVENTS) {
353                 mConnectionEventList.remove(0);
354             }
355             mCurrentConnectionEvent = new ConnectionEvent();
356             mCurrentConnectionEvent.mConnectionEvent.startTimeMillis =
357                     mClock.currentTimeMillis();
358             mCurrentConnectionEvent.mConfigBssid = targetBSSID;
359             mCurrentConnectionEvent.mConnectionEvent.roamType = roamType;
360             mCurrentConnectionEvent.mRouterFingerPrint.updateFromWifiConfiguration(config);
361             mCurrentConnectionEvent.mConfigBssid = "any";
362             mCurrentConnectionEvent.mRealStartTime = mClock.elapsedRealtime();
363             mCurrentConnectionEvent.mWifiState = mWifiState;
364             mCurrentConnectionEvent.mScreenOn = mScreenOn;
365             mConnectionEventList.add(mCurrentConnectionEvent);
366         }
367     }
368 
369     /**
370      * set the RoamType of the current ConnectionEvent (if any)
371      */
setConnectionEventRoamType(int roamType)372     public void setConnectionEventRoamType(int roamType) {
373         synchronized (mLock) {
374             if (mCurrentConnectionEvent != null) {
375                 mCurrentConnectionEvent.mConnectionEvent.roamType = roamType;
376             }
377         }
378     }
379 
380     /**
381      * Set AP related metrics from ScanDetail
382      */
setConnectionScanDetail(ScanDetail scanDetail)383     public void setConnectionScanDetail(ScanDetail scanDetail) {
384         synchronized (mLock) {
385             if (mCurrentConnectionEvent != null && scanDetail != null) {
386                 NetworkDetail networkDetail = scanDetail.getNetworkDetail();
387                 ScanResult scanResult = scanDetail.getScanResult();
388                 //Ensure that we have a networkDetail, and that it corresponds to the currently
389                 //tracked connection attempt
390                 if (networkDetail != null && scanResult != null
391                         && mCurrentConnectionEvent.mConfigSsid != null
392                         && mCurrentConnectionEvent.mConfigSsid
393                         .equals("\"" + networkDetail.getSSID() + "\"")) {
394                     updateMetricsFromNetworkDetail(networkDetail);
395                     updateMetricsFromScanResult(scanResult);
396                 }
397             }
398         }
399     }
400 
401     /**
402      * End a Connection event record. Call when wifi connection attempt succeeds or fails.
403      * If a Connection event has not been started and is active when .end is called, a new one is
404      * created with zero duration.
405      *
406      * @param level2FailureCode Level 2 failure code returned by supplicant
407      * @param connectivityFailureCode WifiMetricsProto.ConnectionEvent.HLF_X
408      */
endConnectionEvent(int level2FailureCode, int connectivityFailureCode)409     public void endConnectionEvent(int level2FailureCode, int connectivityFailureCode) {
410         synchronized (mLock) {
411             if (mCurrentConnectionEvent != null) {
412                 boolean result = (level2FailureCode == 1)
413                         && (connectivityFailureCode == WifiMetricsProto.ConnectionEvent.HLF_NONE);
414                 mCurrentConnectionEvent.mConnectionEvent.connectionResult = result ? 1 : 0;
415                 mCurrentConnectionEvent.mRealEndTime = mClock.elapsedRealtime();
416                 mCurrentConnectionEvent.mConnectionEvent.durationTakenToConnectMillis = (int)
417                         (mCurrentConnectionEvent.mRealEndTime
418                         - mCurrentConnectionEvent.mRealStartTime);
419                 mCurrentConnectionEvent.mConnectionEvent.level2FailureCode = level2FailureCode;
420                 mCurrentConnectionEvent.mConnectionEvent.connectivityLevelFailureCode =
421                         connectivityFailureCode;
422                 // ConnectionEvent already added to ConnectionEvents List. Safe to null current here
423                 mCurrentConnectionEvent = null;
424             }
425         }
426     }
427 
428     /**
429      * Set ConnectionEvent DTIM Interval (if set), and 802.11 Connection mode, from NetworkDetail
430      */
updateMetricsFromNetworkDetail(NetworkDetail networkDetail)431     private void updateMetricsFromNetworkDetail(NetworkDetail networkDetail) {
432         int dtimInterval = networkDetail.getDtimInterval();
433         if (dtimInterval > 0) {
434             mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.dtim =
435                     dtimInterval;
436         }
437         int connectionWifiMode;
438         switch (networkDetail.getWifiMode()) {
439             case InformationElementUtil.WifiMode.MODE_UNDEFINED:
440                 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_UNKNOWN;
441                 break;
442             case InformationElementUtil.WifiMode.MODE_11A:
443                 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_A;
444                 break;
445             case InformationElementUtil.WifiMode.MODE_11B:
446                 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_B;
447                 break;
448             case InformationElementUtil.WifiMode.MODE_11G:
449                 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_G;
450                 break;
451             case InformationElementUtil.WifiMode.MODE_11N:
452                 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_N;
453                 break;
454             case InformationElementUtil.WifiMode.MODE_11AC  :
455                 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_AC;
456                 break;
457             default:
458                 connectionWifiMode = WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_OTHER;
459                 break;
460         }
461         mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto
462                 .routerTechnology = connectionWifiMode;
463     }
464 
465     /**
466      * Set ConnectionEvent RSSI and authentication type from ScanResult
467      */
updateMetricsFromScanResult(ScanResult scanResult)468     private void updateMetricsFromScanResult(ScanResult scanResult) {
469         mCurrentConnectionEvent.mConnectionEvent.signalStrength = scanResult.level;
470         mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
471                 WifiMetricsProto.RouterFingerPrint.AUTH_OPEN;
472         mCurrentConnectionEvent.mConfigBssid = scanResult.BSSID;
473         if (scanResult.capabilities != null) {
474             if (scanResult.capabilities.contains("WEP")) {
475                 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
476                         WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL;
477             } else if (scanResult.capabilities.contains("PSK")) {
478                 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
479                         WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL;
480             } else if (scanResult.capabilities.contains("EAP")) {
481                 mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.authentication =
482                         WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE;
483             }
484         }
485         mCurrentConnectionEvent.mRouterFingerPrint.mRouterFingerPrintProto.channelInfo =
486                 scanResult.frequency;
487     }
488 
setNumSavedNetworks(int num)489     void setNumSavedNetworks(int num) {
490         synchronized (mLock) {
491             mWifiLogProto.numSavedNetworks = num;
492         }
493     }
494 
setNumOpenNetworks(int num)495     void setNumOpenNetworks(int num) {
496         synchronized (mLock) {
497             mWifiLogProto.numOpenNetworks = num;
498         }
499     }
500 
setNumPersonalNetworks(int num)501     void setNumPersonalNetworks(int num) {
502         synchronized (mLock) {
503             mWifiLogProto.numPersonalNetworks = num;
504         }
505     }
506 
setNumEnterpriseNetworks(int num)507     void setNumEnterpriseNetworks(int num) {
508         synchronized (mLock) {
509             mWifiLogProto.numEnterpriseNetworks = num;
510         }
511     }
512 
setNumNetworksAddedByUser(int num)513     void setNumNetworksAddedByUser(int num) {
514         synchronized (mLock) {
515             mWifiLogProto.numNetworksAddedByUser = num;
516         }
517     }
518 
setNumNetworksAddedByApps(int num)519     void setNumNetworksAddedByApps(int num) {
520         synchronized (mLock) {
521             mWifiLogProto.numNetworksAddedByApps = num;
522         }
523     }
524 
setIsLocationEnabled(boolean enabled)525     void setIsLocationEnabled(boolean enabled) {
526         synchronized (mLock) {
527             mWifiLogProto.isLocationEnabled = enabled;
528         }
529     }
530 
setIsScanningAlwaysEnabled(boolean enabled)531     void setIsScanningAlwaysEnabled(boolean enabled) {
532         synchronized (mLock) {
533             mWifiLogProto.isScanningAlwaysEnabled = enabled;
534         }
535     }
536 
537     /**
538      * Increment Non Empty Scan Results count
539      */
incrementNonEmptyScanResultCount()540     public void incrementNonEmptyScanResultCount() {
541         if (DBG) Log.v(TAG, "incrementNonEmptyScanResultCount");
542         synchronized (mLock) {
543             mWifiLogProto.numNonEmptyScanResults++;
544         }
545     }
546 
547     /**
548      * Increment Empty Scan Results count
549      */
incrementEmptyScanResultCount()550     public void incrementEmptyScanResultCount() {
551         if (DBG) Log.v(TAG, "incrementEmptyScanResultCount");
552         synchronized (mLock) {
553             mWifiLogProto.numEmptyScanResults++;
554         }
555     }
556 
557     /**
558      * Increment background scan count
559      */
incrementBackgroundScanCount()560     public void incrementBackgroundScanCount() {
561         if (DBG) Log.v(TAG, "incrementBackgroundScanCount");
562         synchronized (mLock) {
563             mWifiLogProto.numBackgroundScans++;
564         }
565     }
566 
567    /**
568      * Get Background scan count
569      */
getBackgroundScanCount()570     public int getBackgroundScanCount() {
571         synchronized (mLock) {
572             return mWifiLogProto.numBackgroundScans;
573         }
574     }
575 
576     /**
577      * Increment oneshot scan count, and the associated WifiSystemScanStateCount entry
578      */
incrementOneshotScanCount()579     public void incrementOneshotScanCount() {
580         synchronized (mLock) {
581             mWifiLogProto.numOneshotScans++;
582         }
583         incrementWifiSystemScanStateCount(mWifiState, mScreenOn);
584     }
585 
586     /**
587      * Get oneshot scan count
588      */
getOneshotScanCount()589     public int getOneshotScanCount() {
590         synchronized (mLock) {
591             return mWifiLogProto.numOneshotScans;
592         }
593     }
594 
returnCodeToString(int scanReturnCode)595     private String returnCodeToString(int scanReturnCode) {
596         switch(scanReturnCode){
597             case WifiMetricsProto.WifiLog.SCAN_UNKNOWN:
598                 return "SCAN_UNKNOWN";
599             case WifiMetricsProto.WifiLog.SCAN_SUCCESS:
600                 return "SCAN_SUCCESS";
601             case WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED:
602                 return "SCAN_FAILURE_INTERRUPTED";
603             case WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION:
604                 return "SCAN_FAILURE_INVALID_CONFIGURATION";
605             case WifiMetricsProto.WifiLog.FAILURE_WIFI_DISABLED:
606                 return "FAILURE_WIFI_DISABLED";
607             default:
608                 return "<UNKNOWN>";
609         }
610     }
611 
612     /**
613      * Increment count of scan return code occurrence
614      *
615      * @param scanReturnCode Return code from scan attempt WifiMetricsProto.WifiLog.SCAN_X
616      */
incrementScanReturnEntry(int scanReturnCode, int countToAdd)617     public void incrementScanReturnEntry(int scanReturnCode, int countToAdd) {
618         synchronized (mLock) {
619             if (DBG) Log.v(TAG, "incrementScanReturnEntry " + returnCodeToString(scanReturnCode));
620             int entry = mScanReturnEntries.get(scanReturnCode);
621             entry += countToAdd;
622             mScanReturnEntries.put(scanReturnCode, entry);
623         }
624     }
625     /**
626      * Get the count of this scanReturnCode
627      * @param scanReturnCode that we are getting the count for
628      */
getScanReturnEntry(int scanReturnCode)629     public int getScanReturnEntry(int scanReturnCode) {
630         synchronized (mLock) {
631             return mScanReturnEntries.get(scanReturnCode);
632         }
633     }
634 
wifiSystemStateToString(int state)635     private String wifiSystemStateToString(int state) {
636         switch(state){
637             case WifiMetricsProto.WifiLog.WIFI_UNKNOWN:
638                 return "WIFI_UNKNOWN";
639             case WifiMetricsProto.WifiLog.WIFI_DISABLED:
640                 return "WIFI_DISABLED";
641             case WifiMetricsProto.WifiLog.WIFI_DISCONNECTED:
642                 return "WIFI_DISCONNECTED";
643             case WifiMetricsProto.WifiLog.WIFI_ASSOCIATED:
644                 return "WIFI_ASSOCIATED";
645             default:
646                 return "default";
647         }
648     }
649 
650     /**
651      * Increments the count of scans initiated by each wifi state, accounts for screenOn/Off
652      *
653      * @param state State of the system when scan was initiated, see WifiMetricsProto.WifiLog.WIFI_X
654      * @param screenOn Is the screen on
655      */
incrementWifiSystemScanStateCount(int state, boolean screenOn)656     public void incrementWifiSystemScanStateCount(int state, boolean screenOn) {
657         synchronized (mLock) {
658             if (DBG) {
659                 Log.v(TAG, "incrementWifiSystemScanStateCount " + wifiSystemStateToString(state)
660                         + " " + screenOn);
661             }
662             int index = (state * 2) + (screenOn ? SCREEN_ON : SCREEN_OFF);
663             int entry = mWifiSystemStateEntries.get(index);
664             entry++;
665             mWifiSystemStateEntries.put(index, entry);
666         }
667     }
668 
669     /**
670      * Get the count of this system State Entry
671      */
getSystemStateCount(int state, boolean screenOn)672     public int getSystemStateCount(int state, boolean screenOn) {
673         synchronized (mLock) {
674             int index = state * 2 + (screenOn ? SCREEN_ON : SCREEN_OFF);
675             return mWifiSystemStateEntries.get(index);
676         }
677     }
678 
679     /**
680      * Increment number of times the Watchdog of Last Resort triggered, resetting the wifi stack
681      */
incrementNumLastResortWatchdogTriggers()682     public void incrementNumLastResortWatchdogTriggers() {
683         synchronized (mLock) {
684             mWifiLogProto.numLastResortWatchdogTriggers++;
685         }
686     }
687     /**
688      * @param count number of networks over bad association threshold when watchdog triggered
689      */
addCountToNumLastResortWatchdogBadAssociationNetworksTotal(int count)690     public void addCountToNumLastResortWatchdogBadAssociationNetworksTotal(int count) {
691         synchronized (mLock) {
692             mWifiLogProto.numLastResortWatchdogBadAssociationNetworksTotal += count;
693         }
694     }
695     /**
696      * @param count number of networks over bad authentication threshold when watchdog triggered
697      */
addCountToNumLastResortWatchdogBadAuthenticationNetworksTotal(int count)698     public void addCountToNumLastResortWatchdogBadAuthenticationNetworksTotal(int count) {
699         synchronized (mLock) {
700             mWifiLogProto.numLastResortWatchdogBadAuthenticationNetworksTotal += count;
701         }
702     }
703     /**
704      * @param count number of networks over bad dhcp threshold when watchdog triggered
705      */
addCountToNumLastResortWatchdogBadDhcpNetworksTotal(int count)706     public void addCountToNumLastResortWatchdogBadDhcpNetworksTotal(int count) {
707         synchronized (mLock) {
708             mWifiLogProto.numLastResortWatchdogBadDhcpNetworksTotal += count;
709         }
710     }
711     /**
712      * @param count number of networks over bad other threshold when watchdog triggered
713      */
addCountToNumLastResortWatchdogBadOtherNetworksTotal(int count)714     public void addCountToNumLastResortWatchdogBadOtherNetworksTotal(int count) {
715         synchronized (mLock) {
716             mWifiLogProto.numLastResortWatchdogBadOtherNetworksTotal += count;
717         }
718     }
719     /**
720      * @param count number of networks seen when watchdog triggered
721      */
addCountToNumLastResortWatchdogAvailableNetworksTotal(int count)722     public void addCountToNumLastResortWatchdogAvailableNetworksTotal(int count) {
723         synchronized (mLock) {
724             mWifiLogProto.numLastResortWatchdogAvailableNetworksTotal += count;
725         }
726     }
727     /**
728      * Increment count of triggers with atleast one bad association network
729      */
incrementNumLastResortWatchdogTriggersWithBadAssociation()730     public void incrementNumLastResortWatchdogTriggersWithBadAssociation() {
731         synchronized (mLock) {
732             mWifiLogProto.numLastResortWatchdogTriggersWithBadAssociation++;
733         }
734     }
735     /**
736      * Increment count of triggers with atleast one bad authentication network
737      */
incrementNumLastResortWatchdogTriggersWithBadAuthentication()738     public void incrementNumLastResortWatchdogTriggersWithBadAuthentication() {
739         synchronized (mLock) {
740             mWifiLogProto.numLastResortWatchdogTriggersWithBadAuthentication++;
741         }
742     }
743     /**
744      * Increment count of triggers with atleast one bad dhcp network
745      */
incrementNumLastResortWatchdogTriggersWithBadDhcp()746     public void incrementNumLastResortWatchdogTriggersWithBadDhcp() {
747         synchronized (mLock) {
748             mWifiLogProto.numLastResortWatchdogTriggersWithBadDhcp++;
749         }
750     }
751     /**
752      * Increment count of triggers with atleast one bad other network
753      */
incrementNumLastResortWatchdogTriggersWithBadOther()754     public void incrementNumLastResortWatchdogTriggersWithBadOther() {
755         synchronized (mLock) {
756             mWifiLogProto.numLastResortWatchdogTriggersWithBadOther++;
757         }
758     }
759 
760     /**
761      * Increment number of times connectivity watchdog confirmed pno is working
762      */
incrementNumConnectivityWatchdogPnoGood()763     public void incrementNumConnectivityWatchdogPnoGood() {
764         synchronized (mLock) {
765             mWifiLogProto.numConnectivityWatchdogPnoGood++;
766         }
767     }
768     /**
769      * Increment number of times connectivity watchdog found pno not working
770      */
incrementNumConnectivityWatchdogPnoBad()771     public void incrementNumConnectivityWatchdogPnoBad() {
772         synchronized (mLock) {
773             mWifiLogProto.numConnectivityWatchdogPnoBad++;
774         }
775     }
776     /**
777      * Increment number of times connectivity watchdog confirmed background scan is working
778      */
incrementNumConnectivityWatchdogBackgroundGood()779     public void incrementNumConnectivityWatchdogBackgroundGood() {
780         synchronized (mLock) {
781             mWifiLogProto.numConnectivityWatchdogBackgroundGood++;
782         }
783     }
784     /**
785      * Increment number of times connectivity watchdog found background scan not working
786      */
incrementNumConnectivityWatchdogBackgroundBad()787     public void incrementNumConnectivityWatchdogBackgroundBad() {
788         synchronized (mLock) {
789             mWifiLogProto.numConnectivityWatchdogBackgroundBad++;
790         }
791     }
792 
793     public static final String PROTO_DUMP_ARG = "wifiMetricsProto";
794     /**
795      * Dump all WifiMetrics. Collects some metrics from ConfigStore, Settings and WifiManager
796      * at this time
797      *
798      * @param fd unused
799      * @param pw PrintWriter for writing dump to
800      * @param args unused
801      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)802     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
803         synchronized (mLock) {
804             pw.println("WifiMetrics:");
805             if (args.length > 0 && PROTO_DUMP_ARG.equals(args[0])) {
806                 //Dump serialized WifiLog proto
807                 consolidateProto(true);
808                 for (ConnectionEvent event : mConnectionEventList) {
809                     if (mCurrentConnectionEvent != event) {
810                         //indicate that automatic bug report has been taken for all valid
811                         //connection events
812                         event.mConnectionEvent.automaticBugReportTaken = true;
813                     }
814                 }
815                 byte[] wifiMetricsProto = WifiMetricsProto.WifiLog.toByteArray(mWifiLogProto);
816                 String metricsProtoDump = Base64.encodeToString(wifiMetricsProto, Base64.DEFAULT);
817                 pw.println(metricsProtoDump);
818                 pw.println("EndWifiMetrics");
819                 clear();
820             } else {
821                 pw.println("mConnectionEvents:");
822                 for (ConnectionEvent event : mConnectionEventList) {
823                     String eventLine = event.toString();
824                     if (event == mCurrentConnectionEvent) {
825                         eventLine += "CURRENTLY OPEN EVENT";
826                     }
827                     pw.println(eventLine);
828                 }
829                 pw.println("mWifiLogProto.numSavedNetworks=" + mWifiLogProto.numSavedNetworks);
830                 pw.println("mWifiLogProto.numOpenNetworks=" + mWifiLogProto.numOpenNetworks);
831                 pw.println("mWifiLogProto.numPersonalNetworks="
832                         + mWifiLogProto.numPersonalNetworks);
833                 pw.println("mWifiLogProto.numEnterpriseNetworks="
834                         + mWifiLogProto.numEnterpriseNetworks);
835                 pw.println("mWifiLogProto.isLocationEnabled=" + mWifiLogProto.isLocationEnabled);
836                 pw.println("mWifiLogProto.isScanningAlwaysEnabled="
837                         + mWifiLogProto.isScanningAlwaysEnabled);
838                 pw.println("mWifiLogProto.numNetworksAddedByUser="
839                         + mWifiLogProto.numNetworksAddedByUser);
840                 pw.println("mWifiLogProto.numNetworksAddedByApps="
841                         + mWifiLogProto.numNetworksAddedByApps);
842                 pw.println("mWifiLogProto.numNonEmptyScanResults="
843                         + mWifiLogProto.numNonEmptyScanResults);
844                 pw.println("mWifiLogProto.numEmptyScanResults="
845                         + mWifiLogProto.numEmptyScanResults);
846                 pw.println("mWifiLogProto.numOneshotScans="
847                         + mWifiLogProto.numOneshotScans);
848                 pw.println("mWifiLogProto.numBackgroundScans="
849                         + mWifiLogProto.numBackgroundScans);
850 
851                 pw.println("mScanReturnEntries:");
852                 pw.println("  SCAN_UNKNOWN: " + getScanReturnEntry(
853                         WifiMetricsProto.WifiLog.SCAN_UNKNOWN));
854                 pw.println("  SCAN_SUCCESS: " + getScanReturnEntry(
855                         WifiMetricsProto.WifiLog.SCAN_SUCCESS));
856                 pw.println("  SCAN_FAILURE_INTERRUPTED: " + getScanReturnEntry(
857                         WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED));
858                 pw.println("  SCAN_FAILURE_INVALID_CONFIGURATION: " + getScanReturnEntry(
859                         WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION));
860                 pw.println("  FAILURE_WIFI_DISABLED: " + getScanReturnEntry(
861                         WifiMetricsProto.WifiLog.FAILURE_WIFI_DISABLED));
862 
863                 pw.println("mSystemStateEntries: <state><screenOn> : <scansInitiated>");
864                 pw.println("  WIFI_UNKNOWN       ON: "
865                         + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_UNKNOWN, true));
866                 pw.println("  WIFI_DISABLED      ON: "
867                         + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_DISABLED, true));
868                 pw.println("  WIFI_DISCONNECTED  ON: "
869                         + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED, true));
870                 pw.println("  WIFI_ASSOCIATED    ON: "
871                         + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED, true));
872                 pw.println("  WIFI_UNKNOWN      OFF: "
873                         + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_UNKNOWN, false));
874                 pw.println("  WIFI_DISABLED     OFF: "
875                         + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_DISABLED, false));
876                 pw.println("  WIFI_DISCONNECTED OFF: "
877                         + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED, false));
878                 pw.println("  WIFI_ASSOCIATED   OFF: "
879                         + getSystemStateCount(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED, false));
880                 pw.println("mWifiLogProto.numConnectivityWatchdogPnoGood="
881                         + mWifiLogProto.numConnectivityWatchdogPnoGood);
882                 pw.println("mWifiLogProto.numConnectivityWatchdogPnoBad="
883                         + mWifiLogProto.numConnectivityWatchdogPnoBad);
884                 pw.println("mWifiLogProto.numConnectivityWatchdogBackgroundGood="
885                         + mWifiLogProto.numConnectivityWatchdogBackgroundGood);
886                 pw.println("mWifiLogProto.numConnectivityWatchdogBackgroundBad="
887                         + mWifiLogProto.numConnectivityWatchdogBackgroundBad);
888                 pw.println("mWifiLogProto.numLastResortWatchdogTriggers="
889                         + mWifiLogProto.numLastResortWatchdogTriggers);
890                 pw.println("mWifiLogProto.numLastResortWatchdogBadAssociationNetworksTotal="
891                         + mWifiLogProto.numLastResortWatchdogBadAssociationNetworksTotal);
892                 pw.println("mWifiLogProto.numLastResortWatchdogBadAuthenticationNetworksTotal="
893                         + mWifiLogProto.numLastResortWatchdogBadAuthenticationNetworksTotal);
894                 pw.println("mWifiLogProto.numLastResortWatchdogBadDhcpNetworksTotal="
895                         + mWifiLogProto.numLastResortWatchdogBadDhcpNetworksTotal);
896                 pw.println("mWifiLogProto.numLastResortWatchdogBadOtherNetworksTotal="
897                         + mWifiLogProto.numLastResortWatchdogBadOtherNetworksTotal);
898                 pw.println("mWifiLogProto.numLastResortWatchdogAvailableNetworksTotal="
899                         + mWifiLogProto.numLastResortWatchdogAvailableNetworksTotal);
900                 pw.println("mWifiLogProto.numLastResortWatchdogTriggersWithBadAssociation="
901                         + mWifiLogProto.numLastResortWatchdogTriggersWithBadAssociation);
902                 pw.println("mWifiLogProto.numLastResortWatchdogTriggersWithBadAuthentication="
903                         + mWifiLogProto.numLastResortWatchdogTriggersWithBadAuthentication);
904                 pw.println("mWifiLogProto.numLastResortWatchdogTriggersWithBadDhcp="
905                         + mWifiLogProto.numLastResortWatchdogTriggersWithBadDhcp);
906                 pw.println("mWifiLogProto.numLastResortWatchdogTriggersWithBadOther="
907                         + mWifiLogProto.numLastResortWatchdogTriggersWithBadOther);
908                 pw.println("mWifiLogProto.recordDurationSec="
909                         + ((mClock.elapsedRealtime() / 1000) - mRecordStartTimeSec));
910             }
911         }
912     }
913 
914     /**
915      * append the separate ConnectionEvent, SystemStateEntry and ScanReturnCode collections to their
916      * respective lists within mWifiLogProto
917      *
918      * @param incremental Only include ConnectionEvents created since last automatic bug report
919      */
consolidateProto(boolean incremental)920     private void consolidateProto(boolean incremental) {
921         List<WifiMetricsProto.ConnectionEvent> events = new ArrayList<>();
922         synchronized (mLock) {
923             for (ConnectionEvent event : mConnectionEventList) {
924                 // If this is not incremental, dump full ConnectionEvent list
925                 // Else Dump all un-dumped events except for the current one
926                 if (!incremental || ((mCurrentConnectionEvent != event)
927                         && !event.mConnectionEvent.automaticBugReportTaken)) {
928                     //Get all ConnectionEvents that haven not been dumped as a proto, also exclude
929                     //the current active un-ended connection event
930                     events.add(event.mConnectionEvent);
931                     if (incremental) {
932                         event.mConnectionEvent.automaticBugReportTaken = true;
933                     }
934                 }
935             }
936             if (events.size() > 0) {
937                 mWifiLogProto.connectionEvent = events.toArray(mWifiLogProto.connectionEvent);
938             }
939 
940             //Convert the SparseIntArray of scanReturnEntry integers into ScanReturnEntry proto list
941             mWifiLogProto.scanReturnEntries =
942                     new WifiMetricsProto.WifiLog.ScanReturnEntry[mScanReturnEntries.size()];
943             for (int i = 0; i < mScanReturnEntries.size(); i++) {
944                 mWifiLogProto.scanReturnEntries[i] = new WifiMetricsProto.WifiLog.ScanReturnEntry();
945                 mWifiLogProto.scanReturnEntries[i].scanReturnCode = mScanReturnEntries.keyAt(i);
946                 mWifiLogProto.scanReturnEntries[i].scanResultsCount = mScanReturnEntries.valueAt(i);
947             }
948 
949             // Convert the SparseIntArray of systemStateEntry into WifiSystemStateEntry proto list
950             // This one is slightly more complex, as the Sparse are indexed with:
951             //     key: wifiState * 2 + isScreenOn, value: wifiStateCount
952             mWifiLogProto.wifiSystemStateEntries =
953                     new WifiMetricsProto.WifiLog
954                     .WifiSystemStateEntry[mWifiSystemStateEntries.size()];
955             for (int i = 0; i < mWifiSystemStateEntries.size(); i++) {
956                 mWifiLogProto.wifiSystemStateEntries[i] =
957                         new WifiMetricsProto.WifiLog.WifiSystemStateEntry();
958                 mWifiLogProto.wifiSystemStateEntries[i].wifiState =
959                         mWifiSystemStateEntries.keyAt(i) / 2;
960                 mWifiLogProto.wifiSystemStateEntries[i].wifiStateCount =
961                         mWifiSystemStateEntries.valueAt(i);
962                 mWifiLogProto.wifiSystemStateEntries[i].isScreenOn =
963                         (mWifiSystemStateEntries.keyAt(i) % 2) > 0;
964             }
965             mWifiLogProto.recordDurationSec = (int) ((mClock.elapsedRealtime() / 1000)
966                     - mRecordStartTimeSec);
967         }
968     }
969 
970     /**
971      * Clear all WifiMetrics, except for currentConnectionEvent.
972      */
clear()973     private void clear() {
974         synchronized (mLock) {
975             mConnectionEventList.clear();
976             if (mCurrentConnectionEvent != null) {
977                 mConnectionEventList.add(mCurrentConnectionEvent);
978             }
979             mScanReturnEntries.clear();
980             mWifiSystemStateEntries.clear();
981             mRecordStartTimeSec = mClock.elapsedRealtime() / 1000;
982             mWifiLogProto.clear();
983         }
984     }
985 
986     /**
987      *  Set screen state (On/Off)
988      */
setScreenState(boolean screenOn)989     public void setScreenState(boolean screenOn) {
990         synchronized (mLock) {
991             mScreenOn = screenOn;
992         }
993     }
994 
995     /**
996      *  Set wifi state (WIFI_UNKNOWN, WIFI_DISABLED, WIFI_DISCONNECTED, WIFI_ASSOCIATED)
997      */
setWifiState(int wifiState)998     public void setWifiState(int wifiState) {
999         synchronized (mLock) {
1000             mWifiState = wifiState;
1001         }
1002     }
1003 }
1004