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