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