1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wifi;
18 
19 import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS;
20 
21 import static com.android.server.wifi.WifiScoreCard.TS_NONE;
22 
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.app.AlarmManager;
26 import android.content.Context;
27 import android.content.pm.ModuleInfo;
28 import android.content.pm.PackageManager;
29 import android.net.MacAddress;
30 import android.net.wifi.ScanResult;
31 import android.net.wifi.WifiConfiguration;
32 import android.net.wifi.WifiManager;
33 import android.net.wifi.WifiManager.DeviceMobilityState;
34 import android.net.wifi.WifiScanner;
35 import android.os.Build;
36 import android.util.Log;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.server.wifi.WifiScoreCard.MemoryStore;
40 import com.android.server.wifi.WifiScoreCard.MemoryStoreAccessBase;
41 import com.android.server.wifi.WifiScoreCard.PerNetwork;
42 import com.android.server.wifi.proto.WifiScoreCardProto.SoftwareBuildInfo;
43 import com.android.server.wifi.proto.WifiScoreCardProto.SystemInfoStats;
44 import com.android.server.wifi.proto.WifiStatsLog;
45 import com.android.server.wifi.proto.nano.WifiMetricsProto.HealthMonitorFailureStats;
46 import com.android.server.wifi.proto.nano.WifiMetricsProto.HealthMonitorMetrics;
47 import com.android.server.wifi.scanner.WifiScannerInternal;
48 
49 import com.google.protobuf.InvalidProtocolBufferException;
50 
51 import java.io.FileDescriptor;
52 import java.io.PrintWriter;
53 import java.lang.annotation.Retention;
54 import java.lang.annotation.RetentionPolicy;
55 import java.util.ArrayList;
56 import java.util.Calendar;
57 import java.util.List;
58 
59 import javax.annotation.concurrent.NotThreadSafe;
60 
61 /**
62  * Monitor and detect potential WiFi health issue when RSSI is sufficiently high.
63  * There are two detections, daily detection and post-boot detection.
64  * Post-boot detection is to detect abnormal scan/connection behavior change after device reboot
65  * and/or SW build change.
66  * Daily detection is to detect connection and other behavior changes especially after SW change.
67  */
68 
69 @NotThreadSafe
70 public class WifiHealthMonitor {
71     private static final String TAG = "WifiHealthMonitor";
72     private boolean mVerboseLoggingEnabled = false;
73     public static final String DAILY_DETECTION_TIMER_TAG =
74             "WifiHealthMonitor Schedule Daily Detection Timer";
75     public static final String POST_BOOT_DETECTION_TIMER_TAG =
76             "WifiHealthMonitor Schedule Post-Boot Detection Timer";
77     // Package name of WiFi mainline module found from the following adb command
78     // adb shell pm list packages --apex-only| grep wifi
79     private static final String WIFI_APEX_NAME = "com.android.wifi";
80     private static final String SYSTEM_INFO_DATA_NAME = "systemInfoData";
81     // The time that device waits after device boot before triggering post-boot detection.
82     // This needs be long enough so that memory read can complete before post-boot detection.
83     private static final int POST_BOOT_DETECTION_WAIT_TIME_MS = 25_000;
84     // The time interval between two daily detections
85     private static final long DAILY_DETECTION_INTERVAL_MS = 24 * 3600_000;
86     public static final int DAILY_DETECTION_HOUR = 23;
87     private static final int DAILY_DETECTION_MIN = 00;
88     private static final long MIN_WAIT_TIME_BEFORE_FIRST_DETECTION_MS = 100_000;
89     // Max interval between pre-boot scan and post-boot scan to qualify post-boot scan detection
90     private static final long MAX_INTERVAL_BETWEEN_TWO_SCAN_MS = 60_000;
91     // The minimum number of BSSIDs that should be found during a normal scan to trigger detection
92     // of an abnormal scan which happens either before or after the normal scan within a short time.
93     // Minimum number of BSSIDs found at 2G with a normal scan
94     private static final int MIN_NUM_BSSID_SCAN_2G = 2;
95     // Minimum number of BSSIDs found above 2G with a normal scan
96     private static final int MIN_NUM_BSSID_SCAN_ABOVE_2G = 2;
97     // Minimum Tx speed in Mbps for disconnection stats collection
98     static final int HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS = 54;
99     // Minimum Tx packet per seconds for disconnection stats collection
100     static final int HEALTH_MONITOR_MIN_TX_PACKET_PER_SEC = 4;
101     private static final long MAX_TIME_SINCE_LAST_CONNECTION_MS = 48 * 3600_000;
102 
103 
104     private final Context mContext;
105     private final WifiConfigManager mWifiConfigManager;
106     private final WifiScoreCard mWifiScoreCard;
107     private final Clock mClock;
108     private final AlarmManager mAlarmManager;
109     private final RunnerHandler mHandler;
110     private final WifiThreadRunner mWifiThreadRunner;
111     private final WifiNative mWifiNative;
112     private final WifiInjector mWifiInjector;
113     private final DeviceConfigFacade mDeviceConfigFacade;
114     private final ActiveModeWarden mActiveModeWarden;
115     private WifiScannerInternal mScanner;
116     private MemoryStore mMemoryStore;
117     private boolean mWifiEnabled;
118     private WifiSystemInfoStats mWifiSystemInfoStats;
119     private ScanStats mFirstScanStats = new ScanStats();
120     // Detected significant increase of failure stats between daily data and historical data
121     private FailureStats mFailureStatsIncrease = new FailureStats();
122     // Detected significant decrease of failure stats between daily data and historical data
123     private FailureStats mFailureStatsDecrease = new FailureStats();
124     // Detected high failure stats from daily data without historical data
125     private FailureStats mFailureStatsHigh = new FailureStats();
126     private int mNumNetworkSufficientRecentStatsOnly = 0;
127     private int mNumNetworkSufficientRecentPrevStats = 0;
128     private boolean mHasNewDataForWifiMetrics = false;
129     private int mDeviceMobilityState = WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN;
130 
131     private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback {
132         @Override
onActiveModeManagerAdded(@onNull ActiveModeManager activeModeManager)133         public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) {
134             update();
135         }
136 
137         @Override
onActiveModeManagerRemoved(@onNull ActiveModeManager activeModeManager)138         public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) {
139             update();
140         }
141 
142         @Override
onActiveModeManagerRoleChanged(@onNull ActiveModeManager activeModeManager)143         public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) {
144             update();
145         }
146 
update()147         private void update() {
148             setWifiEnabled(mActiveModeWarden.getPrimaryClientModeManagerNullable() != null);
149         }
150     }
151 
WifiHealthMonitor(Context context, WifiInjector wifiInjector, Clock clock, WifiConfigManager wifiConfigManager, WifiScoreCard wifiScoreCard, RunnerHandler handler, WifiNative wifiNative, String l2KeySeed, DeviceConfigFacade deviceConfigFacade, ActiveModeWarden activeModeWarden)152     WifiHealthMonitor(Context context, WifiInjector wifiInjector, Clock clock,
153             WifiConfigManager wifiConfigManager, WifiScoreCard wifiScoreCard, RunnerHandler handler,
154             WifiNative wifiNative, String l2KeySeed, DeviceConfigFacade deviceConfigFacade,
155             ActiveModeWarden activeModeWarden) {
156         mContext = context;
157         mWifiInjector = wifiInjector;
158         mClock = clock;
159         mWifiConfigManager = wifiConfigManager;
160         mWifiScoreCard = wifiScoreCard;
161         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
162         mHandler = handler;
163         mWifiThreadRunner = new WifiThreadRunner(mHandler);
164         mWifiNative = wifiNative;
165         mDeviceConfigFacade = deviceConfigFacade;
166         mActiveModeWarden = activeModeWarden;
167         mWifiSystemInfoStats = new WifiSystemInfoStats(l2KeySeed);
168         mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback());
169         mHandler.postToFront(() -> mWifiConfigManager
170                 .addOnNetworkUpdateListener(new OnNetworkUpdateListener()));
171     }
172 
173     /**
174      * Enable/Disable verbose logging.
175      *
176      * @param verbose true to enable and false to disable.
177      */
enableVerboseLogging(boolean verbose)178     public void enableVerboseLogging(boolean verbose) {
179         mVerboseLoggingEnabled = verbose;
180     }
181 
182     private final AlarmManager.OnAlarmListener mDailyDetectionListener =
183             new AlarmManager.OnAlarmListener() {
184                 public void onAlarm() {
185                     dailyDetectionHandler();
186                 }
187             };
188 
189     private final AlarmManager.OnAlarmListener mPostBootDetectionListener =
190             new AlarmManager.OnAlarmListener() {
191                 public void onAlarm() {
192                     postBootDetectionHandler();
193                 }
194             };
195 
196     /**
197      * Installs a memory store, request read for post-boot detection and set up detection alarms.
198      */
installMemoryStoreSetUpDetectionAlarm(@onNull MemoryStore memoryStore)199     public void installMemoryStoreSetUpDetectionAlarm(@NonNull MemoryStore memoryStore) {
200         if (mMemoryStore == null) {
201             mMemoryStore = memoryStore;
202             Log.i(TAG, "Installing MemoryStore");
203         } else {
204             mMemoryStore = memoryStore;
205             Log.e(TAG, "Reinstalling MemoryStore");
206         }
207         requestReadForPostBootDetection();
208         setFirstHealthDetectionAlarm();
209         setPostBootDetectionAlarm();
210     }
211 
212     /**
213      * Set WiFi enable state.
214      * During the off->on transition, retrieve scanner.
215      * During the on->off transition, issue MemoryStore write to save data.
216      */
setWifiEnabled(boolean enable)217     private void setWifiEnabled(boolean enable) {
218         if (mWifiEnabled == enable) return;
219         mWifiEnabled = enable;
220         logd("Set WiFi " + (enable ? "enabled" : "disabled"));
221         if (enable) {
222             retrieveWifiScanner();
223         } else {
224             doWrites();
225         }
226     }
227 
228     /**
229      * Issue MemoryStore write. This should be called from time to time
230      * to save the state to persistent storage.
231      */
doWrites()232     public void doWrites() {
233         mWifiSystemInfoStats.writeToMemory();
234     }
235 
236     /**
237      * Set device mobility state to assist abnormal scan failure detection
238      */
setDeviceMobilityState(@eviceMobilityState int newState)239     public void setDeviceMobilityState(@DeviceMobilityState int newState) {
240         logd("Device mobility state: " + newState);
241         mDeviceMobilityState = newState;
242         mWifiSystemInfoStats.setMobilityState(newState);
243     }
244 
245     /**
246      * Get the maximum scan RSSI valid time for scan RSSI search which is done by finding
247      * the maximum RSSI found among all valid scan detail entries of each network's scanDetailCache
248      * If a scanDetail was older than the returned value, it will not be considered valid.
249      */
getScanRssiValidTimeMs()250     public int getScanRssiValidTimeMs() {
251         return (mDeviceMobilityState == WifiManager.DEVICE_MOBILITY_STATE_STATIONARY)
252                 ? mDeviceConfigFacade.getStationaryScanRssiValidTimeMs() :
253                 mDeviceConfigFacade.getNonstationaryScanRssiValidTimeMs();
254     }
255 
256     /**
257      * Issue read request to prepare for post-boot detection.
258      */
requestReadForPostBootDetection()259     private void requestReadForPostBootDetection() {
260         mWifiSystemInfoStats.readFromMemory();
261         // Potential SW change detection may require to update all networks.
262         // Thus read all networks.
263         requestReadAllNetworks();
264     }
265 
266     /**
267      * Helper method to populate WifiScanner handle. This is done lazily because
268      * WifiScanningService is started after WifiService.
269      */
retrieveWifiScanner()270     private void retrieveWifiScanner() {
271         if (mScanner != null) return;
272         mScanner = WifiLocalServices.getService(WifiScannerInternal.class);
273         if (mScanner == null) return;
274         // Register for all single scan results
275         mScanner.registerScanListener(
276                 new WifiScannerInternal.ScanListener(new ScanListener(), mWifiThreadRunner));
277     }
278 
279     /**
280      * Handle scan results when scan results come back from WiFi scanner.
281      */
handleScanResults(List<ScanDetail> scanDetails)282     private void handleScanResults(List<ScanDetail> scanDetails) {
283         ScanStats scanStats = mWifiSystemInfoStats.getCurrScanStats();
284         scanStats.clear();
285         scanStats.setLastScanTimeMs(mClock.getWallClockMillis());
286         for (ScanDetail scanDetail : scanDetails) {
287             ScanResult scanResult = scanDetail.getScanResult();
288             if (scanResult.is24GHz()) {
289                 scanStats.incrementNumBssidLastScan2g();
290             } else {
291                 scanStats.incrementNumBssidLastScanAbove2g();
292             }
293         }
294         if (mFirstScanStats.getLastScanTimeMs() == TS_NONE) {
295             mFirstScanStats.copy(scanStats);
296         }
297         mWifiSystemInfoStats.setChanged(true);
298         logd(" 2G scanResult count: " + scanStats.getNumBssidLastScan2g()
299                 + ", Above2g scanResult count: " + scanStats.getNumBssidLastScanAbove2g());
300     }
301 
setFirstHealthDetectionAlarm()302     private void setFirstHealthDetectionAlarm() {
303         long currTimeMs = mClock.getWallClockMillis();
304         Calendar calendar = Calendar.getInstance();
305         calendar.setTimeInMillis(currTimeMs);
306         calendar.set(Calendar.HOUR_OF_DAY, DAILY_DETECTION_HOUR);
307         calendar.set(Calendar.MINUTE, DAILY_DETECTION_MIN);
308         long targetTimeMs = calendar.getTimeInMillis();
309         long waitTimeMs = targetTimeMs - currTimeMs;
310         if (waitTimeMs < MIN_WAIT_TIME_BEFORE_FIRST_DETECTION_MS) {
311             waitTimeMs += DAILY_DETECTION_INTERVAL_MS;
312         }
313         scheduleDailyDetectionAlarm(waitTimeMs);
314     }
315 
scheduleDailyDetectionAlarm(long waitTimeMs)316     private void scheduleDailyDetectionAlarm(long waitTimeMs) {
317         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME,
318                 mClock.getElapsedSinceBootMillis() + waitTimeMs,
319                 DAILY_DETECTION_TIMER_TAG,
320                 mDailyDetectionListener, mHandler);
321     }
322 
setPostBootDetectionAlarm()323     private void setPostBootDetectionAlarm() {
324         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME,
325                 mClock.getElapsedSinceBootMillis() + POST_BOOT_DETECTION_WAIT_TIME_MS,
326                 POST_BOOT_DETECTION_TIMER_TAG,
327                 mPostBootDetectionListener, mHandler);
328     }
329 
330     /**
331      * Dump the internal state of WifiHealthMonitor.
332      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)333     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
334         pw.println("Dump of WifiHealthMonitor");
335         pw.println("WifiHealthMonitor - Log Begin ----");
336         pw.println("System Info Stats");
337         pw.println(mWifiSystemInfoStats);
338         pw.println("configured network connection stats");
339         List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks();
340         for (WifiConfiguration network : configuredNetworks) {
341             if (isInvalidConfiguredNetwork(network)) continue;
342             boolean isRecentlyConnected = (mClock.getWallClockMillis() - network.lastConnected)
343                     < MAX_TIME_SINCE_LAST_CONNECTION_MS;
344             PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(network.SSID);
345             int cntName = WifiScoreCard.CNT_CONNECTION_ATTEMPT;
346             if (perNetwork.getStatsCurrBuild().getCount(cntName) > 0
347                     || perNetwork.getRecentStats().getCount(cntName) > 0
348                     || isRecentlyConnected) {
349                 pw.println(mWifiScoreCard.lookupNetwork(network.SSID));
350             }
351         }
352         pw.println("networks with failure increase: ");
353         pw.println(mFailureStatsIncrease);
354         pw.println("networks with failure drop: ");
355         pw.println(mFailureStatsDecrease);
356         pw.println("networks with high failure without previous stats: ");
357         pw.println(mFailureStatsHigh);
358         pw.println("WifiHealthMonitor - Log End ----");
359     }
360 
361     /**
362      * Get current wifi mainline module long version code
363      * @Return a non-zero value if version code is available, 0 otherwise.
364      */
getWifiStackVersion()365     public long getWifiStackVersion() {
366         PackageManager packageManager = mContext.getPackageManager();
367         long wifiStackVersion = 0;
368         try {
369             ModuleInfo wifiModule = packageManager.getModuleInfo(
370                     WIFI_APEX_NAME, PackageManager.MODULE_APEX_NAME);
371             String wifiPackageName = wifiModule.getPackageName();
372             if (wifiPackageName != null) {
373                 wifiStackVersion = packageManager.getPackageInfo(
374                         wifiPackageName, PackageManager.MATCH_APEX).getLongVersionCode();
375             }
376         } catch (PackageManager.NameNotFoundException e) {
377             Log.e(TAG, " Hit PackageManager exception", e);
378         }
379         return wifiStackVersion;
380     }
381 
dailyDetectionHandler()382     private synchronized void dailyDetectionHandler() {
383         logd("Run daily detection");
384         // Clear daily detection result
385         mFailureStatsDecrease.clear();
386         mFailureStatsIncrease.clear();
387         mFailureStatsHigh.clear();
388         mNumNetworkSufficientRecentStatsOnly = 0;
389         mNumNetworkSufficientRecentPrevStats = 0;
390         mHasNewDataForWifiMetrics = true;
391         int connectionDurationSec = 0;
392         // Set the alarm for the next day
393         scheduleDailyDetectionAlarm(DAILY_DETECTION_INTERVAL_MS);
394         List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks();
395         for (WifiConfiguration network : configuredNetworks) {
396             if (isInvalidConfiguredNetwork(network)) {
397                 continue;
398             }
399             PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(network.SSID);
400 
401             int detectionFlag = perNetwork.dailyDetection(mFailureStatsDecrease,
402                     mFailureStatsIncrease, mFailureStatsHigh);
403             if (detectionFlag == WifiScoreCard.SUFFICIENT_RECENT_STATS_ONLY) {
404                 mNumNetworkSufficientRecentStatsOnly++;
405             }
406             if (detectionFlag == WifiScoreCard.SUFFICIENT_RECENT_PREV_STATS) {
407                 mNumNetworkSufficientRecentPrevStats++;
408             }
409 
410             connectionDurationSec += perNetwork.getRecentStats().getCount(
411                     WifiScoreCard.CNT_CONNECTION_DURATION_SEC);
412 
413             logd("before daily update: " + perNetwork);
414             // Update historical stats with dailyStats and clear dailyStats
415             perNetwork.updateAfterDailyDetection();
416             logd("after daily update: " + perNetwork);
417         }
418         logd("total connection duration: " + connectionDurationSec);
419         logd("#networks w/ sufficient recent stats: " + mNumNetworkSufficientRecentStatsOnly);
420         logd("#networks w/ sufficient recent and prev stats: "
421                 + mNumNetworkSufficientRecentPrevStats);
422         // Write metrics to statsd
423         writeToWifiStatsLog();
424         doWrites();
425         mWifiScoreCard.doWrites();
426     }
427 
writeToWifiStatsLog()428     private void writeToWifiStatsLog() {
429         writeToWifiStatsLogPerStats(mFailureStatsIncrease,
430                 WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__ABNORMALITY_TYPE__SIGNIFICANT_INCREASE);
431         writeToWifiStatsLogPerStats(mFailureStatsDecrease,
432                 WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__ABNORMALITY_TYPE__SIGNIFICANT_DECREASE);
433         writeToWifiStatsLogPerStats(mFailureStatsHigh,
434                 WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__ABNORMALITY_TYPE__SIMPLY_HIGH);
435     }
436 
writeToWifiStatsLogPerStats(FailureStats failureStats, int abnormalityType)437     private void writeToWifiStatsLogPerStats(FailureStats failureStats, int abnormalityType) {
438         int cntAssocRejection = failureStats.getCount(REASON_ASSOC_REJECTION);
439         if (cntAssocRejection > 0) {
440             WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType,
441                     WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_ASSOCIATION_REJECTION,
442                     cntAssocRejection);
443         }
444         int cntAssocTimeout = failureStats.getCount(REASON_ASSOC_TIMEOUT);
445         if (cntAssocTimeout > 0) {
446             WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType,
447                     WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_ASSOCIATION_TIMEOUT,
448                     cntAssocTimeout);
449         }
450         int cntAuthFailure = failureStats.getCount(REASON_AUTH_FAILURE);
451         if (cntAuthFailure > 0) {
452             WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType,
453                     WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_AUTHENTICATION,
454                     cntAuthFailure);
455         }
456         int cntConnectionFailure = failureStats.getCount(REASON_CONNECTION_FAILURE);
457         if (cntConnectionFailure > 0) {
458             WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType,
459                     WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_CONNECTION,
460                     cntConnectionFailure);
461         }
462         int cntDisconnectionNonlocal =  failureStats.getCount(REASON_DISCONNECTION_NONLOCAL);
463         if (cntDisconnectionNonlocal > 0) {
464             WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType,
465                     WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_NON_LOCAL_DISCONNECTION,
466                     cntDisconnectionNonlocal);
467         }
468         int cntShortConnectionNonlocal = failureStats.getCount(REASON_SHORT_CONNECTION_NONLOCAL);
469         if (cntShortConnectionNonlocal > 0) {
470             WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType,
471                     WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_SHORT_CONNECTION_DUE_TO_NON_LOCAL_DISCONNECTION,
472                     cntShortConnectionNonlocal);
473         }
474     }
475 
476     /**
477      * Build HealthMonitor proto for WifiMetrics
478      * @return counts of networks with significant connection failure stats if there is a new
479      * detection, or a empty proto with default values if there is no new detection
480      */
buildProto()481     public synchronized HealthMonitorMetrics buildProto() {
482         if (!mHasNewDataForWifiMetrics) return null;
483         HealthMonitorMetrics metrics = new HealthMonitorMetrics();
484         metrics.failureStatsIncrease = failureStatsToProto(mFailureStatsIncrease);
485         metrics.failureStatsDecrease = failureStatsToProto(mFailureStatsDecrease);
486         metrics.failureStatsHigh = failureStatsToProto(mFailureStatsHigh);
487 
488         metrics.numNetworkSufficientRecentStatsOnly = mNumNetworkSufficientRecentStatsOnly;
489         metrics.numNetworkSufficientRecentPrevStats = mNumNetworkSufficientRecentPrevStats;
490         mHasNewDataForWifiMetrics = false;
491         return metrics;
492     }
493 
failureStatsToProto(FailureStats failureStats)494     private HealthMonitorFailureStats failureStatsToProto(FailureStats failureStats) {
495         HealthMonitorFailureStats stats = new HealthMonitorFailureStats();
496         stats.cntAssocRejection = failureStats.getCount(REASON_ASSOC_REJECTION);
497         stats.cntAssocTimeout = failureStats.getCount(REASON_ASSOC_TIMEOUT);
498         stats.cntAuthFailure = failureStats.getCount(REASON_AUTH_FAILURE);
499         stats.cntConnectionFailure = failureStats.getCount(REASON_CONNECTION_FAILURE);
500         stats.cntDisconnectionNonlocalConnecting =
501                 failureStats.getCount(REASON_CONNECTION_FAILURE_DISCONNECTION);
502         stats.cntDisconnectionNonlocal =
503                 failureStats.getCount(REASON_DISCONNECTION_NONLOCAL);
504         stats.cntShortConnectionNonlocal =
505                 failureStats.getCount(REASON_SHORT_CONNECTION_NONLOCAL);
506         return stats;
507     }
508 
isInvalidConfiguredNetwork(WifiConfiguration config)509     private boolean isInvalidConfiguredNetwork(WifiConfiguration config) {
510         return (config == null || WifiManager.UNKNOWN_SSID.equals(config.SSID)
511                 || config.SSID == null);
512     }
513 
postBootDetectionHandler()514     private void postBootDetectionHandler() {
515         logd("Run post-boot detection");
516         postBootSwBuildCheck();
517         mWifiSystemInfoStats.postBootAbnormalScanDetection(mFirstScanStats);
518         logd(" postBootAbnormalScanDetection: " + mWifiSystemInfoStats.getScanFailure());
519         // TODO: Check if scan is not empty but all high RSSI connection attempts failed
520         //  while connection attempt with the same network succeeded before boot.
521         doWrites();
522     }
523 
postBootSwBuildCheck()524     private void postBootSwBuildCheck() {
525         WifiSoftwareBuildInfo currSoftwareBuildInfo = extractCurrentSoftwareBuildInfo();
526         if (currSoftwareBuildInfo == null) return;
527         logd(currSoftwareBuildInfo.toString());
528 
529         mWifiSystemInfoStats.finishPendingRead();
530         if (mWifiSystemInfoStats.getCurrSoftwareBuildInfo() == null) {
531             logd("Miss current software build info from memory");
532             mWifiSystemInfoStats.setCurrSoftwareBuildInfo(currSoftwareBuildInfo);
533             return;
534         }
535         if (mWifiSystemInfoStats.detectSwBuildChange(currSoftwareBuildInfo)) {
536             logd("Detect SW build change");
537             updateAllNetworkAfterSwBuildChange();
538             mWifiSystemInfoStats.updateBuildInfoAfterSwBuildChange(currSoftwareBuildInfo);
539         } else {
540             logd("Detect no SW build change");
541         }
542     }
543 
544     /**
545      * Issue NetworkStats read request for all configured networks.
546      */
requestReadAllNetworks()547     private void requestReadAllNetworks() {
548         List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks();
549         for (WifiConfiguration network : configuredNetworks) {
550             if (isInvalidConfiguredNetwork(network)) {
551                 continue;
552             }
553             logd(network.SSID);
554             WifiScoreCard.PerNetwork perNetwork = mWifiScoreCard.fetchByNetwork(network.SSID);
555             if (perNetwork == null) {
556                 // This network is not in cache. Move it to cache and read it out from MemoryStore.
557                 mWifiScoreCard.lookupNetwork(network.SSID);
558             } else {
559                 // This network is already in cache before memoryStore is stalled.
560                 mWifiScoreCard.requestReadNetwork(perNetwork);
561             }
562         }
563     }
564 
565     /**
566      * Update NetworkStats of all configured networks after a SW build change is detected
567      */
updateAllNetworkAfterSwBuildChange()568     private void updateAllNetworkAfterSwBuildChange() {
569         List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks();
570         for (WifiConfiguration network : configuredNetworks) {
571             if (isInvalidConfiguredNetwork(network)) {
572                 continue;
573             }
574             logd(network.SSID);
575             WifiScoreCard.PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(network.SSID);
576 
577             logd("before SW build update: " + perNetwork);
578             perNetwork.updateAfterSwBuildChange();
579             logd("after SW build update: " + perNetwork);
580         }
581     }
582 
583     /**
584      * Extract current software build information from the running software.
585      */
extractCurrentSoftwareBuildInfo()586     private WifiSoftwareBuildInfo extractCurrentSoftwareBuildInfo() {
587         if (!mWifiEnabled) {
588             return null;
589         }
590         long wifiStackVersion = getWifiStackVersion();
591         String osBuildVersion = replaceNullByEmptyString(Build.DISPLAY);
592         if (mWifiNative == null) {
593             return null;
594         }
595         String driverVersion = replaceNullByEmptyString(mWifiNative.getDriverVersion());
596         String firmwareVersion = replaceNullByEmptyString(mWifiNative.getFirmwareVersion());
597         return (new WifiSoftwareBuildInfo(osBuildVersion,
598                 wifiStackVersion, driverVersion, firmwareVersion));
599     }
600 
replaceNullByEmptyString(String str)601     private String replaceNullByEmptyString(String str) {
602         return str == null ? "" : str;
603     }
604 
605     /**
606      * Clears the internal state.
607      * This is called in response to a factoryReset call from Settings.
608      */
clear()609     public void clear() {
610         mWifiSystemInfoStats.clearAll();
611     }
612 
613     public static final int REASON_NO_FAILURE = -1;
614     public static final int REASON_ASSOC_REJECTION = 0;
615     public static final int REASON_ASSOC_TIMEOUT = 1;
616     public static final int REASON_AUTH_FAILURE = 2;
617     public static final int REASON_CONNECTION_FAILURE = 3;
618     public static final int REASON_DISCONNECTION_NONLOCAL = 4;
619     public static final int REASON_SHORT_CONNECTION_NONLOCAL = 5;
620     public static final int REASON_CONNECTION_FAILURE_DISCONNECTION = 6;
621     public static final int NUMBER_FAILURE_REASON_CODE = 7;
622     public static final String[] FAILURE_REASON_NAME = {
623             "association rejection failure",
624             "association timeout failure",
625             "authentication failure",
626             "connection failure",
627             "disconnection",
628             "short connection",
629             "connection failure disconnection",
630     };
631     @IntDef(prefix = { "REASON_" }, value = {
632         REASON_NO_FAILURE,
633         REASON_ASSOC_REJECTION,
634         REASON_ASSOC_TIMEOUT,
635         REASON_AUTH_FAILURE,
636         REASON_CONNECTION_FAILURE,
637         REASON_DISCONNECTION_NONLOCAL,
638         REASON_SHORT_CONNECTION_NONLOCAL,
639         REASON_CONNECTION_FAILURE_DISCONNECTION
640     })
641     @Retention(RetentionPolicy.SOURCE)
642     public @interface FailureReasonCode {}
643 
644     /**
645      * A class maintaining the number of networks with high failure rate or
646      * with a significant change of failure rate
647      */
648     public static class FailureStats {
649         private final int[] mCount = new int[NUMBER_FAILURE_REASON_CODE];
clear()650         void clear() {
651             for (int i = 0; i < NUMBER_FAILURE_REASON_CODE; i++) {
652                 mCount[i] = 0;
653             }
654         }
655 
getCount(@ailureReasonCode int reasonCode)656         int getCount(@FailureReasonCode int reasonCode) {
657             return mCount[reasonCode];
658         }
659 
setCount(@ailureReasonCode int reasonCode, int cnt)660         void setCount(@FailureReasonCode int reasonCode, int cnt) {
661             mCount[reasonCode] = cnt;
662         }
663 
incrementCount(@ailureReasonCode int reasonCode)664         void incrementCount(@FailureReasonCode int reasonCode) {
665             mCount[reasonCode]++;
666         }
667 
668         @Override
toString()669         public String toString() {
670             StringBuilder sb = new StringBuilder();
671             for (int i = 0; i < NUMBER_FAILURE_REASON_CODE; i++) {
672                 if (mCount[i] == 0) continue;
673                 sb.append(FAILURE_REASON_NAME[i]).append(": ").append(mCount[i]).append(" ");
674             }
675             return sb.toString();
676         }
677     }
678     /**
679      * A class maintaining current OS, Wifi APK, Wifi driver and firmware build version information.
680      */
681     final class WifiSoftwareBuildInfo {
682         private String mOsBuildVersion;
683         private long mWifiStackVersion;
684         private String mWifiDriverVersion;
685         private String mWifiFirmwareVersion;
WifiSoftwareBuildInfo(@onNull String osBuildVersion, long wifiStackVersion, @NonNull String wifiDriverVersion, @NonNull String wifiFirmwareVersion)686         WifiSoftwareBuildInfo(@NonNull String osBuildVersion, long wifiStackVersion,
687                 @NonNull String wifiDriverVersion, @NonNull String wifiFirmwareVersion) {
688             mOsBuildVersion = osBuildVersion;
689             mWifiStackVersion = wifiStackVersion;
690             mWifiDriverVersion = wifiDriverVersion;
691             mWifiFirmwareVersion = wifiFirmwareVersion;
692         }
WifiSoftwareBuildInfo(@onNull WifiSoftwareBuildInfo wifiSoftwareBuildInfo)693         WifiSoftwareBuildInfo(@NonNull WifiSoftwareBuildInfo wifiSoftwareBuildInfo) {
694             mOsBuildVersion = wifiSoftwareBuildInfo.getOsBuildVersion();
695             mWifiStackVersion = wifiSoftwareBuildInfo.getWifiStackVersion();
696             mWifiDriverVersion = wifiSoftwareBuildInfo.getWifiDriverVersion();
697             mWifiFirmwareVersion = wifiSoftwareBuildInfo.getWifiFirmwareVersion();
698         }
getOsBuildVersion()699         String getOsBuildVersion() {
700             return mOsBuildVersion;
701         }
getWifiStackVersion()702         long getWifiStackVersion() {
703             return mWifiStackVersion;
704         }
getWifiDriverVersion()705         String getWifiDriverVersion() {
706             return mWifiDriverVersion;
707         }
getWifiFirmwareVersion()708         String getWifiFirmwareVersion() {
709             return mWifiFirmwareVersion;
710         }
711         @Override
equals(Object otherObj)712         public boolean equals(Object otherObj) {
713             if (this == otherObj) {
714                 return true;
715             }
716             if (!(otherObj instanceof WifiSoftwareBuildInfo)) {
717                 return false;
718             }
719             if (otherObj == null) {
720                 return false;
721             }
722             WifiSoftwareBuildInfo other = (WifiSoftwareBuildInfo) otherObj;
723             return mOsBuildVersion.equals(other.getOsBuildVersion())
724                     && mWifiStackVersion == other.getWifiStackVersion()
725                     && mWifiDriverVersion.equals(other.getWifiDriverVersion())
726                     && mWifiFirmwareVersion.equals(other.getWifiFirmwareVersion());
727         }
728         @Override
toString()729         public String toString() {
730             StringBuilder sb = new StringBuilder();
731             sb.append("OS build version: ");
732             sb.append(mOsBuildVersion);
733             sb.append(" Wifi stack version: ");
734             sb.append(mWifiStackVersion);
735             sb.append(" Wifi driver version: ");
736             sb.append(mWifiDriverVersion);
737             sb.append(" Wifi firmware version: ");
738             sb.append(mWifiFirmwareVersion);
739             return sb.toString();
740         }
741     }
742 
743     /**
744      * A class maintaining various WiFi system information and statistics.
745      */
746     final class WifiSystemInfoStats extends MemoryStoreAccessBase {
747         private WifiSoftwareBuildInfo mCurrSoftwareBuildInfo;
748         private WifiSoftwareBuildInfo mPrevSoftwareBuildInfo;
749         private ScanStats mCurrScanStats = new ScanStats();
750         private ScanStats mPrevScanStats = new ScanStats();
751         private int mScanFailure;
752         private @DeviceMobilityState int mMobilityState;
753         private boolean mChanged = false;
WifiSystemInfoStats(String l2KeySeed)754         WifiSystemInfoStats(String l2KeySeed) {
755             super(WifiScoreCard.computeHashLong(
756                     "", MacAddress.fromString(DEFAULT_MAC_ADDRESS), l2KeySeed));
757         }
758 
getCurrScanStats()759         ScanStats getCurrScanStats() {
760             return mCurrScanStats;
761         }
762 
setChanged(boolean changed)763         void setChanged(boolean changed) {
764             mChanged = changed;
765         }
766 
setCurrSoftwareBuildInfo(WifiSoftwareBuildInfo currSoftwareBuildInfo)767         void setCurrSoftwareBuildInfo(WifiSoftwareBuildInfo currSoftwareBuildInfo) {
768             mCurrSoftwareBuildInfo = currSoftwareBuildInfo;
769             mChanged = true;
770         }
771 
setMobilityState(@eviceMobilityState int mobilityState)772         void setMobilityState(@DeviceMobilityState int mobilityState) {
773             mMobilityState = mobilityState;
774         }
775 
getCurrSoftwareBuildInfo()776         WifiSoftwareBuildInfo getCurrSoftwareBuildInfo() {
777             return mCurrSoftwareBuildInfo;
778         }
779 
getPrevSoftwareBuildInfo()780         WifiSoftwareBuildInfo getPrevSoftwareBuildInfo() {
781             return mPrevSoftwareBuildInfo;
782         }
783 
clearAll()784         void clearAll() {
785             mCurrSoftwareBuildInfo = null;
786             mPrevSoftwareBuildInfo = null;
787             mCurrScanStats.clear();
788             mPrevScanStats.clear();
789             mChanged = true;
790         }
791 
792         /**
793          * Detect if there is a SW build change by comparing current SW build version vs. SW build
794          * version previously saved in MemoryStore.
795          * @param currSoftwareBuildInfo is current SW build info derived from running SW
796          * @return true if a SW build change is detected, false if no change is detected.
797          */
detectSwBuildChange(@onNull WifiSoftwareBuildInfo currSoftwareBuildInfo)798         boolean detectSwBuildChange(@NonNull WifiSoftwareBuildInfo currSoftwareBuildInfo) {
799             if (mCurrSoftwareBuildInfo == null) {
800                 return false;
801             }
802 
803             logd(" from Memory: " + mCurrSoftwareBuildInfo);
804             logd(" from SW: " + currSoftwareBuildInfo);
805             return (!mCurrSoftwareBuildInfo.equals(currSoftwareBuildInfo));
806         }
807 
updateBuildInfoAfterSwBuildChange(@onNull WifiSoftwareBuildInfo currBuildInfo)808         void updateBuildInfoAfterSwBuildChange(@NonNull WifiSoftwareBuildInfo currBuildInfo) {
809             mPrevSoftwareBuildInfo = new WifiSoftwareBuildInfo(mCurrSoftwareBuildInfo);
810             mCurrSoftwareBuildInfo = new WifiSoftwareBuildInfo(currBuildInfo);
811             mChanged = true;
812         }
813 
readFromMemory()814         void readFromMemory() {
815             if (mMemoryStore != null) {
816                 mMemoryStore.read(getL2Key(), SYSTEM_INFO_DATA_NAME,
817                         (value) -> readBackListener(value));
818             }
819         }
820 
821         // Read may not be completed in theory when finishPendingRead() is called.
822         // Currently it relies on the fact that memory read is issued right after boot complete
823         // while finishPendingRead() is called only POST_BOOT_DETECTION_WAIT_TIME_MS after that.
finishPendingRead()824         private void finishPendingRead() {
825             final byte[] serialized = finishPendingReadBytes();
826             if (serialized == null) {
827                 logd("Fail to read systemInfoStats from memory");
828                 return;
829             }
830             SystemInfoStats systemInfoStats;
831             try {
832                 systemInfoStats = SystemInfoStats.parseFrom(serialized);
833             } catch (InvalidProtocolBufferException e) {
834                 Log.e(TAG, "Failed to deserialize", e);
835                 return;
836             }
837             readFromMemory(systemInfoStats);
838         }
839 
readFromMemory(@onNull SystemInfoStats systemInfoStats)840         private void readFromMemory(@NonNull SystemInfoStats systemInfoStats) {
841             if (systemInfoStats.hasCurrSoftwareBuildInfo()) {
842                 mCurrSoftwareBuildInfo = fromSoftwareBuildInfo(
843                         systemInfoStats.getCurrSoftwareBuildInfo());
844             }
845             if (systemInfoStats.hasPrevSoftwareBuildInfo()) {
846                 mPrevSoftwareBuildInfo = fromSoftwareBuildInfo(
847                         systemInfoStats.getPrevSoftwareBuildInfo());
848             }
849             if (systemInfoStats.hasNumBssidLastScan2G()) {
850                 mPrevScanStats.setNumBssidLastScan2g(systemInfoStats.getNumBssidLastScan2G());
851             }
852             if (systemInfoStats.hasNumBssidLastScanAbove2G()) {
853                 mPrevScanStats.setNumBssidLastScanAbove2g(systemInfoStats
854                         .getNumBssidLastScanAbove2G());
855             }
856             if (systemInfoStats.hasLastScanTimeMs()) {
857                 mPrevScanStats.setLastScanTimeMs(systemInfoStats.getLastScanTimeMs());
858             }
859         }
860 
writeToMemory()861         void writeToMemory() {
862             if (mMemoryStore == null || !mChanged) return;
863             byte[] serialized = toSystemInfoStats().toByteArray();
864             mMemoryStore.write(getL2Key(), SYSTEM_INFO_DATA_NAME, serialized);
865             mChanged = false;
866         }
867 
toSystemInfoStats()868         SystemInfoStats toSystemInfoStats() {
869             SystemInfoStats.Builder builder = SystemInfoStats.newBuilder();
870             if (mCurrSoftwareBuildInfo != null) {
871                 builder.setCurrSoftwareBuildInfo(toSoftwareBuildInfo(mCurrSoftwareBuildInfo));
872             }
873             if (mPrevSoftwareBuildInfo != null) {
874                 builder.setPrevSoftwareBuildInfo(toSoftwareBuildInfo(mPrevSoftwareBuildInfo));
875             }
876             builder.setLastScanTimeMs(mCurrScanStats.getLastScanTimeMs());
877             builder.setNumBssidLastScan2G(mCurrScanStats.getNumBssidLastScan2g());
878             builder.setNumBssidLastScanAbove2G(mCurrScanStats.getNumBssidLastScanAbove2g());
879             return builder.build();
880         }
881 
toSoftwareBuildInfo( @onNull WifiSoftwareBuildInfo softwareBuildInfo)882         private SoftwareBuildInfo toSoftwareBuildInfo(
883                 @NonNull WifiSoftwareBuildInfo softwareBuildInfo) {
884             SoftwareBuildInfo.Builder builder = SoftwareBuildInfo.newBuilder();
885             builder.setOsBuildVersion(softwareBuildInfo.getOsBuildVersion());
886             builder.setWifiStackVersion(softwareBuildInfo.getWifiStackVersion());
887             builder.setWifiDriverVersion(softwareBuildInfo.getWifiDriverVersion());
888             builder.setWifiFirmwareVersion(softwareBuildInfo.getWifiFirmwareVersion());
889             return builder.build();
890         }
891 
fromSoftwareBuildInfo( @onNull SoftwareBuildInfo softwareBuildInfo)892         WifiSoftwareBuildInfo fromSoftwareBuildInfo(
893                 @NonNull SoftwareBuildInfo softwareBuildInfo) {
894             String osBuildVersion = softwareBuildInfo.hasOsBuildVersion()
895                     ? softwareBuildInfo.getOsBuildVersion() : "NA";
896             long stackVersion = softwareBuildInfo.hasWifiStackVersion()
897                     ? softwareBuildInfo.getWifiStackVersion() : 0;
898             String driverVersion = softwareBuildInfo.hasWifiDriverVersion()
899                     ? softwareBuildInfo.getWifiDriverVersion() : "NA";
900             String firmwareVersion = softwareBuildInfo.hasWifiFirmwareVersion()
901                     ? softwareBuildInfo.getWifiFirmwareVersion() : "NA";
902             return new WifiSoftwareBuildInfo(osBuildVersion, stackVersion,
903                     driverVersion, firmwareVersion);
904         }
905         /**
906          *  Detect pre-boot or post-boot detection failure.
907          *  @return 0 if no failure is found or a positive integer if failure is found where
908          *  b0 for pre-boot 2G scan failure
909          *  b1 for pre-boot Above2g scan failure
910          *  b2 for post-boot 2G scan failure
911          *  b3 for post-boot Above2g scan failure
912          */
postBootAbnormalScanDetection(ScanStats firstScanStats)913         void postBootAbnormalScanDetection(ScanStats firstScanStats) {
914             long preBootScanTimeMs = mPrevScanStats.getLastScanTimeMs();
915             long postBootScanTimeMs = firstScanStats.getLastScanTimeMs();
916             logd(" preBootScanTimeMs: " + preBootScanTimeMs);
917             logd(" postBootScanTimeMs: " + postBootScanTimeMs);
918             int preBootNumBssid2g = mPrevScanStats.getNumBssidLastScan2g();
919             int preBootNumBssidAbove2g = mPrevScanStats.getNumBssidLastScanAbove2g();
920             int postBootNumBssid2g = firstScanStats.getNumBssidLastScan2g();
921             int postBootNumBssidAbove2g = firstScanStats.getNumBssidLastScanAbove2g();
922             logd(" preBootScan 2G count: " + preBootNumBssid2g
923                     + ", Above2G count: " + preBootNumBssidAbove2g);
924             logd(" postBootScan 2G count: " + postBootNumBssid2g
925                     + ", Above2G count: " + postBootNumBssidAbove2g);
926             mScanFailure = 0;
927             if (postBootScanTimeMs == TS_NONE || preBootScanTimeMs == TS_NONE) return;
928             if ((postBootScanTimeMs - preBootScanTimeMs) > MAX_INTERVAL_BETWEEN_TWO_SCAN_MS) {
929                 return;
930             }
931             if (preBootNumBssid2g == 0 && postBootNumBssid2g >= MIN_NUM_BSSID_SCAN_2G) {
932                 mScanFailure += 1;
933             }
934             if (preBootNumBssidAbove2g == 0 && postBootNumBssidAbove2g
935                     >= MIN_NUM_BSSID_SCAN_ABOVE_2G) {
936                 mScanFailure += 2;
937             }
938             if (postBootNumBssid2g == 0 && preBootNumBssid2g >= MIN_NUM_BSSID_SCAN_2G) {
939                 mScanFailure += 4;
940             }
941             if (postBootNumBssidAbove2g == 0 && preBootNumBssidAbove2g
942                     >= MIN_NUM_BSSID_SCAN_ABOVE_2G) {
943                 mScanFailure += 8;
944             }
945         }
946 
getScanFailure()947         int getScanFailure() {
948             return mScanFailure;
949         }
950 
951         @Override
toString()952         public String toString() {
953             StringBuilder sb = new StringBuilder();
954             if (mCurrSoftwareBuildInfo != null) {
955                 sb.append("current SW build: ");
956                 sb.append(mCurrSoftwareBuildInfo);
957             }
958             if (mPrevSoftwareBuildInfo != null) {
959                 sb.append("\n");
960                 sb.append("previous SW build: ");
961                 sb.append(mPrevSoftwareBuildInfo);
962             }
963             sb.append("\n");
964             sb.append("currScanStats: ");
965             sb.append(mCurrScanStats);
966             sb.append("\n");
967             sb.append("prevScanStats: ");
968             sb.append(mPrevScanStats);
969             return sb.toString();
970         }
971     }
972 
973     final class ScanStats {
974         private long mLastScanTimeMs = TS_NONE;
975         private int mNumBssidLastScan2g;
976         private int mNumBssidLastScanAbove2g;
copy(ScanStats source)977         void copy(ScanStats source) {
978             mLastScanTimeMs = source.mLastScanTimeMs;
979             mNumBssidLastScan2g = source.mNumBssidLastScan2g;
980             mNumBssidLastScanAbove2g = source.mNumBssidLastScanAbove2g;
981         }
setLastScanTimeMs(long lastScanTimeMs)982         void setLastScanTimeMs(long lastScanTimeMs) {
983             mLastScanTimeMs = lastScanTimeMs;
984         }
setNumBssidLastScan2g(int numBssidLastScan2g)985         void setNumBssidLastScan2g(int numBssidLastScan2g) {
986             mNumBssidLastScan2g = numBssidLastScan2g;
987         }
setNumBssidLastScanAbove2g(int numBssidLastScanAbove2g)988         void setNumBssidLastScanAbove2g(int numBssidLastScanAbove2g) {
989             mNumBssidLastScanAbove2g = numBssidLastScanAbove2g;
990         }
getLastScanTimeMs()991         long getLastScanTimeMs() {
992             return mLastScanTimeMs;
993         }
getNumBssidLastScan2g()994         int getNumBssidLastScan2g() {
995             return mNumBssidLastScan2g;
996         }
getNumBssidLastScanAbove2g()997         int getNumBssidLastScanAbove2g() {
998             return mNumBssidLastScanAbove2g;
999         }
incrementNumBssidLastScan2g()1000         void incrementNumBssidLastScan2g() {
1001             mNumBssidLastScan2g++;
1002         }
incrementNumBssidLastScanAbove2g()1003         void incrementNumBssidLastScanAbove2g() {
1004             mNumBssidLastScanAbove2g++;
1005         }
clear()1006         void clear() {
1007             mLastScanTimeMs = TS_NONE;
1008             mNumBssidLastScan2g = 0;
1009             mNumBssidLastScanAbove2g = 0;
1010         }
1011         @Override
toString()1012         public String toString() {
1013             StringBuilder sb = new StringBuilder();
1014             sb.append("last scan time: ");
1015             sb.append(mLastScanTimeMs);
1016             sb.append(" APs found at 2G: ");
1017             sb.append(mNumBssidLastScan2g);
1018             sb.append(" APs found above 2g: ");
1019             sb.append(mNumBssidLastScanAbove2g);
1020             return sb.toString();
1021         }
1022     }
1023 
1024     @VisibleForTesting
getWifiSystemInfoStats()1025     WifiSystemInfoStats getWifiSystemInfoStats() {
1026         return mWifiSystemInfoStats;
1027     }
1028 
logd(String string)1029     private void logd(String string) {
1030         if (mVerboseLoggingEnabled) {
1031             Log.d(TAG, string, null);
1032         }
1033     }
1034 
1035     /**
1036      * Listener for config manager network config related events.
1037      */
1038     private class OnNetworkUpdateListener implements
1039             WifiConfigManager.OnNetworkUpdateListener {
1040 
1041         @Override
onNetworkAdded(WifiConfiguration config)1042         public void onNetworkAdded(WifiConfiguration config) {
1043             if (config == null) return;
1044             mWifiScoreCard.lookupNetwork(config.SSID);
1045         }
1046 
1047         @Override
onNetworkRemoved(WifiConfiguration config)1048         public void onNetworkRemoved(WifiConfiguration config) {
1049             if (config == null || (config.fromWifiNetworkSuggestion && !config.isPasspoint())) {
1050                 // If a suggestion non-passpoint network is removed from wifiConfigManager do not
1051                 // remove the ScoreCard. That will be removed when suggestion is removed.
1052                 return;
1053             }
1054             mWifiScoreCard.removeNetwork(config.SSID);
1055         }
1056     }
1057 
1058     /**
1059      *  Scan listener for any full band scan.
1060      */
1061     private class ScanListener implements WifiScanner.ScanListener {
1062         private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
1063 
clearScanDetails()1064         public void clearScanDetails() {
1065             mScanDetails.clear();
1066         }
1067 
1068         @Override
onSuccess()1069         public void onSuccess() {
1070         }
1071 
1072         @Override
onFailure(int reason, String description)1073         public void onFailure(int reason, String description) {
1074             logd("registerScanListener onFailure:"
1075                     + " reason: " + reason + " description: " + description);
1076         }
1077 
1078         @Override
onPeriodChanged(int periodInMs)1079         public void onPeriodChanged(int periodInMs) {
1080         }
1081 
1082         @Override
onResults(WifiScanner.ScanData[] results)1083         public void onResults(WifiScanner.ScanData[] results) {
1084             if (!mWifiEnabled || results == null || results.length == 0) {
1085                 clearScanDetails();
1086                 return;
1087             }
1088 
1089             if (WifiScanner.isFullBandScan(results[0].getScannedBandsInternal(), true)) {
1090                 handleScanResults(mScanDetails);
1091             }
1092             clearScanDetails();
1093         }
1094 
1095         @Override
onFullResult(ScanResult fullScanResult)1096         public void onFullResult(ScanResult fullScanResult) {
1097             if (!mWifiEnabled) {
1098                 return;
1099             }
1100             mScanDetails.add(new ScanDetail(fullScanResult));
1101         }
1102     }
1103 }
1104