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