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