summaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
authorKai Shi <kaishi@google.com>2020-01-11 00:20:18 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-01-11 00:20:18 +0000
commit46addc2fbc03652fabbc46d3d70c7649c9463063 (patch)
treeb8f4c2076d5cf8f46bb4bd53e305b4f6eab63bdb /service
parent6a701444f98de01777658f34e16a5a15c1be4da2 (diff)
parente2625a2794d3d4262652edafbb87df9eac54ae53 (diff)
Merge "Wifi: add the initial version of WifiHealthMonitor and related changes."
Diffstat (limited to 'service')
-rw-r--r--service/java/com/android/server/wifi/ClientModeImpl.java53
-rw-r--r--service/java/com/android/server/wifi/MemoryStoreImpl.java18
-rw-r--r--service/java/com/android/server/wifi/WifiConfigManager.java38
-rw-r--r--service/java/com/android/server/wifi/WifiHealthMonitor.java914
-rw-r--r--service/java/com/android/server/wifi/WifiInjector.java11
-rw-r--r--service/java/com/android/server/wifi/WifiNetworkSelector.java14
-rw-r--r--service/java/com/android/server/wifi/WifiScoreCard.java933
-rw-r--r--service/java/com/android/server/wifi/WifiServiceImpl.java20
-rw-r--r--service/proto/src/scorecard.proto63
9 files changed, 1938 insertions, 126 deletions
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java
index 523515919..5a7d39fe0 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -24,6 +24,8 @@ import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
+import static com.android.server.wifi.WifiHealthMonitor.SCAN_RSSI_VALID_TIME_MS;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -213,6 +215,7 @@ public class ClientModeImpl extends StateMachine {
private final BuildProperties mBuildProperties;
private final WifiCountryCode mCountryCode;
private final WifiScoreCard mWifiScoreCard;
+ private final WifiHealthMonitor mWifiHealthMonitor;
private final WifiScoreReport mWifiScoreReport;
private final SarManager mSarManager;
private final WifiTrafficPoller mWifiTrafficPoller;
@@ -791,6 +794,7 @@ public class ClientModeImpl extends StateMachine {
mWifiNetworkSuggestionsManager = mWifiInjector.getWifiNetworkSuggestionsManager();
mProcessingActionListeners = new ExternalCallbackTracker<>(getHandler());
mProcessingTxPacketCountListeners = new ExternalCallbackTracker<>(getHandler());
+ mWifiHealthMonitor = mWifiInjector.getWifiHealthMonitor();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
@@ -1119,6 +1123,8 @@ public class ClientModeImpl extends StateMachine {
mNetworkFactory.enableVerboseLogging(verbose);
mLinkProbeManager.enableVerboseLogging(mVerboseLoggingEnabled);
mMboOceController.enableVerboseLogging(mVerboseLoggingEnabled);
+ mWifiScoreCard.enableVerboseLogging(mVerboseLoggingEnabled);
+ mWifiHealthMonitor.enableVerboseLogging(mVerboseLoggingEnabled);
}
private boolean setRandomMacOui() {
@@ -2579,7 +2585,6 @@ public class ClientModeImpl extends StateMachine {
mWifiInjector.getWakeupController().setLastDisconnectInfo(matchInfo);
mWifiNetworkSuggestionsManager.handleDisconnect(wifiConfig, getCurrentBSSID());
}
-
stopRssiMonitoringOffload();
clearTargetBssid("handleNetworkDisconnect");
@@ -2603,7 +2608,7 @@ public class ClientModeImpl extends StateMachine {
/* Clear network properties */
clearLinkProperties();
- /* Cend event to CM & network change broadcast */
+ /* Send event to CM & network change broadcast */
sendNetworkStateChangeBroadcast(mLastBssid);
mLastBssid = null;
@@ -2746,21 +2751,37 @@ public class ClientModeImpl extends StateMachine {
*/
private void reportConnectionAttemptEnd(int level2FailureCode, int connectivityFailureCode,
int level2FailureReason) {
+ // if connected, this should be non-null.
+ WifiConfiguration configuration = getCurrentWifiConfiguration();
+ if (configuration == null) {
+ // If not connected, this should be non-null.
+ configuration = getTargetWifiConfiguration();
+ }
+
if (level2FailureCode != WifiMetrics.ConnectionEvent.FAILURE_NONE) {
- mWifiScoreCard.noteConnectionFailure(mWifiInfo,
- level2FailureCode, connectivityFailureCode);
+
+ int bssidBlocklistMonitorReason = convertToBssidBlocklistMonitorFailureReason(
+ level2FailureCode, level2FailureReason);
+
String bssid = mLastBssid == null ? mTargetRoamBSSID : mLastBssid;
String ssid = mWifiInfo.getSSID();
if (WifiManager.UNKNOWN_SSID.equals(ssid)) {
ssid = getTargetSsid();
}
- int bssidBlocklistMonitorReason = convertToBssidBlocklistMonitorFailureReason(
- level2FailureCode, level2FailureReason);
+
+ if (level2FailureCode != WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION) {
+ int networkId = (configuration == null) ? WifiConfiguration.INVALID_NETWORK_ID
+ : configuration.networkId;
+ int scanRssi = mWifiConfigManager.findScanRssi(networkId, SCAN_RSSI_VALID_TIME_MS);
+ mWifiScoreCard.noteConnectionFailure(mWifiInfo, scanRssi, ssid,
+ bssidBlocklistMonitorReason);
+ }
if (bssidBlocklistMonitorReason != -1) {
mBssidBlocklistMonitor.handleBssidConnectionFailure(bssid, ssid,
bssidBlocklistMonitorReason);
}
}
+
boolean isAssociationRejection = level2FailureCode
== WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION;
boolean isAuthenticationFailure = level2FailureCode
@@ -2771,12 +2792,7 @@ public class ClientModeImpl extends StateMachine {
mConnectionFailureNotifier
.showFailedToConnectDueToNoRandomizedMacSupportNotification(mTargetNetworkId);
}
- // if connected, this should be non-null.
- WifiConfiguration configuration = getCurrentWifiConfiguration();
- if (configuration == null) {
- // If not connected, this should be non-null.
- configuration = getTargetWifiConfiguration();
- }
+
mWifiMetrics.endConnectionEvent(level2FailureCode, connectivityFailureCode,
level2FailureReason);
mWifiConnectivityManager.handleConnectionAttemptEnded(level2FailureCode);
@@ -3480,6 +3496,7 @@ public class ClientModeImpl extends StateMachine {
mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
mWifiMetrics.logStaEvent(StaEvent.TYPE_WIFI_ENABLED);
mWifiScoreCard.noteSupplicantStateChanged(mWifiInfo);
+ mWifiHealthMonitor.setWifiEnabled(true);
}
@Override
@@ -3504,6 +3521,7 @@ public class ClientModeImpl extends StateMachine {
mWifiInfo.reset();
mWifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
mWifiScoreCard.noteSupplicantStateChanged(mWifiInfo);
+ mWifiHealthMonitor.setWifiEnabled(false);
stopClientMode();
}
@@ -3762,7 +3780,8 @@ public class ClientModeImpl extends StateMachine {
break;
}
// Update scorecard while there is still state from existing connection
- mWifiScoreCard.noteConnectionAttempt(mWifiInfo);
+ int scanRssi = mWifiConfigManager.findScanRssi(netId, SCAN_RSSI_VALID_TIME_MS);
+ mWifiScoreCard.noteConnectionAttempt(mWifiInfo, scanRssi, config.SSID);
mTargetNetworkId = netId;
setTargetBssid(config, bssid);
mBssidBlocklistMonitor.updateFirmwareRoamingConfiguration(config.SSID);
@@ -5056,6 +5075,7 @@ public class ClientModeImpl extends StateMachine {
mLastBssid, config.SSID,
BssidBlocklistMonitor
.REASON_NETWORK_VALIDATION_FAILURE);
+ mWifiScoreCard.noteValidationFailure(mWifiInfo);
}
}
}
@@ -5107,7 +5127,8 @@ public class ClientModeImpl extends StateMachine {
WifiDiagnostics.REPORT_REASON_UNEXPECTED_DISCONNECT);
}
boolean localGen = message.arg1 == 1;
- if (!localGen) { // ignore disconnects initiatied by wpa_supplicant.
+ if (!localGen) { // ignore disconnects initiated by wpa_supplicant.
+ mWifiScoreCard.noteNonlocalDisconnect(message.arg2);
mBssidBlocklistMonitor.handleBssidConnectionFailure(mWifiInfo.getBSSID(),
mWifiInfo.getSSID(),
BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT);
@@ -5140,7 +5161,8 @@ public class ClientModeImpl extends StateMachine {
loge("CMD_START_ROAM and no config, bail out...");
break;
}
- mWifiScoreCard.noteConnectionAttempt(mWifiInfo);
+ int scanRssi = mWifiConfigManager.findScanRssi(netId, SCAN_RSSI_VALID_TIME_MS);
+ mWifiScoreCard.noteConnectionAttempt(mWifiInfo, scanRssi, config.SSID);
setTargetBssid(config, bssid);
mTargetNetworkId = netId;
@@ -5678,6 +5700,7 @@ public class ClientModeImpl extends StateMachine {
*/
public void setDeviceMobilityState(@DeviceMobilityState int state) {
mWifiConnectivityManager.setDeviceMobilityState(state);
+ mWifiHealthMonitor.setDeviceMobilityState(state);
}
/**
diff --git a/service/java/com/android/server/wifi/MemoryStoreImpl.java b/service/java/com/android/server/wifi/MemoryStoreImpl.java
index 3d9e7626b..1b819d46d 100644
--- a/service/java/com/android/server/wifi/MemoryStoreImpl.java
+++ b/service/java/com/android/server/wifi/MemoryStoreImpl.java
@@ -39,17 +39,17 @@ final class MemoryStoreImpl implements WifiScoreCard.MemoryStore {
// The id of the client that stored this data
public static final String WIFI_FRAMEWORK_IP_MEMORY_STORE_CLIENT_ID = "com.android.server.wifi";
- // The name of the data
- public static final String WIFI_FRAMEWORK_IP_MEMORY_STORE_DATA_NAME = "scorecard.proto";
-
@NonNull private final Context mContext;
@NonNull private final WifiScoreCard mWifiScoreCard;
+ @NonNull private final WifiHealthMonitor mWifiHealthMonitor;
@NonNull private final WifiInjector mWifiInjector;
@Nullable private IpMemoryStore mIpMemoryStore;
- MemoryStoreImpl(Context context, WifiInjector wifiInjector, WifiScoreCard wifiScoreCard) {
+ MemoryStoreImpl(Context context, WifiInjector wifiInjector, WifiScoreCard wifiScoreCard,
+ WifiHealthMonitor wifiHealthMonitor) {
mContext = Preconditions.checkNotNull(context);
mWifiScoreCard = Preconditions.checkNotNull(wifiScoreCard);
+ mWifiHealthMonitor = Preconditions.checkNotNull(wifiHealthMonitor);
mWifiInjector = Preconditions.checkNotNull(wifiInjector);
mIpMemoryStore = null;
}
@@ -62,13 +62,13 @@ final class MemoryStoreImpl implements WifiScoreCard.MemoryStore {
@Override
- public void read(final String key, final BlobListener blobListener) {
+ public void read(final String key, final String name, final BlobListener blobListener) {
if (mBroken) return;
try {
mIpMemoryStore.retrieveBlob(
key,
WIFI_FRAMEWORK_IP_MEMORY_STORE_CLIENT_ID,
- WIFI_FRAMEWORK_IP_MEMORY_STORE_DATA_NAME,
+ name,
new CatchAFallingBlob(key, blobListener));
} catch (RuntimeException e) {
handleException(e);
@@ -111,7 +111,7 @@ final class MemoryStoreImpl implements WifiScoreCard.MemoryStore {
}
@Override
- public void write(String key, byte[] value) {
+ public void write(String key, String name, byte[] value) {
if (mBroken) return;
final Blob blob = new Blob();
blob.data = value;
@@ -119,7 +119,7 @@ final class MemoryStoreImpl implements WifiScoreCard.MemoryStore {
mIpMemoryStore.storeBlob(
key,
WIFI_FRAMEWORK_IP_MEMORY_STORE_CLIENT_ID,
- WIFI_FRAMEWORK_IP_MEMORY_STORE_DATA_NAME,
+ name,
blob,
null /* no listener for now, just fire and forget */);
} catch (RuntimeException e) {
@@ -140,6 +140,7 @@ final class MemoryStoreImpl implements WifiScoreCard.MemoryStore {
return;
}
mWifiScoreCard.installMemoryStore(this);
+ mWifiHealthMonitor.installMemoryStoreSetUpDetectionAlarm(this);
}
/**
@@ -148,6 +149,7 @@ final class MemoryStoreImpl implements WifiScoreCard.MemoryStore {
public void stop() {
if (mIpMemoryStore == null) return;
mWifiScoreCard.doWrites();
+ mWifiHealthMonitor.doWrites();
// TODO - Should wait for writes to complete (or time out)
Log.i(TAG, "Disconnecting from IpMemoryStore service");
mIpMemoryStore = null;
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index 0647ed987..dd3997a28 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -2384,6 +2384,19 @@ public class WifiConfigManager {
}
/**
+ * Caches the provided |scanDetail| into the corresponding scan detail cache entry
+ * {@link #mScanDetailCaches} for the retrieved network.
+ *
+ * @param scanDetail input a scanDetail from the scan result
+ */
+ public void updateScanDetailCacheFromScanDetail(ScanDetail scanDetail) {
+ WifiConfiguration network = getConfiguredNetworkForScanDetail(scanDetail);
+ if (network == null) {
+ return;
+ }
+ saveToScanDetailCacheForNetwork(network, scanDetail);
+ }
+ /**
* Retrieves a configured network corresponding to the provided scan detail if one exists and
* caches the provided |scanDetail| into the corresponding scan detail cache entry
* {@link #mScanDetailCaches} for the retrieved network.
@@ -3492,4 +3505,29 @@ public class WifiConfigManager {
}
config.recentFailure.clear();
}
+
+ /**
+ * Find the highest RSSI among all valid scanDetails in current network's scanDetail cache.
+ * If scanDetail is too old, it is not considered to be valid.
+ * @param netId The network ID of the config to find scan RSSI
+ * @params scanRssiValidTimeMs The valid time for scan RSSI
+ * @return The highest RSSI in dBm found with current network's scanDetail cache.
+ */
+ public int findScanRssi(int netId, int scanRssiValidTimeMs) {
+ int scanMaxRssi = WifiInfo.INVALID_RSSI;
+ ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(netId);
+ if (scanDetailCache == null || scanDetailCache.size() == 0) return scanMaxRssi;
+ long nowInMillis = mClock.getWallClockMillis();
+ for (ScanDetail scanDetail : scanDetailCache.values()) {
+ ScanResult result = scanDetail.getScanResult();
+ if (result == null) continue;
+ boolean valid = (nowInMillis - result.seen) < scanRssiValidTimeMs;
+
+ if (valid) {
+ scanMaxRssi = Math.max(scanMaxRssi, result.level);
+ }
+ }
+ return scanMaxRssi;
+ }
+
}
diff --git a/service/java/com/android/server/wifi/WifiHealthMonitor.java b/service/java/com/android/server/wifi/WifiHealthMonitor.java
new file mode 100644
index 000000000..f545c0985
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiHealthMonitor.java
@@ -0,0 +1,914 @@
+/*
+ * 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.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.util.ScanResultUtil;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+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_APK_PACKAGE_NAME = "com.google.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;
+ // 0 - ELAPSED_REAL_TIME, 1 - RTC
+ private static final int DAILY_DETECTION_ALARM_TYPE = 1;
+ // The time interval between two daily detections
+ private static final int DAILY_DETECTION_INTERVAL_MS = 24 * 3600_000;
+ private static final int DAILY_DETECTION_HOUR = 24;
+ // 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;
+ // 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 is seen more than SCAN_RSSI_VALID_TIME_MS ago,
+ // it will not be considered valid anymore.
+ static final int SCAN_RSSI_VALID_TIME_MS = 6_000;
+ // Minimum RSSI in dBm for connection stats collection
+ // Connection or disconnection events with RSSI below this threshold are not
+ // included in connection stats collection.
+ static final int HEALTH_MONITOR_COUNT_RSSI_MIN_DBM = -68;
+ // Minimum Tx speed in Mbps for disconnection stats collection
+ // Disconnection events with Tx speed below this threshold are not
+ // included in connection stats collection.
+ static final int HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS = 54;
+ static final int HEALTH_MONITOR_COUNT_MIN_TX_RATE = 6;
+ 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 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();
+
+ WifiHealthMonitor(Context context, WifiInjector wifiInjector, Clock clock,
+ WifiConfigManager wifiConfigManager, WifiScoreCard wifiScoreCard, Handler handler,
+ WifiNative wifiNative, String l2KeySeed, DeviceConfigFacade deviceConfigFacade) {
+ mContext = context;
+ mWifiInjector = wifiInjector;
+ mClock = clock;
+ mWifiConfigManager = wifiConfigManager;
+ mWifiScoreCard = wifiScoreCard;
+ mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ mHandler = handler;
+ mWifiNative = wifiNative;
+ mDeviceConfigFacade = deviceConfigFacade;
+ mWifiEnabled = false;
+ mWifiSystemInfoStats = new WifiSystemInfoStats(l2KeySeed);
+ mWifiConfigManager.addOnNetworkUpdateListener(new OnNetworkUpdateListener());
+ }
+
+ /**
+ * 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();
+ setDailyDetectionAlarm();
+ setPostBootDetectionAlarm();
+ }
+
+ /**
+ * Set WiFi enable state.
+ * During the off->on transition, retrieve scanner.
+ * During the on->off transition, issue MemoryStore write to save data.
+ */
+ public void setWifiEnabled(boolean enable) {
+ 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);
+ mWifiSystemInfoStats.setMobilityState(newState);
+ }
+
+ /**
+ * 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<ScanDetail> 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 setDailyDetectionAlarm() {
+ if (DAILY_DETECTION_ALARM_TYPE == 0) {
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mClock.getElapsedSinceBootMillis() + DAILY_DETECTION_INTERVAL_MS,
+ DAILY_DETECTION_TIMER_TAG,
+ mDailyDetectionListener, mHandler);
+ } else {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTimeInMillis(System.currentTimeMillis());
+ calendar.set(Calendar.HOUR_OF_DAY, DAILY_DETECTION_HOUR);
+ calendar.set(Calendar.MINUTE, 0);
+ mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+ calendar.getTimeInMillis(),
+ 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);
+ }
+
+ private void dailyDetectionHandler() {
+ logd("Run daily detection");
+ // Clear daily detection result
+ mFailureStatsDecrease.clear();
+ mFailureStatsIncrease.clear();
+ mFailureStatsHigh.clear();
+ int connectionDurationSec = 0;
+ // Set the alarm for the next day
+ setDailyDetectionAlarm();
+ int numNetworkSufficientRecentStatsOnly = 0;
+ int numNetworkSufficientRecentPrevStats = 0;
+ List<WifiConfiguration> configuredNetworks = mWifiConfigManager.getConfiguredNetworks();
+ for (WifiConfiguration network : configuredNetworks) {
+ if (isInValidConfiguredNetwork(network)) {
+ continue;
+ }
+ logd(network.SSID);
+ PerNetwork perNetwork = mWifiScoreCard.lookupNetwork(network.SSID);
+ logd("before daily update: " + perNetwork.toString());
+
+ int detectionFlag = perNetwork.dailyDetection(mFailureStatsDecrease,
+ mFailureStatsIncrease, mFailureStatsHigh);
+ if (detectionFlag == WifiScoreCard.SUFFICIENT_RECENT_STATS_ONLY) {
+ numNetworkSufficientRecentStatsOnly++;
+ }
+ if (detectionFlag == WifiScoreCard.SUFFICIENT_RECENT_PREV_STATS) {
+ numNetworkSufficientRecentPrevStats++;
+ }
+ connectionDurationSec += perNetwork.getRecentStats().getCount(
+ WifiScoreCard.CNT_CONNECTION_DURATION_SEC);
+ // Update historical stats with dailyStats and clear dailyStats
+ perNetwork.updateAfterDailyDetection();
+ logd("after daily update: " + perNetwork.toString());
+ }
+ logd("total connection duration: " + connectionDurationSec);
+ logd("#networks w/ sufficient recent stats: " + numNetworkSufficientRecentStatsOnly);
+ logd("#networks w/ sufficient recent/prev stats: " + numNetworkSufficientRecentPrevStats);
+ // TODO: Report numNetworkSufficientRecentStatsOnly, numNetworkSufficientRecentPrevStats
+ // mFailureStatsDecrease, mFailureStatsIncrease and mFailureStatsHigh to metrics
+ doWrites();
+ mWifiScoreCard.doWrites();
+ }
+
+ 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<WifiConfiguration> 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<WifiConfiguration> 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.toString());
+ perNetwork.updateAfterSwBuildChange();
+ logd("after SW build update: " + perNetwork.toString());
+ }
+ }
+
+ /**
+ * Extract current software build information from the running software.
+ */
+ private WifiSoftwareBuildInfo extractCurrentSoftwareBuildInfo() {
+ PackageManager packageManager = mContext.getPackageManager();
+ int wifiStackVersion = 0;
+ try {
+ wifiStackVersion = packageManager.getPackageInfo(
+ WIFI_APK_PACKAGE_NAME, PackageManager.MATCH_APEX).versionCode;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, " hit PackageManager nameNotFoundException");
+ }
+ 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_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 NUMBER_FAILURE_REASON_CODE = 6;
+ @IntDef(prefix = { "REASON_" }, value = {
+ REASON_ASSOC_REJECTION,
+ REASON_ASSOC_TIMEOUT,
+ REASON_AUTH_FAILURE,
+ REASON_CONNECTION_FAILURE,
+ REASON_DISCONNECTION_NONLOCAL,
+ REASON_SHORT_CONNECTION_NONLOCAL
+ })
+ @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]++;
+ }
+ }
+ /**
+ * A class maintaining current OS, Wifi APK, Wifi driver and firmware build version information.
+ */
+ final class WifiSoftwareBuildInfo {
+ private String mOsBuildVersion;
+ private int mWifiStackVersion;
+ private String mWifiDriverVersion;
+ private String mWifiFirmwareVersion;
+ WifiSoftwareBuildInfo(@NonNull String osBuildVersion, int 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;
+ }
+ int 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.toString());
+ logd(" from SW: " + currSoftwareBuildInfo.toString());
+ 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";
+ int 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.toString());
+ }
+ if (mPrevSoftwareBuildInfo != null) {
+ sb.append(" previous SW build: ");
+ sb.append(mPrevSoftwareBuildInfo.toString());
+ }
+ sb.append(" currScanStats: ");
+ sb.append(mCurrScanStats.toString());
+ sb.append(" prevScanStats: ");
+ sb.append(mPrevScanStats.toString());
+ 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 onNetworkEnabled(WifiConfiguration config) {
+ }
+
+ @Override
+ public void onNetworkPermanentlyDisabled(WifiConfiguration config, int disableReason) {
+ }
+
+ @Override
+ public void onNetworkRemoved(WifiConfiguration config) {
+ if (config == null) return;
+ mWifiScoreCard.removeNetwork(config.SSID);
+ }
+
+ @Override
+ public void onNetworkTemporarilyDisabled(WifiConfiguration config, int disableReason) {
+ }
+
+ @Override
+ public void onNetworkUpdated(WifiConfiguration config) {
+ }
+ }
+
+ /**
+ * Scan listener for any full band scan.
+ */
+ private class ScanListener implements WifiScanner.ScanListener {
+ private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
+
+ 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) {
+ clearScanDetails();
+ return;
+ }
+
+ boolean isFullBandScanResults =
+ results[0].getBandScanned() == WifiScanner.WIFI_BAND_BOTH_WITH_DFS
+ || results[0].getBandScanned() == WifiScanner.WIFI_BAND_BOTH;
+ if (isFullBandScanResults) {
+ handleScanResults(mScanDetails);
+ }
+ clearScanDetails();
+ }
+
+ @Override
+ public void onFullResult(ScanResult fullScanResult) {
+ if (!mWifiEnabled) {
+ return;
+ }
+ mScanDetails.add(ScanResultUtil.toScanDetail(fullScanResult));
+ }
+ }
+}
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 3fbd58ed5..51436eaad 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -157,6 +157,7 @@ public class WifiInjector {
private final ConnectionFailureNotificationBuilder mConnectionFailureNotificationBuilder;
private final ThroughputPredictor mThroughputPredictor;
private NetdWrapper mNetdWrapper;
+ private final WifiHealthMonitor mWifiHealthMonitor;
public WifiInjector(Context context) {
if (context == null) {
@@ -185,8 +186,6 @@ public class WifiInjector {
mConnectionFailureNotificationBuilder = new ConnectionFailureNotificationBuilder(
mContext, getWifiStackPackageName(), mFrameworkFacade);
mBatteryStats = context.getSystemService(BatteryStatsManager.class);
- mWifiScoreCard = new WifiScoreCard(mClock,
- Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID));
mSettingsStore = new WifiSettingsStore(mContext);
mWifiPermissionsWrapper = new WifiPermissionsWrapper(mContext);
mNetworkScoreManager = mContext.getSystemService(NetworkScoreManager.class);
@@ -260,6 +259,8 @@ public class WifiInjector {
new NetworkListUserStoreData(mContext),
new DeletedEphemeralSsidsStoreData(mClock), new RandomizedMacStoreData(),
mFrameworkFacade, wifiHandler, mDeviceConfigFacade);
+ String l2KeySeed = Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID);
+ mWifiScoreCard = new WifiScoreCard(mClock, l2KeySeed);
mWifiMetrics.setWifiConfigManager(mWifiConfigManager);
mWifiApConfigStore = new WifiApConfigStore(
@@ -321,6 +322,8 @@ public class WifiInjector {
SupplicantStateTracker supplicantStateTracker = new SupplicantStateTracker(
mContext, mWifiConfigManager, mBatteryStats, wifiHandler);
mMboOceController = new MboOceController(makeTelephonyManager(), mWifiNative);
+ mWifiHealthMonitor = new WifiHealthMonitor(mContext, this, mClock, mWifiConfigManager,
+ mWifiScoreCard, wifiHandler, mWifiNative, l2KeySeed, mDeviceConfigFacade);
mClientModeImpl = new ClientModeImpl(mContext, mFrameworkFacade,
wifiLooper, mUserManager,
this, mBackupManagerProxy, mCountryCode, mWifiNative,
@@ -790,4 +793,8 @@ public class WifiInjector {
public WifiCondManager getWifiCondManager() {
return mWifiCondManager;
}
+
+ public WifiHealthMonitor getWifiHealthMonitor() {
+ return mWifiHealthMonitor;
+ }
}
diff --git a/service/java/com/android/server/wifi/WifiNetworkSelector.java b/service/java/com/android/server/wifi/WifiNetworkSelector.java
index fec8e0dde..3d3a48147 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSelector.java
@@ -655,7 +655,7 @@ public class WifiNetworkSelector {
}
/**
- * Select the best network from the ones in range.
+ * Select the best network from the ones in range. Scan detail cache is also updated here.
*
* @param scanDetails List of ScanDetail for all the APs in range
* @param bssidBlacklist Blacklisted BSSIDs
@@ -685,6 +685,9 @@ public class WifiNetworkSelector {
// Shall we start network selection at all?
if (!isNetworkSelectionNeeded(scanDetails, wifiInfo, connected, disconnected)) {
+ // If network selection is skipped, update scan detail cache before exit.
+ // Otherwise, scan detail cache will be updated in each nominator.
+ updateScanDetailCache(scanDetails);
return null;
}
@@ -700,6 +703,9 @@ public class WifiNetworkSelector {
mFilteredNetworks = filterScanResults(scanDetails, bssidBlacklist,
connected && wifiInfo.getScore() >= WIFI_POOR_SCORE, currentBssid);
if (mFilteredNetworks.size() == 0) {
+ // If network selection is skipped, update scan detail cache before exit.
+ // Otherwise, scan detail cache will be updated in each nominator.
+ updateScanDetailCache(scanDetails);
return null;
}
@@ -831,6 +837,12 @@ public class WifiNetworkSelector {
}
}
+ private void updateScanDetailCache(List<ScanDetail> scanDetails) {
+ for (ScanDetail scanDetail : scanDetails) {
+ mWifiConfigManager.updateScanDetailCacheFromScanDetail(scanDetail);
+ }
+ }
+
private static int toProtoNominatorId(@NetworkNominator.NominatorId int nominatorId) {
switch (nominatorId) {
case NetworkNominator.NOMINATOR_ID_SAVED:
diff --git a/service/java/com/android/server/wifi/WifiScoreCard.java b/service/java/com/android/server/wifi/WifiScoreCard.java
index cc5f1285f..4b15a97c3 100644
--- a/service/java/com/android/server/wifi/WifiScoreCard.java
+++ b/service/java/com/android/server/wifi/WifiScoreCard.java
@@ -18,7 +18,19 @@ package com.android.server.wifi;
import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS;
import static android.net.wifi.WifiInfo.INVALID_RSSI;
-
+import static android.net.wifi.WifiInfo.LINK_SPEED_UNKNOWN;
+
+import static com.android.server.wifi.WifiHealthMonitor.HEALTH_MONITOR_COUNT_MIN_TX_RATE;
+import static com.android.server.wifi.WifiHealthMonitor.HEALTH_MONITOR_COUNT_RSSI_MIN_DBM;
+import static com.android.server.wifi.WifiHealthMonitor.HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS;
+import static com.android.server.wifi.WifiHealthMonitor.REASON_ASSOC_REJECTION;
+import static com.android.server.wifi.WifiHealthMonitor.REASON_ASSOC_TIMEOUT;
+import static com.android.server.wifi.WifiHealthMonitor.REASON_AUTH_FAILURE;
+import static com.android.server.wifi.WifiHealthMonitor.REASON_CONNECTION_FAILURE;
+import static com.android.server.wifi.WifiHealthMonitor.REASON_DISCONNECTION_NONLOCAL;
+import static com.android.server.wifi.WifiHealthMonitor.REASON_SHORT_CONNECTION_NONLOCAL;
+
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.MacAddress;
@@ -31,12 +43,16 @@ import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.server.wifi.BssidBlocklistMonitor.FailureReason;
+import com.android.server.wifi.WifiHealthMonitor.FailureStats;
import com.android.server.wifi.proto.WifiScoreCardProto;
import com.android.server.wifi.proto.WifiScoreCardProto.AccessPoint;
+import com.android.server.wifi.proto.WifiScoreCardProto.ConnectionStats;
import com.android.server.wifi.proto.WifiScoreCardProto.Event;
import com.android.server.wifi.proto.WifiScoreCardProto.HistogramBucket;
import com.android.server.wifi.proto.WifiScoreCardProto.Network;
import com.android.server.wifi.proto.WifiScoreCardProto.NetworkList;
+import com.android.server.wifi.proto.WifiScoreCardProto.NetworkStats;
import com.android.server.wifi.proto.WifiScoreCardProto.SecurityType;
import com.android.server.wifi.proto.WifiScoreCardProto.Signal;
import com.android.server.wifi.proto.WifiScoreCardProto.UnivariateStatistic;
@@ -46,6 +62,8 @@ import com.android.server.wifi.util.NativeUtil;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -58,10 +76,10 @@ import javax.annotation.concurrent.NotThreadSafe;
/**
* Retains statistical information about the performance of various
- * access points, as experienced by this device.
+ * access points and networks, as experienced by this device.
*
* The purpose is to better inform future network selection and switching
- * by this device.
+ * by this device and help health monitor detect network issues.
*/
@NotThreadSafe
public class WifiScoreCard {
@@ -69,12 +87,50 @@ public class WifiScoreCard {
public static final String DUMP_ARG = "WifiScoreCard";
private static final String TAG = "WifiScoreCard";
- private static final boolean DBG = false;
+ private boolean mVerboseLoggingEnabled = false;
@VisibleForTesting
boolean mPersistentHistograms = true;
private static final int TARGET_IN_MEMORY_ENTRIES = 50;
+ private static final int UNKNOWN_REASON = -1;
+
+ public static final String PER_BSSID_DATA_NAME = "scorecard.proto";
+ public static final String PER_NETWORK_DATA_NAME = "perNetworkData";
+ // Maximum connection duration in seconds to qualify short connection
+ private static final int SHORT_CONNECTION_DURATION_MAX_SEC = 20;
+ // Maximum interval between last RSSI poll and disconnection to qualify
+ // disconnection stats collection.
+ private static final int LAST_RSSI_POLL_MAX_INTERVAL_MS = 3_100;
+ // Minimum number of connection attempts to qualify daily detection
+ @VisibleForTesting
+ static final int MIN_NUM_CONNECTION_ATTEMPT = 20;
+ // Minimum number of connection attempts for historical data
+ private static final int MIN_NUM_CONNECTION_ATTEMPT_PREV = 40;
+ private static final int MIN_NUM_DISCONNECTION = 20;
+ private static final int MIN_NUM_DISCONNECTION_PREV = 40;
+
+ static final int INSUFFICIENT_RECENT_STATS = 0;
+ static final int SUFFICIENT_RECENT_STATS_ONLY = 1;
+ static final int SUFFICIENT_RECENT_PREV_STATS = 2;
+
+ // High and low threshold values for connection failure rate. All of them are in percent with
+ // respect to connection attempts
+ private static final int ONE_HUNDRED_PERCENT = 100;
+ private static final int CONNECTION_FAILURE_PERCENT_HIGH_THRESHOLD = 30;
+ private static final int CONNECTION_FAILURE_PERCENT_LOW_THRESHOLD = 5;
+ private static final int ASSOC_REJECTION_PERCENT_HIGH_THRESHOLD = 10;
+ private static final int ASSOC_REJECTION_PERCENT_LOW_THRESHOLD = 1;
+ private static final int ASSOC_TIMEOUT_PERCENT_HIGH_THRESHOLD = 10;
+ private static final int ASSOC_TIMEOUT_PERCENT_LOW_THRESHOLD = 2;
+ private static final int AUTH_FAILURE_PERCENT_HIGH_THRESHOLD = 10;
+ private static final int AUTH_FAILURE_PERCENT_LOW_THRESHOLD = 2;
+ // High and low threshold values for non-local disconnection rate at high RSSI or high Tx speed
+ // with respect to CNT_DISCONNECTION count (with a recent RSSI poll)
+ private static final int SHORT_CONNECTION_NONLOCAL_PERCENT_HIGH_THRESHOLD = 10;
+ private static final int SHORT_CONNECTION_NONLOCAL_PERCENT_LOW_THRESHOLD = 1;
+ private static final int DISCONNECTION_NONLOCAL_PERCENT_HIGH_THRESHOLD = 15;
+ private static final int DISCONNECTION_NONLOCAL_PERCENT_LOW_THRESHOLD = 1;
private final Clock mClock;
private final String mL2KeySeed;
@@ -94,9 +150,9 @@ public class WifiScoreCard {
/** Our view of the memory store */
public interface MemoryStore {
/** Requests a read, with asynchronous reply */
- void read(String key, BlobListener blobListener);
+ void read(String key, String name, BlobListener blobListener);
/** Requests a write, does not wait for completion */
- void write(String key, byte[] value);
+ void write(String key, String name, byte[] value);
}
/** Asynchronous response to a read request */
public interface BlobListener {
@@ -132,6 +188,15 @@ public class WifiScoreCard {
}
/**
+ * Enable/Disable verbose logging.
+ *
+ * @param verbose true to enable and false to disable.
+ */
+ public void enableVerboseLogging(boolean verbose) {
+ mVerboseLoggingEnabled = verbose;
+ }
+
+ /**
* Timestamp of the start of the most recent connection attempt.
*
* Based on mClock.getElapsedSinceBootMillis().
@@ -140,7 +205,8 @@ public class WifiScoreCard {
* Any negative value means we are not currently connected.
*/
private long mTsConnectionAttemptStart = TS_NONE;
- private static final long TS_NONE = -1;
+ @VisibleForTesting
+ static final long TS_NONE = -1;
/**
* Timestamp captured when we find out about a firmware roam
@@ -165,6 +231,22 @@ public class WifiScoreCard {
private boolean mAttemptingSwitch = false;
/**
+ * SSID of currently connected or connecting network. Used during disconnection
+ */
+ private String mSsidCurr = "";
+ /**
+ * SSID of previously connected network. Used during disconnection when connection attempt
+ * of current network is issued before the disconnection of previous network.
+ */
+ private String mSsidPrev = "";
+ /**
+ * A flag that notes that current disconnection is not generated by wpa_supplicant
+ * which may indicate abnormal disconnection.
+ */
+ private boolean mNonlocalDisconnection = false;
+ private int mDisconnectionReason;
+
+ /**
* @param clock is the time source
* @param l2KeySeed is for making our L2Keys usable only on this device
*/
@@ -172,6 +254,7 @@ public class WifiScoreCard {
mClock = clock;
mL2KeySeed = l2KeySeed;
mDummyPerBssid = new PerBssid("", MacAddress.fromString(DEFAULT_MAC_ADDRESS));
+ mDummyPerNetwork = new PerNetwork("");
}
/**
@@ -182,23 +265,27 @@ public class WifiScoreCard {
if (perBssid == mDummyPerBssid) {
return new Pair<>(null, null);
}
- final long groupIdHash = computeHashLong(perBssid.ssid, mDummyPerBssid.bssid);
- return new Pair<>(perBssid.l2Key, groupHintFromLong(groupIdHash));
+ final long groupIdHash = computeHashLong(
+ perBssid.ssid, mDummyPerBssid.bssid, mL2KeySeed);
+ return new Pair<>(perBssid.getL2Key(), groupHintFromLong(groupIdHash));
}
/**
- * Resets the connection state
+ * Handle network disconnection or shutdown event
*/
public void resetConnectionState() {
- if (DBG && mTsConnectionAttemptStart > TS_NONE && !mAttemptingSwitch) {
- Log.v(TAG, "resetConnectionState", new Exception());
+ String ssidDisconnected = (mAttemptingSwitch) ? mSsidPrev : mSsidCurr;
+ updatePerNetwork(Event.DISCONNECTION, ssidDisconnected, INVALID_RSSI, LINK_SPEED_UNKNOWN,
+ UNKNOWN_REASON);
+ if (mVerboseLoggingEnabled && mTsConnectionAttemptStart > TS_NONE && !mAttemptingSwitch) {
+ Log.v(TAG, "handleNetworkDisconnect", new Exception());
}
resetConnectionStateInternal(true);
}
/**
* @param calledFromResetConnectionState says the call is from outside the class,
- * indicating that we need to resepect the value of mAttemptingSwitch.
+ * indicating that we need to respect the value of mAttemptingSwitch.
*/
private void resetConnectionStateInternal(boolean calledFromResetConnectionState) {
if (!calledFromResetConnectionState) {
@@ -210,22 +297,34 @@ public class WifiScoreCard {
mTsRoam = TS_NONE;
mPolled = false;
mValidated = false;
+ mNonlocalDisconnection = false;
}
/**
- * Updates the score card using relevant parts of WifiInfo
+ * Updates perBssid using relevant parts of WifiInfo
*
* @param wifiInfo object holding relevant values.
*/
- private void update(WifiScoreCardProto.Event event, ExtendedWifiInfo wifiInfo) {
+ private void updatePerBssid(WifiScoreCardProto.Event event, ExtendedWifiInfo wifiInfo) {
PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID());
perBssid.updateEventStats(event,
wifiInfo.getFrequency(),
wifiInfo.getRssi(),
wifiInfo.getLinkSpeed());
perBssid.setNetworkConfigId(wifiInfo.getNetworkId());
+ logd("BSSID update " + event.toString() + " ID: " + perBssid.id + " " + wifiInfo);
+ }
- if (DBG) Log.d(TAG, event.toString() + " ID: " + perBssid.id + " " + wifiInfo);
+ /**
+ * Updates perNetwork with SSID, current RSSI and failureReason. failureReason is meaningful
+ * only during connection failure.
+ */
+ private void updatePerNetwork(WifiScoreCardProto.Event event, String ssid, int rssi,
+ int txSpeed, int failureReason) {
+ PerNetwork perNetwork = lookupNetwork(ssid);
+ logd("network update " + event.toString() + ((ssid == null) ? " " : " "
+ + ssid) + " ID: " + perNetwork.id + " RSSI " + rssi + " txSpeed " + txSpeed);
+ perNetwork.updateEventStats(event, rssi, txSpeed, failureReason);
}
/**
@@ -233,21 +332,33 @@ public class WifiScoreCard {
*
* @param wifiInfo object holding relevant values
*/
- public void noteSignalPoll(ExtendedWifiInfo wifiInfo) {
+ public void noteSignalPoll(@NonNull ExtendedWifiInfo wifiInfo) {
if (!mPolled && wifiInfo.getRssi() != INVALID_RSSI) {
- update(Event.FIRST_POLL_AFTER_CONNECTION, wifiInfo);
+ updatePerBssid(Event.FIRST_POLL_AFTER_CONNECTION, wifiInfo);
mPolled = true;
}
- update(Event.SIGNAL_POLL, wifiInfo);
+ updatePerBssid(Event.SIGNAL_POLL, wifiInfo);
+ int validTxSpeed = geTxLinkSpeedWithSufficientTxRate(wifiInfo);
+ updatePerNetwork(Event.SIGNAL_POLL, wifiInfo.getSSID(), wifiInfo.getRssi(),
+ validTxSpeed, UNKNOWN_REASON);
if (mTsRoam > TS_NONE && wifiInfo.getRssi() != INVALID_RSSI) {
long duration = mClock.getElapsedSinceBootMillis() - mTsRoam;
if (duration >= SUCCESS_MILLIS_SINCE_ROAM) {
- update(Event.ROAM_SUCCESS, wifiInfo);
+ updatePerBssid(Event.ROAM_SUCCESS, wifiInfo);
mTsRoam = TS_NONE;
- doWrites();
+ doWritesBssid();
}
}
}
+
+ private int geTxLinkSpeedWithSufficientTxRate(@NonNull ExtendedWifiInfo wifiInfo) {
+ double txRate = wifiInfo.getTxSuccessRate() + wifiInfo.getTxBadRate()
+ + wifiInfo.getTxRetriesRate();
+ int txSpeed = wifiInfo.getTxLinkSpeedMbps();
+ logd("txRate: " + txRate + " txSpeed: " + txSpeed);
+ return (txRate >= HEALTH_MONITOR_COUNT_MIN_TX_RATE) ? txSpeed : LINK_SPEED_UNKNOWN;
+ }
+
/** Wait a few seconds before considering the roam successful */
private static final long SUCCESS_MILLIS_SINCE_ROAM = 4_000;
@@ -256,8 +367,8 @@ public class WifiScoreCard {
*
* @param wifiInfo object holding relevant values
*/
- public void noteIpConfiguration(ExtendedWifiInfo wifiInfo) {
- update(Event.IP_CONFIGURATION_SUCCESS, wifiInfo);
+ public void noteIpConfiguration(@NonNull ExtendedWifiInfo wifiInfo) {
+ updatePerBssid(Event.IP_CONFIGURATION_SUCCESS, wifiInfo);
mAttemptingSwitch = false;
doWrites();
}
@@ -267,58 +378,83 @@ public class WifiScoreCard {
*
* @param wifiInfo object holding relevant values
*/
- public void noteValidationSuccess(ExtendedWifiInfo wifiInfo) {
+ public void noteValidationSuccess(@NonNull ExtendedWifiInfo wifiInfo) {
if (mValidated) return; // Only once per connection
- update(Event.VALIDATION_SUCCESS, wifiInfo);
+ updatePerBssid(Event.VALIDATION_SUCCESS, wifiInfo);
mValidated = true;
doWrites();
}
/**
+ * Updates the score card after network validation failure
+ *
+ * @param wifiInfo object holding relevant values
+ */
+ public void noteValidationFailure(@NonNull ExtendedWifiInfo wifiInfo) {
+ mValidated = false;
+ }
+
+ /**
* Records the start of a connection attempt
*
* @param wifiInfo may have state about an existing connection
+ * @param scanRssi is the highest RSSI of recent scan found from scanDetailCache
+ * @param ssid is the network SSID of connection attempt
*/
- public void noteConnectionAttempt(ExtendedWifiInfo wifiInfo) {
+ public void noteConnectionAttempt(@NonNull ExtendedWifiInfo wifiInfo,
+ int scanRssi, String ssid) {
// We may or may not be currently connected. If not, simply record the start.
// But if we are connected, wrap up the old one first.
if (mTsConnectionAttemptStart > TS_NONE) {
if (mPolled) {
- update(Event.LAST_POLL_BEFORE_SWITCH, wifiInfo);
+ updatePerBssid(Event.LAST_POLL_BEFORE_SWITCH, wifiInfo);
}
mAttemptingSwitch = true;
}
mTsConnectionAttemptStart = mClock.getElapsedSinceBootMillis();
mPolled = false;
+ mSsidPrev = mSsidCurr;
+ mSsidCurr = ssid;
- if (DBG) Log.d(TAG, "CONNECTION_ATTEMPT" + (mAttemptingSwitch ? " X " : " ") + wifiInfo);
+ updatePerNetwork(Event.CONNECTION_ATTEMPT, ssid, scanRssi, LINK_SPEED_UNKNOWN,
+ UNKNOWN_REASON);
+ logd("CONNECTION_ATTEMPT" + (mAttemptingSwitch ? " X " : " ") + wifiInfo);
}
/**
* Records a newly assigned NetworkAgent netId.
*/
- public void noteNetworkAgentCreated(ExtendedWifiInfo wifiInfo, int networkAgentId) {
+ public void noteNetworkAgentCreated(@NonNull ExtendedWifiInfo wifiInfo, int networkAgentId) {
PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID());
- if (DBG) {
- Log.d(TAG, "NETWORK_AGENT_ID: " + networkAgentId + " ID: " + perBssid.id);
- }
+ logd("NETWORK_AGENT_ID: " + networkAgentId + " ID: " + perBssid.id);
perBssid.mNetworkAgentId = networkAgentId;
}
/**
+ * Record disconnection not initiated by wpa_supplicant in connected mode
+ * @param reason is detailed disconnection reason code
+ */
+ public void noteNonlocalDisconnect(int reason) {
+ mNonlocalDisconnection = true;
+ mDisconnectionReason = reason;
+ logd("nonlocal disconnection with reason: " + reason);
+ }
+
+ /**
* Updates the score card after a failed connection attempt
*
- * @param wifiInfo object holding relevant values
+ * @param wifiInfo object holding relevant values.
+ * @param scanRssi is the highest RSSI of recent scan found from scanDetailCache
+ * @param ssid is the network SSID.
+ * @param failureReason is connection failure reason
*/
- public void noteConnectionFailure(ExtendedWifiInfo wifiInfo,
- int codeMetrics, int codeMetricsProto) {
- if (DBG) {
- Log.d(TAG, "noteConnectionFailure(..., " + codeMetrics + ", " + codeMetricsProto + ")");
- }
- // TODO(b/112196799) Need to sort out the reasons better. Also, we get here
- // when we disconnect from below, so it should sometimes get counted as a
- // disconnection rather than a connection failure.
- update(Event.CONNECTION_FAILURE, wifiInfo);
+ public void noteConnectionFailure(@NonNull ExtendedWifiInfo wifiInfo,
+ int scanRssi, String ssid, @FailureReason int failureReason) {
+ // TODO: add the breakdown of level2FailureReason
+ updatePerBssid(Event.CONNECTION_FAILURE, wifiInfo);
+
+ updatePerNetwork(Event.CONNECTION_FAILURE, ssid, scanRssi, LINK_SPEED_UNKNOWN,
+ failureReason);
resetConnectionStateInternal(false);
}
@@ -327,13 +463,14 @@ public class WifiScoreCard {
*
* @param wifiInfo object holding relevant values
*/
- public void noteIpReachabilityLost(ExtendedWifiInfo wifiInfo) {
- update(Event.IP_REACHABILITY_LOST, wifiInfo);
+ public void noteIpReachabilityLost(@NonNull ExtendedWifiInfo wifiInfo) {
+ updatePerBssid(Event.IP_REACHABILITY_LOST, wifiInfo);
if (mTsRoam > TS_NONE) {
mTsConnectionAttemptStart = mTsRoam; // just to update elapsed
- update(Event.ROAM_FAILURE, wifiInfo);
+ updatePerBssid(Event.ROAM_FAILURE, wifiInfo);
}
- resetConnectionStateInternal(false);
+ // No need to call resetConnectionStateInternal() because
+ // resetConnectionState() will be called after WifiNative.disconnect() in ClientModeImpl
doWrites();
}
@@ -345,8 +482,8 @@ public class WifiScoreCard {
*
* @param wifiInfo object holding relevant values
*/
- public void noteRoam(ExtendedWifiInfo wifiInfo) {
- update(Event.LAST_POLL_BEFORE_ROAM, wifiInfo);
+ public void noteRoam(@NonNull ExtendedWifiInfo wifiInfo) {
+ updatePerBssid(Event.LAST_POLL_BEFORE_ROAM, wifiInfo);
mTsRoam = mClock.getElapsedSinceBootMillis();
}
@@ -356,10 +493,9 @@ public class WifiScoreCard {
* @param wifiInfo object holding old values
* @param state the new supplicant state
*/
- public void noteSupplicantStateChanging(ExtendedWifiInfo wifiInfo, SupplicantState state) {
- if (DBG) {
- Log.d(TAG, "Changing state to " + state + " " + wifiInfo);
- }
+ public void noteSupplicantStateChanging(@NonNull ExtendedWifiInfo wifiInfo,
+ SupplicantState state) {
+ logd("Changing state to " + state + " " + wifiInfo);
}
/**
@@ -368,9 +504,7 @@ public class WifiScoreCard {
* @param wifiInfo object holding old values
*/
public void noteSupplicantStateChanged(ExtendedWifiInfo wifiInfo) {
- if (DBG) {
- Log.d(TAG, "STATE " + wifiInfo);
- }
+ logd("STATE " + wifiInfo);
}
/**
@@ -378,8 +512,8 @@ public class WifiScoreCard {
*
* @param wifiInfo object holding relevant values
*/
- public void noteWifiDisabled(ExtendedWifiInfo wifiInfo) {
- update(Event.WIFI_DISABLED, wifiInfo);
+ public void noteWifiDisabled(@NonNull ExtendedWifiInfo wifiInfo) {
+ updatePerBssid(Event.WIFI_DISABLED, wifiInfo);
resetConnectionStateInternal(false);
doWrites();
}
@@ -445,9 +579,8 @@ public class WifiScoreCard {
}
}
- final class PerBssid {
+ final class PerBssid extends MemoryStoreAccessBase {
public int id;
- public final String l2Key;
public final String ssid;
public final MacAddress bssid;
public final int[] blocklistStreakCount =
@@ -463,11 +596,10 @@ public class WifiScoreCard {
private final Map<Pair<Event, Integer>, PerSignal>
mSignalForEventAndFrequency = new ArrayMap<>();
PerBssid(String ssid, MacAddress bssid) {
+ super(computeHashLong(ssid, bssid, mL2KeySeed));
this.ssid = ssid;
this.bssid = bssid;
- final long hash = computeHashLong(ssid, bssid);
- this.l2Key = l2KeyFromLong(hash);
- this.id = idFromLong(hash);
+ this.id = idFromLong();
this.changed = false;
this.referenced = false;
}
@@ -543,7 +675,7 @@ public class WifiScoreCard {
if (mSecurityType == null) {
mSecurityType = prev;
} else if (!mSecurityType.equals(prev)) {
- if (DBG) {
+ if (mVerboseLoggingEnabled) {
Log.i(TAG, "ID: " + id
+ "SecurityType changed: " + prev + " to " + mSecurityType);
}
@@ -564,19 +696,7 @@ public class WifiScoreCard {
}
return this;
}
- String getL2Key() {
- return l2Key.toString();
- }
- /**
- * Called when the (asynchronous) answer to a read request comes back.
- */
- void lazyMerge(byte[] serialized) {
- if (serialized == null) return;
- byte[] old = mPendingReadFromStore.getAndSet(serialized);
- if (old != null) {
- Log.e(TAG, "More answers than we expected!");
- }
- }
+
/**
* Handles (when convenient) the arrival of previously stored data.
*
@@ -586,7 +706,7 @@ public class WifiScoreCard {
* data before now, so we need to be prepared to merge the new and old together.
*/
void finishPendingRead() {
- final byte[] serialized = mPendingReadFromStore.getAndSet(null);
+ final byte[] serialized = finishPendingReadBytes();
if (serialized == null) return;
AccessPoint ap;
try {
@@ -597,9 +717,543 @@ public class WifiScoreCard {
}
merge(ap);
}
+ }
+
+ /**
+ * A class collecting the connection stats of one network or SSID.
+ */
+ final class PerNetwork extends MemoryStoreAccessBase {
+ public int id;
+ public final String ssid;
+ public boolean changed;
+ private int mLastRssiPoll = INVALID_RSSI;
+ private int mLastTxSpeedPoll = LINK_SPEED_UNKNOWN;
+ private long mLastRssiPollTimeMs = TS_NONE;
+ private long mConnectionSessionStartTimeMs = TS_NONE;
+ private NetworkConnectionStats mRecentStats;
+ private NetworkConnectionStats mStatsCurrBuild;
+ private NetworkConnectionStats mStatsPrevBuild;
+
+ PerNetwork(String ssid) {
+ super(computeHashLong(ssid, MacAddress.fromString(DEFAULT_MAC_ADDRESS), mL2KeySeed));
+ this.ssid = ssid;
+ this.id = idFromLong();
+ this.changed = false;
+ mRecentStats = new NetworkConnectionStats();
+ mStatsCurrBuild = new NetworkConnectionStats();
+ mStatsPrevBuild = new NetworkConnectionStats();
+ }
+
+ void updateEventStats(Event event, int rssi, int txSpeed, int failureReason) {
+ finishPendingRead();
+ long currTimeMs = mClock.getWallClockMillis();
+ switch (event) {
+ case SIGNAL_POLL:
+ mLastRssiPoll = rssi;
+ mLastRssiPollTimeMs = currTimeMs;
+ mLastTxSpeedPoll = txSpeed;
+ changed = true;
+ break;
+ case CONNECTION_ATTEMPT:
+ logd(" scan rssi: " + rssi);
+ if (rssi >= HEALTH_MONITOR_COUNT_RSSI_MIN_DBM) {
+ mRecentStats.incrementCount(CNT_CONNECTION_ATTEMPT);
+ }
+ mConnectionSessionStartTimeMs = currTimeMs;
+ changed = true;
+ break;
+ case CONNECTION_FAILURE:
+ mConnectionSessionStartTimeMs = TS_NONE;
+ if (rssi >= HEALTH_MONITOR_COUNT_RSSI_MIN_DBM) {
+ if (failureReason != BssidBlocklistMonitor.REASON_WRONG_PASSWORD) {
+ mRecentStats.incrementCount(CNT_CONNECTION_FAILURE);
+ }
+ switch (failureReason) {
+ case BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA:
+ case BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION:
+ mRecentStats.incrementCount(CNT_ASSOCIATION_REJECTION);
+ break;
+ case BssidBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT:
+ mRecentStats.incrementCount(CNT_ASSOCIATION_TIMEOUT);
+ break;
+ case BssidBlocklistMonitor.REASON_AUTHENTICATION_FAILURE:
+ case BssidBlocklistMonitor.REASON_EAP_FAILURE:
+ mRecentStats.incrementCount(CNT_AUTHENTICATION_FAILURE);
+ break;
+ case BssidBlocklistMonitor.REASON_WRONG_PASSWORD:
+ case BssidBlocklistMonitor.REASON_DHCP_FAILURE:
+ default:
+ break;
+ }
+ }
+ changed = true;
+ break;
+ case WIFI_DISABLED:
+ case DISCONNECTION:
+ handleDisconnection();
+ changed = true;
+ break;
+ default:
+ break;
+ }
+ logd(this.toString());
+ }
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(" LastRssiPollTime: " + mLastRssiPollTimeMs);
+ sb.append(" LastRssiPoll: " + mLastRssiPoll);
+ sb.append(" LastTxSpeedPoll: " + mLastTxSpeedPoll);
+ sb.append("\n");
+ sb.append(" StatsRecent: " + mRecentStats.toString());
+ sb.append("\n");
+ sb.append(" StatsCurr: " + mStatsCurrBuild.toString());
+ sb.append("\n");
+ sb.append(" StatsPrev: " + mStatsPrevBuild.toString());
+ return sb.toString();
+ }
+ private void handleDisconnection() {
+ if (mConnectionSessionStartTimeMs > TS_NONE) {
+ long currTimeMs = mClock.getWallClockMillis();
+ int currSessionDurationSec = (int) ((currTimeMs
+ - mConnectionSessionStartTimeMs) / 1000);
+ mRecentStats.accumulate(CNT_CONNECTION_DURATION_SEC, currSessionDurationSec);
+ long timeSinceLastRssiPollMs = currTimeMs - mLastRssiPollTimeMs;
+ boolean hasRecentRssiPoll = (mLastRssiPollTimeMs > TS_NONE
+ && timeSinceLastRssiPollMs <= LAST_RSSI_POLL_MAX_INTERVAL_MS);
+ if (hasRecentRssiPoll) {
+ mRecentStats.incrementCount(CNT_DISCONNECTION);
+ }
+ if (mNonlocalDisconnection && hasRecentRssiPoll
+ && (mLastRssiPoll >= HEALTH_MONITOR_COUNT_RSSI_MIN_DBM
+ || mLastTxSpeedPoll >= HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS)) {
+ mRecentStats.incrementCount(CNT_DISCONNECTION_NONLOCAL);
+ if (currSessionDurationSec <= SHORT_CONNECTION_DURATION_MAX_SEC) {
+ mRecentStats.incrementCount(CNT_SHORT_CONNECTION_NONLOCAL);
+ }
+ }
+ }
+ mConnectionSessionStartTimeMs = TS_NONE;
+ mLastRssiPollTimeMs = TS_NONE;
+ }
+ @NonNull NetworkConnectionStats getRecentStats() {
+ return mRecentStats;
+ }
+ @NonNull NetworkConnectionStats getStatsCurrBuild() {
+ return mStatsCurrBuild;
+ }
+ @NonNull NetworkConnectionStats getStatsPrevBuild() {
+ return mStatsPrevBuild;
+ }
+
+ /**
+ /* Detect a significant failure stats change with historical data
+ /* or high failure stats without historical data.
+ /* @return 0 if recentStats doesn't have sufficient data
+ * 1 if recentStats has sufficient data while statsPrevBuild doesn't
+ * 2 if recentStats and statsPrevBuild have sufficient data
+ */
+ int dailyDetection(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh) {
+ finishPendingRead();
+ dailyDetectionDisconnectionEvent(statsDec, statsInc, statsHigh);
+ return dailyDetectionConnectionEvent(statsDec, statsInc, statsHigh);
+ }
+
+ private int dailyDetectionConnectionEvent(FailureStats statsDec, FailureStats statsInc,
+ FailureStats statsHigh) {
+ // Skip daily detection if recentStats is not sufficient
+ if (!isRecentConnectionStatsSufficient()) return INSUFFICIENT_RECENT_STATS;
+ if (mStatsPrevBuild.getCount(CNT_CONNECTION_ATTEMPT)
+ < MIN_NUM_CONNECTION_ATTEMPT_PREV) {
+ // don't have enough historical data,
+ // so only detect high failure stats without relying on mStatsPrevBuild.
+ // Increase low threshold so that mStatsPrevBuild is always below it
+ // statsHigh only depends on mRecentStats.
+ FailureStats statsDummy = new FailureStats();
+ statsDeltaDetectionConnection(statsDummy, statsHigh, ONE_HUNDRED_PERCENT);
+ return SUFFICIENT_RECENT_STATS_ONLY;
+ } else {
+ // mStatsPrevBuild has enough updates,
+ // detect improvement or degradation with normal threshold values.
+ statsDeltaDetectionConnection(statsDec, statsInc, /* thresholdLowOffset */ 0);
+ return SUFFICIENT_RECENT_PREV_STATS;
+ }
+ }
+
+ private void dailyDetectionDisconnectionEvent(FailureStats statsDec, FailureStats statsInc,
+ FailureStats statsHigh) {
+ // Skip daily detection if recentStats is not sufficient
+ if (mRecentStats.getCount(CNT_DISCONNECTION) < MIN_NUM_DISCONNECTION) return;
+ if (mStatsPrevBuild.getCount(CNT_DISCONNECTION) < MIN_NUM_DISCONNECTION_PREV) {
+ FailureStats statsDummy = new FailureStats();
+ statsDeltaDetectionDisconnection(statsDummy, statsHigh, ONE_HUNDRED_PERCENT);
+ } else {
+ statsDeltaDetectionDisconnection(statsDec, statsInc, /* thresholdLowOffset */ 0);
+ }
+ }
+
+ private void statsDeltaDetectionConnection(FailureStats statsDec,
+ FailureStats statsInc, int thresholdLowOffset) {
+ statsDeltaDetection(statsDec, statsInc, CNT_CONNECTION_FAILURE,
+ REASON_CONNECTION_FAILURE,
+ CONNECTION_FAILURE_PERCENT_HIGH_THRESHOLD,
+ CONNECTION_FAILURE_PERCENT_LOW_THRESHOLD + thresholdLowOffset,
+ CNT_CONNECTION_ATTEMPT);
+ statsDeltaDetection(statsDec, statsInc, CNT_AUTHENTICATION_FAILURE,
+ REASON_AUTH_FAILURE,
+ AUTH_FAILURE_PERCENT_HIGH_THRESHOLD,
+ AUTH_FAILURE_PERCENT_LOW_THRESHOLD + thresholdLowOffset,
+ CNT_CONNECTION_ATTEMPT);
+ statsDeltaDetection(statsDec, statsInc, CNT_ASSOCIATION_REJECTION,
+ REASON_ASSOC_REJECTION,
+ ASSOC_REJECTION_PERCENT_HIGH_THRESHOLD,
+ ASSOC_REJECTION_PERCENT_LOW_THRESHOLD + thresholdLowOffset,
+ CNT_CONNECTION_ATTEMPT);
+ statsDeltaDetection(statsDec, statsInc, CNT_ASSOCIATION_TIMEOUT,
+ REASON_ASSOC_TIMEOUT,
+ ASSOC_TIMEOUT_PERCENT_HIGH_THRESHOLD,
+ ASSOC_TIMEOUT_PERCENT_LOW_THRESHOLD + thresholdLowOffset,
+ CNT_CONNECTION_ATTEMPT);
+ }
+
+ private void statsDeltaDetectionDisconnection(FailureStats statsDec,
+ FailureStats statsInc, int thresholdLowOffset) {
+ statsDeltaDetection(statsDec, statsInc, CNT_SHORT_CONNECTION_NONLOCAL,
+ REASON_SHORT_CONNECTION_NONLOCAL,
+ SHORT_CONNECTION_NONLOCAL_PERCENT_HIGH_THRESHOLD,
+ SHORT_CONNECTION_NONLOCAL_PERCENT_LOW_THRESHOLD + thresholdLowOffset,
+ CNT_DISCONNECTION);
+ statsDeltaDetection(statsDec, statsInc, CNT_DISCONNECTION_NONLOCAL,
+ REASON_DISCONNECTION_NONLOCAL,
+ DISCONNECTION_NONLOCAL_PERCENT_HIGH_THRESHOLD,
+ DISCONNECTION_NONLOCAL_PERCENT_LOW_THRESHOLD + thresholdLowOffset,
+ CNT_DISCONNECTION);
+ }
+
+ private boolean statsDeltaDetection(FailureStats statsDec,
+ FailureStats statsInc, int countCode, int reasonCode,
+ int highThreshold, int lowThreshold, int refCountCode) {
+ if (isRateBelowThreshold(mStatsPrevBuild, countCode, lowThreshold, refCountCode)
+ && isRateAboveThreshold(mRecentStats, countCode, highThreshold, refCountCode)) {
+ statsInc.incrementCount(reasonCode);
+ return true;
+ }
+ if (isRateAboveThreshold(mStatsPrevBuild, countCode, highThreshold, refCountCode)
+ && isRateBelowThreshold(mRecentStats, countCode, lowThreshold, refCountCode)) {
+ statsDec.incrementCount(reasonCode);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isRateAboveThreshold(NetworkConnectionStats stats,
+ @ConnectionCountCode int countCode, int threshold, int refCountCode) {
+ return (stats.getCount(countCode) * ONE_HUNDRED_PERCENT)
+ >= (threshold * stats.getCount(refCountCode));
+ }
+
+ private boolean isRateBelowThreshold(NetworkConnectionStats stats,
+ @ConnectionCountCode int countCode, int threshold, int refCountCode) {
+ return (stats.getCount(countCode) * ONE_HUNDRED_PERCENT)
+ <= (threshold * stats.getCount(refCountCode));
+ }
+
+ private boolean isRecentConnectionStatsSufficient() {
+ return (mRecentStats.getCount(CNT_CONNECTION_ATTEMPT) >= MIN_NUM_CONNECTION_ATTEMPT);
+ }
+
+ // Update StatsCurrBuild with recentStats and clear recentStats
+ void updateAfterDailyDetection() {
+ // Skip update if recentStats is not sufficient since daily detection is also skipped
+ if (!isRecentConnectionStatsSufficient()) return;
+ mStatsCurrBuild.accumulateAll(mRecentStats);
+ mRecentStats.clear();
+ changed = true;
+ }
+
+ // Refresh StatsPrevBuild with StatsCurrBuild which is cleared afterwards
+ void updateAfterSwBuildChange() {
+ finishPendingRead();
+ mStatsPrevBuild.copy(mStatsCurrBuild);
+ mRecentStats.clear();
+ mStatsCurrBuild.clear();
+ changed = true;
+ }
+
+ NetworkStats toNetworkStats() {
+ finishPendingRead();
+ NetworkStats.Builder builder = NetworkStats.newBuilder();
+ builder.setId(id);
+ builder.setRecentStats(toConnectionStats(mRecentStats));
+ builder.setStatsCurrBuild(toConnectionStats(mStatsCurrBuild));
+ builder.setStatsPrevBuild(toConnectionStats(mStatsPrevBuild));
+ return builder.build();
+ }
+
+ private ConnectionStats toConnectionStats(NetworkConnectionStats stats) {
+ ConnectionStats.Builder builder = ConnectionStats.newBuilder();
+ builder.setNumConnectionAttempt(stats.getCount(CNT_CONNECTION_ATTEMPT));
+ builder.setNumConnectionFailure(stats.getCount(CNT_CONNECTION_FAILURE));
+ builder.setConnectionDurationSec(stats.getCount(CNT_CONNECTION_DURATION_SEC));
+ builder.setNumDisconnectionNonlocal(stats.getCount(CNT_DISCONNECTION_NONLOCAL));
+ builder.setNumDisconnection(stats.getCount(CNT_DISCONNECTION));
+ builder.setNumShortConnectionNonlocal(stats.getCount(CNT_SHORT_CONNECTION_NONLOCAL));
+ builder.setNumAssociationRejection(stats.getCount(CNT_ASSOCIATION_REJECTION));
+ builder.setNumAssociationTimeout(stats.getCount(CNT_ASSOCIATION_TIMEOUT));
+ builder.setNumAuthenticationFailure(stats.getCount(CNT_AUTHENTICATION_FAILURE));
+ return builder.build();
+ }
+
+ void finishPendingRead() {
+ final byte[] serialized = finishPendingReadBytes();
+ if (serialized == null) return;
+ NetworkStats ns;
+ try {
+ ns = NetworkStats.parseFrom(serialized);
+ } catch (InvalidProtocolBufferException e) {
+ Log.e(TAG, "Failed to deserialize", e);
+ return;
+ }
+ mergeNetworkStatsFromMemory(ns);
+ changed = true;
+ }
+
+ PerNetwork mergeNetworkStatsFromMemory(@NonNull NetworkStats ns) {
+ if (ns.hasId() && this.id != ns.getId()) {
+ return this;
+ }
+ if (ns.hasRecentStats()) {
+ ConnectionStats recentStats = ns.getRecentStats();
+ mergeConnectionStats(recentStats, mRecentStats);
+ }
+ if (ns.hasStatsCurrBuild()) {
+ ConnectionStats statsCurr = ns.getStatsCurrBuild();
+ mStatsCurrBuild.clear();
+ mergeConnectionStats(statsCurr, mStatsCurrBuild);
+ }
+ if (ns.hasStatsPrevBuild()) {
+ ConnectionStats statsPrev = ns.getStatsPrevBuild();
+ mStatsPrevBuild.clear();
+ mergeConnectionStats(statsPrev, mStatsPrevBuild);
+ }
+ return this;
+ }
+
+ private void mergeConnectionStats(ConnectionStats source, NetworkConnectionStats target) {
+ if (source.hasNumConnectionAttempt()) {
+ target.accumulate(CNT_CONNECTION_ATTEMPT, source.getNumConnectionAttempt());
+ }
+ if (source.hasNumConnectionFailure()) {
+ target.accumulate(CNT_CONNECTION_ATTEMPT, source.getNumConnectionFailure());
+ }
+ if (source.hasConnectionDurationSec()) {
+ target.accumulate(CNT_CONNECTION_DURATION_SEC, source.getConnectionDurationSec());
+ }
+ if (source.hasNumDisconnectionNonlocal()) {
+ target.accumulate(CNT_DISCONNECTION_NONLOCAL, source.getNumDisconnectionNonlocal());
+ }
+ if (source.hasNumDisconnection()) {
+ target.accumulate(CNT_DISCONNECTION, source.getNumDisconnection());
+ }
+ if (source.hasNumShortConnectionNonlocal()) {
+ target.accumulate(CNT_SHORT_CONNECTION_NONLOCAL,
+ source.getNumShortConnectionNonlocal());
+ }
+ if (source.hasNumAssociationRejection()) {
+ target.accumulate(CNT_ASSOCIATION_REJECTION, source.getNumAssociationRejection());
+ }
+ if (source.hasNumAssociationTimeout()) {
+ target.accumulate(CNT_ASSOCIATION_TIMEOUT, source.getNumAssociationTimeout());
+ }
+ if (source.hasNumAuthenticationFailure()) {
+ target.accumulate(CNT_AUTHENTICATION_FAILURE, source.getNumAuthenticationFailure());
+ }
+ }
+ }
+
+ // Codes for various connection related counts
+ public static final int CNT_CONNECTION_ATTEMPT = 0;
+ public static final int CNT_CONNECTION_FAILURE = 1;
+ public static final int CNT_CONNECTION_DURATION_SEC = 2;
+ public static final int CNT_ASSOCIATION_REJECTION = 3;
+ public static final int CNT_ASSOCIATION_TIMEOUT = 4;
+ public static final int CNT_AUTHENTICATION_FAILURE = 5;
+ public static final int CNT_SHORT_CONNECTION_NONLOCAL = 6;
+ public static final int CNT_DISCONNECTION_NONLOCAL = 7;
+ public static final int CNT_DISCONNECTION = 8;
+ // Constant being used to keep track of how many counter there are.
+ public static final int NUMBER_CONNECTION_CNT_CODE = 9;
+ private static final String[] CONNECTION_CNT_NAME = {
+ " ConnectAttempt: ",
+ " ConnectFailure: ",
+ " ConnectDurSec: ",
+ " AssocRej: ",
+ " AssocTimeout: ",
+ " AuthFailure: ",
+ " ShortDiscNonlocal: ",
+ " DisconnectNonlocal: ",
+ " Disconnect: "
+ };
+
+ @IntDef(prefix = { "CNT_" }, value = {
+ CNT_CONNECTION_ATTEMPT,
+ CNT_CONNECTION_FAILURE,
+ CNT_CONNECTION_DURATION_SEC,
+ CNT_ASSOCIATION_REJECTION,
+ CNT_ASSOCIATION_TIMEOUT,
+ CNT_AUTHENTICATION_FAILURE,
+ CNT_SHORT_CONNECTION_NONLOCAL,
+ CNT_DISCONNECTION_NONLOCAL,
+ CNT_DISCONNECTION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ConnectionCountCode {}
+
+ /**
+ * A class maintaining the connection related statistics of a Wifi network.
+ */
+ public static class NetworkConnectionStats {
+ private final int[] mCount = new int[NUMBER_CONNECTION_CNT_CODE];
+
+ /**
+ * Copy all values
+ * @param src is the source of copy
+ */
+ public void copy(NetworkConnectionStats src) {
+ for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) {
+ mCount[i] = src.getCount(i);
+ }
+ }
+
+ /**
+ * Clear all counters
+ */
+ public void clear() {
+ for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) {
+ mCount[i] = 0;
+ }
+ }
+
+ /**
+ * Get counter value
+ * @param countCode is the selected counter
+ * @return the value of selected counter
+ */
+ public int getCount(@ConnectionCountCode int countCode) {
+ return mCount[countCode];
+ }
+
+ /**
+ * Set counterer value
+ * @param countCode is the selected counter
+ * @param cnt is the value set to the selected counter
+ */
+ public void setCount(@ConnectionCountCode int countCode, int cnt) {
+ mCount[countCode] = cnt;
+ }
+
+ /**
+ * Increment count value by 1
+ * @param countCode is the selected counter
+ */
+ public void incrementCount(@ConnectionCountCode int countCode) {
+ mCount[countCode]++;
+ }
+
+ /**
+ * Decrement count value by 1
+ * @param countCode is the selected counter
+ */
+ public void decrementCount(@ConnectionCountCode int countCode) {
+ mCount[countCode]--;
+ }
+
+ /**
+ * Add and accumulate the selected counter
+ * @param countCode is the selected counter
+ * @param cnt is the value to be added to the counter
+ */
+ public void accumulate(@ConnectionCountCode int countCode, int cnt) {
+ mCount[countCode] += cnt;
+ }
+
+ /**
+ * Accumulate daily stats to historical data
+ * @param recentStats are the raw daily counts
+ */
+ public void accumulateAll(NetworkConnectionStats recentStats) {
+ // 32-bit counter in second can support connection duration up to 68 years.
+ // Similarly 32-bit counter can support up to continuous connection attempt
+ // up to 68 years with one attempt per second.
+ for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) {
+ mCount[i] += recentStats.getCount(i);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) {
+ sb.append(CONNECTION_CNT_NAME[i]);
+ sb.append(mCount[i]);
+ }
+ return sb.toString();
+ }
+ }
+ /**
+ * A base class dealing with common operations of MemoryStore.
+ */
+ public static class MemoryStoreAccessBase {
+ private final String mL2Key;
+ private final long mHash;
+ private static final String TAG = "WifiMemoryStoreAccessBase";
private final AtomicReference<byte[]> mPendingReadFromStore = new AtomicReference<>();
+ MemoryStoreAccessBase(long hash) {
+ mHash = hash;
+ mL2Key = l2KeyFromLong();
+ }
+ String getL2Key() {
+ return mL2Key;
+ }
+
+ private String l2KeyFromLong() {
+ return "W" + Long.toHexString(mHash);
+ }
+
+ /**
+ * Callback function when MemoryStore read is done
+ * @param serialized is the readback value
+ */
+ void readBackListener(byte[] serialized) {
+ if (serialized == null) return;
+ byte[] old = mPendingReadFromStore.getAndSet(serialized);
+ if (old != null) {
+ Log.e(TAG, "More answers than we expected!");
+ }
+ }
+ /**
+ * Handles (when convenient) the arrival of previously stored data.
+ *
+ * The response from IpMemoryStore arrives on a different thread, so we
+ * defer handling it until here, when we're on our favorite thread and
+ * in a good position to deal with it. We may have already collected some
+ * data before now, so we need to be prepared to merge the new and old together.
+ */
+ byte[] finishPendingReadBytes() {
+ return mPendingReadFromStore.getAndSet(null);
+ }
+
+
+ int idFromLong() {
+ return (int) mHash & 0x7fffffff;
+ }
}
+ private void logd(String string) {
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, string);
+ }
+ }
// Returned by lookupBssid when the BSSID is not available,
// for instance when we are not associated.
private final PerBssid mDummyPerBssid;
@@ -630,7 +1284,7 @@ public class WifiScoreCard {
Log.i(TAG, "Discarding stats for score card (ssid changed) ID: " + old.id);
if (old.referenced) mApForBssidReferenced--;
}
- requestReadForPerBssid(ans);
+ requestReadBssid(ans);
}
if (!ans.referenced) {
ans.referenced = true;
@@ -640,20 +1294,61 @@ public class WifiScoreCard {
return ans;
}
- private void requestReadForPerBssid(final PerBssid perBssid) {
+ private void requestReadBssid(final PerBssid perBssid) {
if (mMemoryStore != null) {
- mMemoryStore.read(perBssid.getL2Key(), (value) -> perBssid.lazyMerge(value));
+ mMemoryStore.read(perBssid.getL2Key(), PER_BSSID_DATA_NAME,
+ (value) -> perBssid.readBackListener(value));
}
}
private void requestReadForAllChanged() {
for (PerBssid perBssid : mApForBssid.values()) {
if (perBssid.changed) {
- requestReadForPerBssid(perBssid);
+ requestReadBssid(perBssid);
}
}
}
+ // Returned by lookupNetwork when the network is not available,
+ // for instance when we are not associated.
+ private final PerNetwork mDummyPerNetwork;
+ private final Map<String, PerNetwork> mApForNetwork = new ArrayMap<>();
+ PerNetwork lookupNetwork(String ssid) {
+ if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)) {
+ return mDummyPerNetwork;
+ }
+
+ PerNetwork ans = mApForNetwork.get(ssid);
+ if (ans == null) {
+ ans = new PerNetwork(ssid);
+ mApForNetwork.put(ssid, ans);
+ requestReadNetwork(ans);
+ }
+ return ans;
+ }
+
+ /**
+ * Remove network from cache and memory store
+ * @param ssid is the network SSID
+ */
+ public void removeNetwork(String ssid) {
+ if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)) {
+ return;
+ }
+ mApForNetwork.remove(ssid);
+ if (mMemoryStore == null) return;
+ PerNetwork ans = new PerNetwork(ssid);
+ byte[] serialized = {};
+ mMemoryStore.write(ans.getL2Key(), PER_NETWORK_DATA_NAME, serialized);
+ }
+
+ void requestReadNetwork(final PerNetwork perNetwork) {
+ if (mMemoryStore != null) {
+ mMemoryStore.read(perNetwork.getL2Key(), PER_NETWORK_DATA_NAME,
+ (value) -> perNetwork.readBackListener(value));
+ }
+ }
+
/**
* Issues write requests for all changed entries.
*
@@ -664,6 +1359,10 @@ public class WifiScoreCard {
* @returns number of writes issued.
*/
public int doWrites() {
+ return doWritesBssid() + doWritesNetwork();
+ }
+
+ private int doWritesBssid() {
if (mMemoryStore == null) return 0;
int count = 0;
int bytes = 0;
@@ -671,13 +1370,33 @@ public class WifiScoreCard {
if (perBssid.changed) {
perBssid.finishPendingRead();
byte[] serialized = perBssid.toAccessPoint(/* No BSSID */ true).toByteArray();
- mMemoryStore.write(perBssid.getL2Key(), serialized);
+ mMemoryStore.write(perBssid.getL2Key(), PER_BSSID_DATA_NAME, serialized);
perBssid.changed = false;
count++;
bytes += serialized.length;
}
}
- if (DBG && count > 0) {
+ if (mVerboseLoggingEnabled && count > 0) {
+ Log.v(TAG, "Write count: " + count + ", bytes: " + bytes);
+ }
+ return count;
+ }
+
+ private int doWritesNetwork() {
+ if (mMemoryStore == null) return 0;
+ int count = 0;
+ int bytes = 0;
+ for (PerNetwork perNetwork : mApForNetwork.values()) {
+ if (perNetwork.changed) {
+ perNetwork.finishPendingRead();
+ byte[] serialized = perNetwork.toNetworkStats().toByteArray();
+ mMemoryStore.write(perNetwork.getL2Key(), PER_NETWORK_DATA_NAME, serialized);
+ perNetwork.changed = false;
+ count++;
+ bytes += serialized.length;
+ }
+ }
+ if (mVerboseLoggingEnabled && count > 0) {
Log.v(TAG, "Write count: " + count + ", bytes: " + bytes);
}
return count;
@@ -695,7 +1414,7 @@ public class WifiScoreCard {
private void clean() {
if (mMemoryStore == null) return;
if (mApForBssidReferenced >= mApForBssidTargetSize) {
- doWrites(); // Do not want to evict changed items
+ doWritesBssid(); // Do not want to evict changed items
// Evict the unreferenced ones, and clear all the referenced bits for the next round.
Iterator<Map.Entry<MacAddress, PerBssid>> it = mApForBssid.entrySet().iterator();
while (it.hasNext()) {
@@ -704,17 +1423,24 @@ public class WifiScoreCard {
perBssid.referenced = false;
} else {
it.remove();
- if (DBG) Log.v(TAG, "Evict " + perBssid.id);
+ if (mVerboseLoggingEnabled) Log.v(TAG, "Evict " + perBssid.id);
}
}
mApForBssidReferenced = 0;
}
}
- private long computeHashLong(String ssid, MacAddress mac) {
+ /**
+ * Compute a hash value with the given SSID and MAC address
+ * @param ssid is the network SSID
+ * @param mac is the network MAC address
+ * @param l2KeySeed is the seed for hash generation
+ * @return
+ */
+ public static long computeHashLong(String ssid, MacAddress mac, String l2KeySeed) {
byte[][] parts = {
// Our seed keeps the L2Keys specific to this device
- mL2KeySeed.getBytes(),
+ l2KeySeed.getBytes(),
// ssid is either quoted utf8 or hex-encoded bytes; turn it into plain bytes.
NativeUtil.byteArrayFromArrayList(NativeUtil.decodeSsid(ssid)),
// And the BSSID
@@ -746,14 +1472,6 @@ public class WifiScoreCard {
return buffer.getLong();
}
- private static int idFromLong(long hash) {
- return (int) hash & 0x7fffffff;
- }
-
- private static String l2KeyFromLong(long hash) {
- return "W" + Long.toHexString(hash);
- }
-
private static String groupHintFromLong(long hash) {
return "G" + Long.toHexString(hash);
}
@@ -764,11 +1482,21 @@ public class WifiScoreCard {
}
@VisibleForTesting
+ PerNetwork fetchByNetwork(String ssid) {
+ return mApForNetwork.get(ssid);
+ }
+
+ @VisibleForTesting
PerBssid perBssidFromAccessPoint(String ssid, AccessPoint ap) {
MacAddress bssid = MacAddress.fromBytes(ap.getBssid().toByteArray());
return new PerBssid(ssid, bssid).merge(ap);
}
+ @VisibleForTesting
+ PerNetwork perNetworkFromNetworkStats(String ssid, NetworkStats ns) {
+ return new PerNetwork(ssid).mergeNetworkStatsFromMemory(ns);
+ }
+
final class PerSignal {
public final Event event;
public final int frequency;
@@ -794,6 +1522,7 @@ public class WifiScoreCard {
case IP_CONFIGURATION_SUCCESS:
case VALIDATION_SUCCESS:
case CONNECTION_FAILURE:
+ case DISCONNECTION:
case WIFI_DISABLED:
case ROAM_FAILURE:
this.elapsedMs = new PerUnivariateStatistic();
@@ -822,8 +1551,9 @@ public class WifiScoreCard {
if (elapsedMs != null) {
builder.setElapsedMs(elapsedMs.toUnivariateStatistic());
}
- if (DBG && rssi.intHistogram != null && rssi.intHistogram.numNonEmptyBuckets() > 0) {
- Log.d(TAG, "Histogram " + event + " RSSI" + rssi.intHistogram);
+ if (rssi.intHistogram != null
+ && rssi.intHistogram.numNonEmptyBuckets() > 0) {
+ logd("Histogram " + event + " RSSI" + rssi.intHistogram);
}
return builder.build();
}
@@ -950,6 +1680,13 @@ public class WifiScoreCard {
}
network.addAccessPoints(perBssid.toAccessPoint(obfuscate));
}
+ for (PerNetwork perNetwork: mApForNetwork.values()) {
+ String key = perNetwork.ssid;
+ Network.Builder network = networks.get(key);
+ if (network != null) {
+ network.setNetworkStats(perNetwork.toNetworkStats());
+ }
+ }
NetworkList.Builder builder = NetworkList.newBuilder();
for (Network.Builder network: networks.values()) {
builder.addNetworks(network);
@@ -983,7 +1720,7 @@ public class WifiScoreCard {
*/
public void clear() {
mApForBssid.clear();
+ mApForNetwork.clear();
resetConnectionStateInternal(false);
}
-
}
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 6b5b43128..b9dc498ab 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -284,6 +284,8 @@ public class WifiServiceImpl extends BaseWifiService {
private final DppManager mDppManager;
private final WifiApConfigStore mWifiApConfigStore;
private final WifiThreadRunner mWifiThreadRunner;
+ private final MemoryStoreImpl mMemoryStoreImpl;
+ private final WifiScoreCard mWifiScoreCard;
public WifiServiceImpl(Context context, WifiInjector wifiInjector, AsyncChannel asyncChannel) {
mContext = context;
@@ -322,6 +324,9 @@ public class WifiServiceImpl extends BaseWifiService {
mWifiThreadRunner = mWifiInjector.getWifiThreadRunner();
mWifiConfigManager = mWifiInjector.getWifiConfigManager();
mPasspointManager = mWifiInjector.getPasspointManager();
+ mWifiScoreCard = mWifiInjector.getWifiScoreCard();
+ mMemoryStoreImpl = new MemoryStoreImpl(mContext, mWifiInjector,
+ mWifiScoreCard, mWifiInjector.getWifiHealthMonitor());
}
/**
@@ -390,14 +395,14 @@ public class WifiServiceImpl extends BaseWifiService {
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
intentFilter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_SHUTDOWN);
boolean trackEmergencyCallState = mContext.getResources().getBoolean(
R.bool.config_wifi_turn_off_during_emergency_call);
if (trackEmergencyCallState) {
intentFilter.addAction(TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED);
}
mContext.registerReceiver(mReceiver, intentFilter);
-
- new MemoryStoreImpl(mContext, mWifiInjector, mWifiInjector.getWifiScoreCard()).start();
+ mMemoryStoreImpl.start();
if (!mWifiConfigManager.loadFromStore()) {
Log.e(TAG, "Failed to load from config store");
}
@@ -529,6 +534,14 @@ public class WifiServiceImpl extends BaseWifiService {
}
}
+ private void handleShutDown() {
+ // There is no explicit disconnection event in clientModeImpl during shutdown.
+ // Call resetConnectionState() so that connection duration is calculated correctly
+ // before memory store write triggered by mMemoryStoreImpl.stop().
+ mWifiScoreCard.resetConnectionState();
+ mMemoryStoreImpl.stop();
+ }
+
private boolean checkNetworkSettingsPermission(int pid, int uid) {
return mContext.checkPermission(android.Manifest.permission.NETWORK_SETTINGS, pid, uid)
== PERMISSION_GRANTED;
@@ -2814,6 +2827,8 @@ public class WifiServiceImpl extends BaseWifiService {
mActiveModeWarden.emergencyCallStateChanged(inCall);
} else if (action.equals(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)) {
handleIdleModeChanged();
+ } else if (action.equals(Intent.ACTION_SHUTDOWN)) {
+ handleShutDown();
}
}
};
@@ -3140,6 +3155,7 @@ public class WifiServiceImpl extends BaseWifiService {
mClientModeImpl.clearNetworkRequestUserApprovedAccessPoints();
mWifiNetworkSuggestionsManager.clear();
mWifiInjector.getWifiScoreCard().clear();
+ mWifiInjector.getWifiHealthMonitor().clear();
notifyFactoryReset();
});
}
diff --git a/service/proto/src/scorecard.proto b/service/proto/src/scorecard.proto
index 7e42287f4..b924d3d4f 100644
--- a/service/proto/src/scorecard.proto
+++ b/service/proto/src/scorecard.proto
@@ -41,6 +41,7 @@ message Network {
repeated AccessPoint access_points = 3; // The list of related APs
optional int32 network_config_id = 4; // The networkId of WifiConfiguration
optional int32 network_agent_id = 5; // Latest NetworkAgent netId
+ optional NetworkStats network_stats = 6; // Network stats of current SSID
};
// Describes an access point (single BSSID)
@@ -123,4 +124,66 @@ enum Event {
ROAM_FAILURE = 11;
LAST_POLL_BEFORE_SWITCH = 12;
VALIDATION_SUCCESS = 13;
+ DISCONNECTION = 14;
+ CONNECTION_ATTEMPT = 15;
+ VALIDATION_FAILURE = 16;
};
+
+message SystemInfoStats {
+ // Current software build information
+ optional SoftwareBuildInfo curr_software_build_info = 1;
+ // Previous software build information
+ optional SoftwareBuildInfo prev_software_build_info = 2;
+ // Most recent WiFi scan time
+ optional int64 last_scan_time_ms = 3;
+ // Number of access points found in most recent WiFi scan at 2G
+ optional int32 num_bssid_last_scan_2g = 4;
+ // Number of access points found in most recent WiFi scan above 2G
+ optional int32 num_bssid_last_scan_above_2g = 5;
+}
+
+message SoftwareBuildInfo {
+ // Android OS build version
+ optional string os_build_version = 1;
+ // WiFi stack APK version, 0 means not available.
+ optional int32 wifi_stack_version = 2;
+ // WiFi driver version
+ optional string wifi_driver_version = 3;
+ // WiFi firmware version
+ optional string wifi_firmware_version = 4;
+}
+
+message NetworkStats {
+ optional int32 id = 1; // Concise id
+ // The most recent connection stats with current SW build that may be collected over days
+ optional ConnectionStats recent_stats = 2;
+ // Accumulated connection stats with current SW build
+ optional ConnectionStats stats_curr_build = 3;
+ // Accumulated connection stats with previous SW build
+ optional ConnectionStats stats_prev_build = 4;
+
+};
+
+message ConnectionStats {
+ // Number of connection attempts at high RSSI
+ optional int32 num_connection_attempt = 1;
+ // Number of connection failures at high RSSI
+ // Does not include wrong password but does include DHCP
+ optional int32 num_connection_failure = 2;
+ // Total connection duration in seconds
+ optional int32 connection_duration_sec = 3;
+ // Number of association rejections at high RSSI
+ optional int32 num_association_rejection = 4;
+ // Number of association timeouts at high RSSI
+ optional int32 num_association_timeout = 5;
+ // Number of authentication failures (excluding wrong password) at high RSSI
+ optional int32 num_authentication_failure = 6;
+ // Number of short connections caused by nonlocal disconnection at high RSSI
+ // or at high Tx speed with a recent RSSI poll
+ optional int32 num_short_connection_nonlocal = 7;
+ // Number of non-locally generated disconnections at high RSSI or Tx speed
+ // with a recent RSSI poll
+ optional int32 num_disconnection_nonlocal = 8;
+ // Number of disconnections with a recent RSSI poll
+ optional int32 num_disconnection = 9;
+}