/* * Copyright 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi; import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS; import static com.android.server.wifi.WifiScoreCard.TS_NONE; import android.annotation.IntDef; import android.annotation.NonNull; import android.app.AlarmManager; import android.content.Context; import android.content.pm.ModuleInfo; import android.content.pm.PackageManager; import android.net.MacAddress; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.DeviceMobilityState; import android.net.wifi.WifiScanner; import android.os.Build; import android.os.Handler; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.WifiScoreCard.MemoryStore; import com.android.server.wifi.WifiScoreCard.MemoryStoreAccessBase; import com.android.server.wifi.WifiScoreCard.PerNetwork; import com.android.server.wifi.proto.WifiScoreCardProto.SoftwareBuildInfo; import com.android.server.wifi.proto.WifiScoreCardProto.SystemInfoStats; import com.android.server.wifi.proto.WifiStatsLog; import com.android.server.wifi.proto.nano.WifiMetricsProto.HealthMonitorFailureStats; import com.android.server.wifi.proto.nano.WifiMetricsProto.HealthMonitorMetrics; import com.android.server.wifi.util.ScanResultUtil; import com.google.protobuf.InvalidProtocolBufferException; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import javax.annotation.concurrent.NotThreadSafe; /** * Monitor and detect potential WiFi health issue when RSSI is sufficiently high. * There are two detections, daily detection and post-boot detection. * Post-boot detection is to detect abnormal scan/connection behavior change after device reboot * and/or SW build change. * Daily detection is to detect connection and other behavior changes especially after SW change. */ @NotThreadSafe public class WifiHealthMonitor { private static final String TAG = "WifiHealthMonitor"; private boolean mVerboseLoggingEnabled = false; public static final String DAILY_DETECTION_TIMER_TAG = "WifiHealthMonitor Schedule Daily Detection Timer"; public static final String POST_BOOT_DETECTION_TIMER_TAG = "WifiHealthMonitor Schedule Post-Boot Detection Timer"; // Package name of WiFi mainline module found from the following adb command // adb shell pm list packages --apex-only| grep wifi private static final String WIFI_APEX_NAME = "com.android.wifi"; private static final String SYSTEM_INFO_DATA_NAME = "systemInfoData"; // The time that device waits after device boot before triggering post-boot detection. // This needs be long enough so that memory read can complete before post-boot detection. private static final int POST_BOOT_DETECTION_WAIT_TIME_MS = 25_000; // The time interval between two daily detections private static final long DAILY_DETECTION_INTERVAL_MS = 24 * 3600_000; public static final int DAILY_DETECTION_HOUR = 23; private static final int DAILY_DETECTION_MIN = 00; private static final long MIN_WAIT_TIME_BEFORE_FIRST_DETECTION_MS = 100_000; // Max interval between pre-boot scan and post-boot scan to qualify post-boot scan detection private static final long MAX_INTERVAL_BETWEEN_TWO_SCAN_MS = 60_000; // The minimum number of BSSIDs that should be found during a normal scan to trigger detection // of an abnormal scan which happens either before or after the normal scan within a short time. // Minimum number of BSSIDs found at 2G with a normal scan private static final int MIN_NUM_BSSID_SCAN_2G = 2; // Minimum number of BSSIDs found above 2G with a normal scan private static final int MIN_NUM_BSSID_SCAN_ABOVE_2G = 2; // Minimum Tx speed in Mbps for disconnection stats collection static final int HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS = 54; // Minimum Tx packet per seconds for disconnection stats collection static final int HEALTH_MONITOR_MIN_TX_PACKET_PER_SEC = 4; private static final long MAX_TIME_SINCE_LAST_CONNECTION_MS = 48 * 3600_000; private final Context mContext; private final WifiConfigManager mWifiConfigManager; private final WifiScoreCard mWifiScoreCard; private final Clock mClock; private final AlarmManager mAlarmManager; private final Handler mHandler; private final WifiNative mWifiNative; private final WifiInjector mWifiInjector; private final DeviceConfigFacade mDeviceConfigFacade; private final ActiveModeWarden mActiveModeWarden; private WifiScanner mScanner; private MemoryStore mMemoryStore; private boolean mWifiEnabled; private WifiSystemInfoStats mWifiSystemInfoStats; private ScanStats mFirstScanStats = new ScanStats(); // Detected significant increase of failure stats between daily data and historical data private FailureStats mFailureStatsIncrease = new FailureStats(); // Detected significant decrease of failure stats between daily data and historical data private FailureStats mFailureStatsDecrease = new FailureStats(); // Detected high failure stats from daily data without historical data private FailureStats mFailureStatsHigh = new FailureStats(); private int mNumNetworkSufficientRecentStatsOnly = 0; private int mNumNetworkSufficientRecentPrevStats = 0; private boolean mHasNewDataForWifiMetrics = false; private int mDeviceMobilityState = WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN; private class ModeChangeCallback implements ActiveModeWarden.ModeChangeCallback { @Override public void onActiveModeManagerAdded(@NonNull ActiveModeManager activeModeManager) { update(); } @Override public void onActiveModeManagerRemoved(@NonNull ActiveModeManager activeModeManager) { update(); } @Override public void onActiveModeManagerRoleChanged(@NonNull ActiveModeManager activeModeManager) { update(); } private void update() { setWifiEnabled(mActiveModeWarden.getPrimaryClientModeManagerNullable() != null); } } WifiHealthMonitor(Context context, WifiInjector wifiInjector, Clock clock, WifiConfigManager wifiConfigManager, WifiScoreCard wifiScoreCard, Handler handler, WifiNative wifiNative, String l2KeySeed, DeviceConfigFacade deviceConfigFacade, ActiveModeWarden activeModeWarden) { mContext = context; mWifiInjector = wifiInjector; mClock = clock; mWifiConfigManager = wifiConfigManager; mWifiScoreCard = wifiScoreCard; mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); mHandler = handler; mWifiNative = wifiNative; mDeviceConfigFacade = deviceConfigFacade; mActiveModeWarden = activeModeWarden; mWifiSystemInfoStats = new WifiSystemInfoStats(l2KeySeed); mWifiConfigManager.addOnNetworkUpdateListener(new OnNetworkUpdateListener()); mActiveModeWarden.registerModeChangeCallback(new ModeChangeCallback()); } /** * Enable/Disable verbose logging. * * @param verbose true to enable and false to disable. */ public void enableVerboseLogging(boolean verbose) { mVerboseLoggingEnabled = verbose; } private final AlarmManager.OnAlarmListener mDailyDetectionListener = new AlarmManager.OnAlarmListener() { public void onAlarm() { dailyDetectionHandler(); } }; private final AlarmManager.OnAlarmListener mPostBootDetectionListener = new AlarmManager.OnAlarmListener() { public void onAlarm() { postBootDetectionHandler(); } }; /** * Installs a memory store, request read for post-boot detection and set up detection alarms. */ public void installMemoryStoreSetUpDetectionAlarm(@NonNull MemoryStore memoryStore) { if (mMemoryStore == null) { mMemoryStore = memoryStore; Log.i(TAG, "Installing MemoryStore"); } else { mMemoryStore = memoryStore; Log.e(TAG, "Reinstalling MemoryStore"); } requestReadForPostBootDetection(); setFirstHealthDetectionAlarm(); setPostBootDetectionAlarm(); } /** * Set WiFi enable state. * During the off->on transition, retrieve scanner. * During the on->off transition, issue MemoryStore write to save data. */ private void setWifiEnabled(boolean enable) { if (mWifiEnabled == enable) return; mWifiEnabled = enable; logd("Set WiFi " + (enable ? "enabled" : "disabled")); if (enable) { retrieveWifiScanner(); } else { doWrites(); } } /** * Issue MemoryStore write. This should be called from time to time * to save the state to persistent storage. */ public void doWrites() { mWifiSystemInfoStats.writeToMemory(); } /** * Set device mobility state to assist abnormal scan failure detection */ public void setDeviceMobilityState(@DeviceMobilityState int newState) { logd("Device mobility state: " + newState); mDeviceMobilityState = newState; mWifiSystemInfoStats.setMobilityState(newState); } /** * Get the maximum scan RSSI valid time for scan RSSI search which is done by finding * the maximum RSSI found among all valid scan detail entries of each network's scanDetailCache * If a scanDetail was older than the returned value, it will not be considered valid. */ public int getScanRssiValidTimeMs() { return (mDeviceMobilityState == WifiManager.DEVICE_MOBILITY_STATE_STATIONARY) ? mDeviceConfigFacade.getStationaryScanRssiValidTimeMs() : mDeviceConfigFacade.getNonstationaryScanRssiValidTimeMs(); } /** * Issue read request to prepare for post-boot detection. */ private void requestReadForPostBootDetection() { mWifiSystemInfoStats.readFromMemory(); // Potential SW change detection may require to update all networks. // Thus read all networks. requestReadAllNetworks(); } /** * Helper method to populate WifiScanner handle. This is done lazily because * WifiScanningService is started after WifiService. */ private void retrieveWifiScanner() { if (mScanner != null) return; mScanner = mWifiInjector.getWifiScanner(); if (mScanner == null) return; // Register for all single scan results mScanner.registerScanListener(new ScanListener()); } /** * Handle scan results when scan results come back from WiFi scanner. */ private void handleScanResults(List scanDetails) { ScanStats scanStats = mWifiSystemInfoStats.getCurrScanStats(); scanStats.clear(); scanStats.setLastScanTimeMs(mClock.getWallClockMillis()); for (ScanDetail scanDetail : scanDetails) { ScanResult scanResult = scanDetail.getScanResult(); if (scanResult.is24GHz()) { scanStats.incrementNumBssidLastScan2g(); } else { scanStats.incrementNumBssidLastScanAbove2g(); } } if (mFirstScanStats.getLastScanTimeMs() == TS_NONE) { mFirstScanStats.copy(scanStats); } mWifiSystemInfoStats.setChanged(true); logd(" 2G scanResult count: " + scanStats.getNumBssidLastScan2g() + ", Above2g scanResult count: " + scanStats.getNumBssidLastScanAbove2g()); } private void setFirstHealthDetectionAlarm() { long currTimeMs = mClock.getWallClockMillis(); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(currTimeMs); calendar.set(Calendar.HOUR_OF_DAY, DAILY_DETECTION_HOUR); calendar.set(Calendar.MINUTE, DAILY_DETECTION_MIN); long targetTimeMs = calendar.getTimeInMillis(); long waitTimeMs = targetTimeMs - currTimeMs; if (waitTimeMs < MIN_WAIT_TIME_BEFORE_FIRST_DETECTION_MS) { waitTimeMs += DAILY_DETECTION_INTERVAL_MS; } scheduleDailyDetectionAlarm(waitTimeMs); } private void scheduleDailyDetectionAlarm(long waitTimeMs) { mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, mClock.getElapsedSinceBootMillis() + waitTimeMs, DAILY_DETECTION_TIMER_TAG, mDailyDetectionListener, mHandler); } private void setPostBootDetectionAlarm() { mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mClock.getElapsedSinceBootMillis() + POST_BOOT_DETECTION_WAIT_TIME_MS, POST_BOOT_DETECTION_TIMER_TAG, mPostBootDetectionListener, mHandler); } /** * Dump the internal state of WifiHealthMonitor. */ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Dump of WifiHealthMonitor"); pw.println("WifiHealthMonitor - Log Begin ----"); pw.println("System Info Stats"); pw.println(mWifiSystemInfoStats); pw.println("configured network connection stats"); List configuredNetworks = mWifiConfigManager.getConfiguredNetworks(); for (WifiConfiguration network : configuredNetworks) { if (isInvalidConfiguredNetwork(network)) continue; boolean isRecentlyConnected = (mClock.getWallClockMillis() - network.lastConnected) < MAX_TIME_SINCE_LAST_CONNECTION_MS; PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(network.SSID); int cntName = WifiScoreCard.CNT_CONNECTION_ATTEMPT; if (perNetwork.getStatsCurrBuild().getCount(cntName) > 0 || perNetwork.getRecentStats().getCount(cntName) > 0 || isRecentlyConnected) { pw.println(mWifiScoreCard.lookupNetwork(network.SSID)); } } pw.println("networks with failure increase: "); pw.println(mFailureStatsIncrease); pw.println("networks with failure drop: "); pw.println(mFailureStatsDecrease); pw.println("networks with high failure without previous stats: "); pw.println(mFailureStatsHigh); pw.println("WifiHealthMonitor - Log End ----"); } /** * Get current wifi mainline module long version code * @Return a non-zero value if version code is available, 0 otherwise. */ public long getWifiStackVersion() { PackageManager packageManager = mContext.getPackageManager(); long wifiStackVersion = 0; try { ModuleInfo wifiModule = packageManager.getModuleInfo( WIFI_APEX_NAME, PackageManager.MODULE_APEX_NAME); String wifiPackageName = wifiModule.getPackageName(); if (wifiPackageName != null) { wifiStackVersion = packageManager.getPackageInfo( wifiPackageName, PackageManager.MATCH_APEX).getLongVersionCode(); } } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, " Hit PackageManager exception", e); } return wifiStackVersion; } private synchronized void dailyDetectionHandler() { logd("Run daily detection"); // Clear daily detection result mFailureStatsDecrease.clear(); mFailureStatsIncrease.clear(); mFailureStatsHigh.clear(); mNumNetworkSufficientRecentStatsOnly = 0; mNumNetworkSufficientRecentPrevStats = 0; mHasNewDataForWifiMetrics = true; int connectionDurationSec = 0; // Set the alarm for the next day scheduleDailyDetectionAlarm(DAILY_DETECTION_INTERVAL_MS); List configuredNetworks = mWifiConfigManager.getConfiguredNetworks(); for (WifiConfiguration network : configuredNetworks) { if (isInvalidConfiguredNetwork(network)) { continue; } PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(network.SSID); int detectionFlag = perNetwork.dailyDetection(mFailureStatsDecrease, mFailureStatsIncrease, mFailureStatsHigh); if (detectionFlag == WifiScoreCard.SUFFICIENT_RECENT_STATS_ONLY) { mNumNetworkSufficientRecentStatsOnly++; } if (detectionFlag == WifiScoreCard.SUFFICIENT_RECENT_PREV_STATS) { mNumNetworkSufficientRecentPrevStats++; } connectionDurationSec += perNetwork.getRecentStats().getCount( WifiScoreCard.CNT_CONNECTION_DURATION_SEC); logd("before daily update: " + perNetwork); // Update historical stats with dailyStats and clear dailyStats perNetwork.updateAfterDailyDetection(); logd("after daily update: " + perNetwork); } logd("total connection duration: " + connectionDurationSec); logd("#networks w/ sufficient recent stats: " + mNumNetworkSufficientRecentStatsOnly); logd("#networks w/ sufficient recent and prev stats: " + mNumNetworkSufficientRecentPrevStats); // Write metrics to statsd writeToWifiStatsLog(); doWrites(); mWifiScoreCard.doWrites(); } private void writeToWifiStatsLog() { writeToWifiStatsLogPerStats(mFailureStatsIncrease, WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__ABNORMALITY_TYPE__SIGNIFICANT_INCREASE); writeToWifiStatsLogPerStats(mFailureStatsDecrease, WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__ABNORMALITY_TYPE__SIGNIFICANT_DECREASE); writeToWifiStatsLogPerStats(mFailureStatsHigh, WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__ABNORMALITY_TYPE__SIMPLY_HIGH); } private void writeToWifiStatsLogPerStats(FailureStats failureStats, int abnormalityType) { int cntAssocRejection = failureStats.getCount(REASON_ASSOC_REJECTION); if (cntAssocRejection > 0) { WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType, WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_ASSOCIATION_REJECTION, cntAssocRejection); } int cntAssocTimeout = failureStats.getCount(REASON_ASSOC_TIMEOUT); if (cntAssocTimeout > 0) { WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType, WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_ASSOCIATION_TIMEOUT, cntAssocTimeout); } int cntAuthFailure = failureStats.getCount(REASON_AUTH_FAILURE); if (cntAuthFailure > 0) { WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType, WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_AUTHENTICATION, cntAuthFailure); } int cntConnectionFailure = failureStats.getCount(REASON_CONNECTION_FAILURE); if (cntConnectionFailure > 0) { WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType, WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_CONNECTION, cntConnectionFailure); } int cntDisconnectionNonlocal = failureStats.getCount(REASON_DISCONNECTION_NONLOCAL); if (cntDisconnectionNonlocal > 0) { WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType, WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_NON_LOCAL_DISCONNECTION, cntDisconnectionNonlocal); } int cntShortConnectionNonlocal = failureStats.getCount(REASON_SHORT_CONNECTION_NONLOCAL); if (cntShortConnectionNonlocal > 0) { WifiStatsLog.write(WifiStatsLog.WIFI_FAILURE_STAT_REPORTED, abnormalityType, WifiStatsLog.WIFI_FAILURE_STAT_REPORTED__FAILURE_TYPE__FAILURE_SHORT_CONNECTION_DUE_TO_NON_LOCAL_DISCONNECTION, cntShortConnectionNonlocal); } } /** * Build HealthMonitor proto for WifiMetrics * @return counts of networks with significant connection failure stats if there is a new * detection, or a empty proto with default values if there is no new detection */ public synchronized HealthMonitorMetrics buildProto() { if (!mHasNewDataForWifiMetrics) return null; HealthMonitorMetrics metrics = new HealthMonitorMetrics(); metrics.failureStatsIncrease = failureStatsToProto(mFailureStatsIncrease); metrics.failureStatsDecrease = failureStatsToProto(mFailureStatsDecrease); metrics.failureStatsHigh = failureStatsToProto(mFailureStatsHigh); metrics.numNetworkSufficientRecentStatsOnly = mNumNetworkSufficientRecentStatsOnly; metrics.numNetworkSufficientRecentPrevStats = mNumNetworkSufficientRecentPrevStats; mHasNewDataForWifiMetrics = false; return metrics; } private HealthMonitorFailureStats failureStatsToProto(FailureStats failureStats) { HealthMonitorFailureStats stats = new HealthMonitorFailureStats(); stats.cntAssocRejection = failureStats.getCount(REASON_ASSOC_REJECTION); stats.cntAssocTimeout = failureStats.getCount(REASON_ASSOC_TIMEOUT); stats.cntAuthFailure = failureStats.getCount(REASON_AUTH_FAILURE); stats.cntConnectionFailure = failureStats.getCount(REASON_CONNECTION_FAILURE); stats.cntDisconnectionNonlocalConnecting = failureStats.getCount(REASON_CONNECTION_FAILURE_DISCONNECTION); stats.cntDisconnectionNonlocal = failureStats.getCount(REASON_DISCONNECTION_NONLOCAL); stats.cntShortConnectionNonlocal = failureStats.getCount(REASON_SHORT_CONNECTION_NONLOCAL); return stats; } private boolean isInvalidConfiguredNetwork(WifiConfiguration config) { return (config == null || WifiManager.UNKNOWN_SSID.equals(config.SSID) || config.SSID == null); } private void postBootDetectionHandler() { logd("Run post-boot detection"); postBootSwBuildCheck(); mWifiSystemInfoStats.postBootAbnormalScanDetection(mFirstScanStats); logd(" postBootAbnormalScanDetection: " + mWifiSystemInfoStats.getScanFailure()); // TODO: Check if scan is not empty but all high RSSI connection attempts failed // while connection attempt with the same network succeeded before boot. doWrites(); } private void postBootSwBuildCheck() { WifiSoftwareBuildInfo currSoftwareBuildInfo = extractCurrentSoftwareBuildInfo(); if (currSoftwareBuildInfo == null) return; logd(currSoftwareBuildInfo.toString()); mWifiSystemInfoStats.finishPendingRead(); if (mWifiSystemInfoStats.getCurrSoftwareBuildInfo() == null) { logd("Miss current software build info from memory"); mWifiSystemInfoStats.setCurrSoftwareBuildInfo(currSoftwareBuildInfo); return; } if (mWifiSystemInfoStats.detectSwBuildChange(currSoftwareBuildInfo)) { logd("Detect SW build change"); updateAllNetworkAfterSwBuildChange(); mWifiSystemInfoStats.updateBuildInfoAfterSwBuildChange(currSoftwareBuildInfo); } else { logd("Detect no SW build change"); } } /** * Issue NetworkStats read request for all configured networks. */ private void requestReadAllNetworks() { List configuredNetworks = mWifiConfigManager.getConfiguredNetworks(); for (WifiConfiguration network : configuredNetworks) { if (isInvalidConfiguredNetwork(network)) { continue; } logd(network.SSID); WifiScoreCard.PerNetwork perNetwork = mWifiScoreCard.fetchByNetwork(network.SSID); if (perNetwork == null) { // This network is not in cache. Move it to cache and read it out from MemoryStore. mWifiScoreCard.lookupNetwork(network.SSID); } else { // This network is already in cache before memoryStore is stalled. mWifiScoreCard.requestReadNetwork(perNetwork); } } } /** * Update NetworkStats of all configured networks after a SW build change is detected */ private void updateAllNetworkAfterSwBuildChange() { List configuredNetworks = mWifiConfigManager.getConfiguredNetworks(); for (WifiConfiguration network : configuredNetworks) { if (isInvalidConfiguredNetwork(network)) { continue; } logd(network.SSID); WifiScoreCard.PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(network.SSID); logd("before SW build update: " + perNetwork); perNetwork.updateAfterSwBuildChange(); logd("after SW build update: " + perNetwork); } } /** * Extract current software build information from the running software. */ private WifiSoftwareBuildInfo extractCurrentSoftwareBuildInfo() { if (!mWifiEnabled) { return null; } long wifiStackVersion = getWifiStackVersion(); String osBuildVersion = replaceNullByEmptyString(Build.DISPLAY); if (mWifiNative == null) { return null; } String driverVersion = replaceNullByEmptyString(mWifiNative.getDriverVersion()); String firmwareVersion = replaceNullByEmptyString(mWifiNative.getFirmwareVersion()); return (new WifiSoftwareBuildInfo(osBuildVersion, wifiStackVersion, driverVersion, firmwareVersion)); } private String replaceNullByEmptyString(String str) { return str == null ? "" : str; } /** * Clears the internal state. * This is called in response to a factoryReset call from Settings. */ public void clear() { mWifiSystemInfoStats.clearAll(); } public static final int REASON_NO_FAILURE = -1; public static final int REASON_ASSOC_REJECTION = 0; public static final int REASON_ASSOC_TIMEOUT = 1; public static final int REASON_AUTH_FAILURE = 2; public static final int REASON_CONNECTION_FAILURE = 3; public static final int REASON_DISCONNECTION_NONLOCAL = 4; public static final int REASON_SHORT_CONNECTION_NONLOCAL = 5; public static final int REASON_CONNECTION_FAILURE_DISCONNECTION = 6; public static final int NUMBER_FAILURE_REASON_CODE = 7; public static final String[] FAILURE_REASON_NAME = { "association rejection failure", "association timeout failure", "authentication failure", "connection failure", "disconnection", "short connection", "connection failure disconnection", }; @IntDef(prefix = { "REASON_" }, value = { REASON_NO_FAILURE, REASON_ASSOC_REJECTION, REASON_ASSOC_TIMEOUT, REASON_AUTH_FAILURE, REASON_CONNECTION_FAILURE, REASON_DISCONNECTION_NONLOCAL, REASON_SHORT_CONNECTION_NONLOCAL, REASON_CONNECTION_FAILURE_DISCONNECTION }) @Retention(RetentionPolicy.SOURCE) public @interface FailureReasonCode {} /** * A class maintaining the number of networks with high failure rate or * with a significant change of failure rate */ public static class FailureStats { private final int[] mCount = new int[NUMBER_FAILURE_REASON_CODE]; void clear() { for (int i = 0; i < NUMBER_FAILURE_REASON_CODE; i++) { mCount[i] = 0; } } int getCount(@FailureReasonCode int reasonCode) { return mCount[reasonCode]; } void setCount(@FailureReasonCode int reasonCode, int cnt) { mCount[reasonCode] = cnt; } void incrementCount(@FailureReasonCode int reasonCode) { mCount[reasonCode]++; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < NUMBER_FAILURE_REASON_CODE; i++) { if (mCount[i] == 0) continue; sb.append(FAILURE_REASON_NAME[i]).append(": ").append(mCount[i]).append(" "); } return sb.toString(); } } /** * A class maintaining current OS, Wifi APK, Wifi driver and firmware build version information. */ final class WifiSoftwareBuildInfo { private String mOsBuildVersion; private long mWifiStackVersion; private String mWifiDriverVersion; private String mWifiFirmwareVersion; WifiSoftwareBuildInfo(@NonNull String osBuildVersion, long wifiStackVersion, @NonNull String wifiDriverVersion, @NonNull String wifiFirmwareVersion) { mOsBuildVersion = osBuildVersion; mWifiStackVersion = wifiStackVersion; mWifiDriverVersion = wifiDriverVersion; mWifiFirmwareVersion = wifiFirmwareVersion; } WifiSoftwareBuildInfo(@NonNull WifiSoftwareBuildInfo wifiSoftwareBuildInfo) { mOsBuildVersion = wifiSoftwareBuildInfo.getOsBuildVersion(); mWifiStackVersion = wifiSoftwareBuildInfo.getWifiStackVersion(); mWifiDriverVersion = wifiSoftwareBuildInfo.getWifiDriverVersion(); mWifiFirmwareVersion = wifiSoftwareBuildInfo.getWifiFirmwareVersion(); } String getOsBuildVersion() { return mOsBuildVersion; } long getWifiStackVersion() { return mWifiStackVersion; } String getWifiDriverVersion() { return mWifiDriverVersion; } String getWifiFirmwareVersion() { return mWifiFirmwareVersion; } @Override public boolean equals(Object otherObj) { if (this == otherObj) { return true; } if (!(otherObj instanceof WifiSoftwareBuildInfo)) { return false; } if (otherObj == null) { return false; } WifiSoftwareBuildInfo other = (WifiSoftwareBuildInfo) otherObj; return mOsBuildVersion.equals(other.getOsBuildVersion()) && mWifiStackVersion == other.getWifiStackVersion() && mWifiDriverVersion.equals(other.getWifiDriverVersion()) && mWifiFirmwareVersion.equals(other.getWifiFirmwareVersion()); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("OS build version: "); sb.append(mOsBuildVersion); sb.append(" Wifi stack version: "); sb.append(mWifiStackVersion); sb.append(" Wifi driver version: "); sb.append(mWifiDriverVersion); sb.append(" Wifi firmware version: "); sb.append(mWifiFirmwareVersion); return sb.toString(); } } /** * A class maintaining various WiFi system information and statistics. */ final class WifiSystemInfoStats extends MemoryStoreAccessBase { private WifiSoftwareBuildInfo mCurrSoftwareBuildInfo; private WifiSoftwareBuildInfo mPrevSoftwareBuildInfo; private ScanStats mCurrScanStats = new ScanStats(); private ScanStats mPrevScanStats = new ScanStats(); private int mScanFailure; private @DeviceMobilityState int mMobilityState; private boolean mChanged = false; WifiSystemInfoStats(String l2KeySeed) { super(WifiScoreCard.computeHashLong( "", MacAddress.fromString(DEFAULT_MAC_ADDRESS), l2KeySeed)); } ScanStats getCurrScanStats() { return mCurrScanStats; } void setChanged(boolean changed) { mChanged = changed; } void setCurrSoftwareBuildInfo(WifiSoftwareBuildInfo currSoftwareBuildInfo) { mCurrSoftwareBuildInfo = currSoftwareBuildInfo; mChanged = true; } void setMobilityState(@DeviceMobilityState int mobilityState) { mMobilityState = mobilityState; } WifiSoftwareBuildInfo getCurrSoftwareBuildInfo() { return mCurrSoftwareBuildInfo; } WifiSoftwareBuildInfo getPrevSoftwareBuildInfo() { return mPrevSoftwareBuildInfo; } void clearAll() { mCurrSoftwareBuildInfo = null; mPrevSoftwareBuildInfo = null; mCurrScanStats.clear(); mPrevScanStats.clear(); mChanged = true; } /** * Detect if there is a SW build change by comparing current SW build version vs. SW build * version previously saved in MemoryStore. * @param currSoftwareBuildInfo is current SW build info derived from running SW * @return true if a SW build change is detected, false if no change is detected. */ boolean detectSwBuildChange(@NonNull WifiSoftwareBuildInfo currSoftwareBuildInfo) { if (mCurrSoftwareBuildInfo == null) { return false; } logd(" from Memory: " + mCurrSoftwareBuildInfo); logd(" from SW: " + currSoftwareBuildInfo); return (!mCurrSoftwareBuildInfo.equals(currSoftwareBuildInfo)); } void updateBuildInfoAfterSwBuildChange(@NonNull WifiSoftwareBuildInfo currBuildInfo) { mPrevSoftwareBuildInfo = new WifiSoftwareBuildInfo(mCurrSoftwareBuildInfo); mCurrSoftwareBuildInfo = new WifiSoftwareBuildInfo(currBuildInfo); mChanged = true; } void readFromMemory() { if (mMemoryStore != null) { mMemoryStore.read(getL2Key(), SYSTEM_INFO_DATA_NAME, (value) -> readBackListener(value)); } } // Read may not be completed in theory when finishPendingRead() is called. // Currently it relies on the fact that memory read is issued right after boot complete // while finishPendingRead() is called only POST_BOOT_DETECTION_WAIT_TIME_MS after that. private void finishPendingRead() { final byte[] serialized = finishPendingReadBytes(); if (serialized == null) { logd("Fail to read systemInfoStats from memory"); return; } SystemInfoStats systemInfoStats; try { systemInfoStats = SystemInfoStats.parseFrom(serialized); } catch (InvalidProtocolBufferException e) { Log.e(TAG, "Failed to deserialize", e); return; } readFromMemory(systemInfoStats); } private void readFromMemory(@NonNull SystemInfoStats systemInfoStats) { if (systemInfoStats.hasCurrSoftwareBuildInfo()) { mCurrSoftwareBuildInfo = fromSoftwareBuildInfo( systemInfoStats.getCurrSoftwareBuildInfo()); } if (systemInfoStats.hasPrevSoftwareBuildInfo()) { mPrevSoftwareBuildInfo = fromSoftwareBuildInfo( systemInfoStats.getPrevSoftwareBuildInfo()); } if (systemInfoStats.hasNumBssidLastScan2G()) { mPrevScanStats.setNumBssidLastScan2g(systemInfoStats.getNumBssidLastScan2G()); } if (systemInfoStats.hasNumBssidLastScanAbove2G()) { mPrevScanStats.setNumBssidLastScanAbove2g(systemInfoStats .getNumBssidLastScanAbove2G()); } if (systemInfoStats.hasLastScanTimeMs()) { mPrevScanStats.setLastScanTimeMs(systemInfoStats.getLastScanTimeMs()); } } void writeToMemory() { if (mMemoryStore == null || !mChanged) return; byte[] serialized = toSystemInfoStats().toByteArray(); mMemoryStore.write(getL2Key(), SYSTEM_INFO_DATA_NAME, serialized); mChanged = false; } SystemInfoStats toSystemInfoStats() { SystemInfoStats.Builder builder = SystemInfoStats.newBuilder(); if (mCurrSoftwareBuildInfo != null) { builder.setCurrSoftwareBuildInfo(toSoftwareBuildInfo(mCurrSoftwareBuildInfo)); } if (mPrevSoftwareBuildInfo != null) { builder.setPrevSoftwareBuildInfo(toSoftwareBuildInfo(mPrevSoftwareBuildInfo)); } builder.setLastScanTimeMs(mCurrScanStats.getLastScanTimeMs()); builder.setNumBssidLastScan2G(mCurrScanStats.getNumBssidLastScan2g()); builder.setNumBssidLastScanAbove2G(mCurrScanStats.getNumBssidLastScanAbove2g()); return builder.build(); } private SoftwareBuildInfo toSoftwareBuildInfo( @NonNull WifiSoftwareBuildInfo softwareBuildInfo) { SoftwareBuildInfo.Builder builder = SoftwareBuildInfo.newBuilder(); builder.setOsBuildVersion(softwareBuildInfo.getOsBuildVersion()); builder.setWifiStackVersion(softwareBuildInfo.getWifiStackVersion()); builder.setWifiDriverVersion(softwareBuildInfo.getWifiDriverVersion()); builder.setWifiFirmwareVersion(softwareBuildInfo.getWifiFirmwareVersion()); return builder.build(); } WifiSoftwareBuildInfo fromSoftwareBuildInfo( @NonNull SoftwareBuildInfo softwareBuildInfo) { String osBuildVersion = softwareBuildInfo.hasOsBuildVersion() ? softwareBuildInfo.getOsBuildVersion() : "NA"; long stackVersion = softwareBuildInfo.hasWifiStackVersion() ? softwareBuildInfo.getWifiStackVersion() : 0; String driverVersion = softwareBuildInfo.hasWifiDriverVersion() ? softwareBuildInfo.getWifiDriverVersion() : "NA"; String firmwareVersion = softwareBuildInfo.hasWifiFirmwareVersion() ? softwareBuildInfo.getWifiFirmwareVersion() : "NA"; return new WifiSoftwareBuildInfo(osBuildVersion, stackVersion, driverVersion, firmwareVersion); } /** * Detect pre-boot or post-boot detection failure. * @return 0 if no failure is found or a positive integer if failure is found where * b0 for pre-boot 2G scan failure * b1 for pre-boot Above2g scan failure * b2 for post-boot 2G scan failure * b3 for post-boot Above2g scan failure */ void postBootAbnormalScanDetection(ScanStats firstScanStats) { long preBootScanTimeMs = mPrevScanStats.getLastScanTimeMs(); long postBootScanTimeMs = firstScanStats.getLastScanTimeMs(); logd(" preBootScanTimeMs: " + preBootScanTimeMs); logd(" postBootScanTimeMs: " + postBootScanTimeMs); int preBootNumBssid2g = mPrevScanStats.getNumBssidLastScan2g(); int preBootNumBssidAbove2g = mPrevScanStats.getNumBssidLastScanAbove2g(); int postBootNumBssid2g = firstScanStats.getNumBssidLastScan2g(); int postBootNumBssidAbove2g = firstScanStats.getNumBssidLastScanAbove2g(); logd(" preBootScan 2G count: " + preBootNumBssid2g + ", Above2G count: " + preBootNumBssidAbove2g); logd(" postBootScan 2G count: " + postBootNumBssid2g + ", Above2G count: " + postBootNumBssidAbove2g); mScanFailure = 0; if (postBootScanTimeMs == TS_NONE || preBootScanTimeMs == TS_NONE) return; if ((postBootScanTimeMs - preBootScanTimeMs) > MAX_INTERVAL_BETWEEN_TWO_SCAN_MS) { return; } if (preBootNumBssid2g == 0 && postBootNumBssid2g >= MIN_NUM_BSSID_SCAN_2G) { mScanFailure += 1; } if (preBootNumBssidAbove2g == 0 && postBootNumBssidAbove2g >= MIN_NUM_BSSID_SCAN_ABOVE_2G) { mScanFailure += 2; } if (postBootNumBssid2g == 0 && preBootNumBssid2g >= MIN_NUM_BSSID_SCAN_2G) { mScanFailure += 4; } if (postBootNumBssidAbove2g == 0 && preBootNumBssidAbove2g >= MIN_NUM_BSSID_SCAN_ABOVE_2G) { mScanFailure += 8; } } int getScanFailure() { return mScanFailure; } @Override public String toString() { StringBuilder sb = new StringBuilder(); if (mCurrSoftwareBuildInfo != null) { sb.append("current SW build: "); sb.append(mCurrSoftwareBuildInfo); } if (mPrevSoftwareBuildInfo != null) { sb.append("\n"); sb.append("previous SW build: "); sb.append(mPrevSoftwareBuildInfo); } sb.append("\n"); sb.append("currScanStats: "); sb.append(mCurrScanStats); sb.append("\n"); sb.append("prevScanStats: "); sb.append(mPrevScanStats); return sb.toString(); } } final class ScanStats { private long mLastScanTimeMs = TS_NONE; private int mNumBssidLastScan2g; private int mNumBssidLastScanAbove2g; void copy(ScanStats source) { mLastScanTimeMs = source.mLastScanTimeMs; mNumBssidLastScan2g = source.mNumBssidLastScan2g; mNumBssidLastScanAbove2g = source.mNumBssidLastScanAbove2g; } void setLastScanTimeMs(long lastScanTimeMs) { mLastScanTimeMs = lastScanTimeMs; } void setNumBssidLastScan2g(int numBssidLastScan2g) { mNumBssidLastScan2g = numBssidLastScan2g; } void setNumBssidLastScanAbove2g(int numBssidLastScanAbove2g) { mNumBssidLastScanAbove2g = numBssidLastScanAbove2g; } long getLastScanTimeMs() { return mLastScanTimeMs; } int getNumBssidLastScan2g() { return mNumBssidLastScan2g; } int getNumBssidLastScanAbove2g() { return mNumBssidLastScanAbove2g; } void incrementNumBssidLastScan2g() { mNumBssidLastScan2g++; } void incrementNumBssidLastScanAbove2g() { mNumBssidLastScanAbove2g++; } void clear() { mLastScanTimeMs = TS_NONE; mNumBssidLastScan2g = 0; mNumBssidLastScanAbove2g = 0; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("last scan time: "); sb.append(mLastScanTimeMs); sb.append(" APs found at 2G: "); sb.append(mNumBssidLastScan2g); sb.append(" APs found above 2g: "); sb.append(mNumBssidLastScanAbove2g); return sb.toString(); } } @VisibleForTesting WifiSystemInfoStats getWifiSystemInfoStats() { return mWifiSystemInfoStats; } private void logd(String string) { if (mVerboseLoggingEnabled) { Log.d(TAG, string); } } /** * Listener for config manager network config related events. */ private class OnNetworkUpdateListener implements WifiConfigManager.OnNetworkUpdateListener { @Override public void onNetworkAdded(WifiConfiguration config) { if (config == null) return; mWifiScoreCard.lookupNetwork(config.SSID); } @Override public void onNetworkRemoved(WifiConfiguration config) { if (config == null || (config.fromWifiNetworkSuggestion && !config.isPasspoint())) { // If a suggestion non-passpoint network is removed from wifiConfigManager do not // remove the ScoreCard. That will be removed when suggestion is removed. return; } mWifiScoreCard.removeNetwork(config.SSID); } } /** * Scan listener for any full band scan. */ private class ScanListener implements WifiScanner.ScanListener { private List mScanDetails = new ArrayList(); public void clearScanDetails() { mScanDetails.clear(); } @Override public void onSuccess() { } @Override public void onFailure(int reason, String description) { logd("registerScanListener onFailure:" + " reason: " + reason + " description: " + description); } @Override public void onPeriodChanged(int periodInMs) { } @Override public void onResults(WifiScanner.ScanData[] results) { if (!mWifiEnabled || results == null || results.length == 0) { clearScanDetails(); return; } if (WifiScanner.isFullBandScan(results[0].getScannedBandsInternal(), true)) { handleScanResults(mScanDetails); } clearScanDetails(); } @Override public void onFullResult(ScanResult fullScanResult) { if (!mWifiEnabled) { return; } mScanDetails.add(ScanResultUtil.toScanDetail(fullScanResult)); } } }