summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/java/com/android/server/wifi/BssidBlocklistMonitor.java17
-rw-r--r--service/java/com/android/server/wifi/WifiMetrics.java136
-rw-r--r--service/java/com/android/server/wifi/WifiServiceImpl.java22
-rw-r--r--service/java/com/android/server/wifi/util/MetricsUtils.java65
-rw-r--r--service/proto/src/metrics.proto80
-rw-r--r--tests/wifitests/src/com/android/server/wifi/BssidBlocklistMonitorTest.java20
-rw-r--r--tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java121
-rw-r--r--tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java9
8 files changed, 456 insertions, 14 deletions
diff --git a/service/java/com/android/server/wifi/BssidBlocklistMonitor.java b/service/java/com/android/server/wifi/BssidBlocklistMonitor.java
index bc41aa929..d96fdb39e 100644
--- a/service/java/com/android/server/wifi/BssidBlocklistMonitor.java
+++ b/service/java/com/android/server/wifi/BssidBlocklistMonitor.java
@@ -37,6 +37,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -60,7 +61,7 @@ public class BssidBlocklistMonitor {
public static final int REASON_EAP_FAILURE = 3;
// Other association rejection failures
public static final int REASON_ASSOCIATION_REJECTION = 4;
- // Associated timeout failures, when the RSSI is good
+ // Association timeout failures.
public static final int REASON_ASSOCIATION_TIMEOUT = 5;
// Other authentication failures
public static final int REASON_AUTHENTICATION_FAILURE = 6;
@@ -528,6 +529,20 @@ public class BssidBlocklistMonitor {
}
/**
+ * Gets the list of block reasons for BSSIDs currently in the blocklist.
+ * @return The set of unique reasons for blocking BSSIDs with this SSID.
+ */
+ public Set<Integer> getFailureReasonsForSsid(@NonNull String ssid) {
+ if (ssid == null) {
+ return Collections.emptySet();
+ }
+ return mBssidStatusMap.values().stream()
+ .filter(entry -> entry.isInBlocklist && ssid.equals(entry.ssid))
+ .map(entry -> entry.blockReason)
+ .collect(Collectors.toSet());
+ }
+
+ /**
* Attempts to re-enable BSSIDs that likely experienced failures due to low RSSI.
* @param scanDetails
*/
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index 6c2b5e369..1ac361ef3 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -29,6 +29,7 @@ import android.net.wifi.SoftApCapability;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
@@ -74,6 +75,7 @@ import com.android.server.wifi.proto.nano.WifiMetricsProto.LinkProbeStats.Experi
import com.android.server.wifi.proto.nano.WifiMetricsProto.LinkProbeStats.LinkProbeFailureReasonCount;
import com.android.server.wifi.proto.nano.WifiMetricsProto.LinkSpeedCount;
import com.android.server.wifi.proto.nano.WifiMetricsProto.MeteredNetworkStats;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.NetworkDisableReason;
import com.android.server.wifi.proto.nano.WifiMetricsProto.NetworkSelectionExperimentDecisions;
import com.android.server.wifi.proto.nano.WifiMetricsProto.PasspointProfileTypeCount;
import com.android.server.wifi.proto.nano.WifiMetricsProto.PasspointProvisionStats;
@@ -92,6 +94,7 @@ import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiLockStats;
import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiNetworkRequestApiLog;
import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiNetworkSuggestionApiLog;
import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiNetworkSuggestionApiLog.SuggestionAppCount;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiStatus;
import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiToggleStats;
import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiUsabilityStats;
import com.android.server.wifi.proto.nano.WifiMetricsProto.WifiUsabilityStatsEntry;
@@ -113,6 +116,7 @@ import org.json.JSONObject;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.Calendar;
import java.util.HashMap;
@@ -232,6 +236,7 @@ public class WifiMetrics {
@VisibleForTesting static final int MAX_USER_ACTION_EVENTS = 200;
private LinkedList<StaEventWithTime> mStaEventList = new LinkedList<>();
private LinkedList<UserActionEventWithTime> mUserActionEventList = new LinkedList<>();
+ private WifiStatusBuilder mWifiStatusBuilder = new WifiStatusBuilder();
private int mLastPollRssi = -127;
private int mLastPollLinkSpeed = -1;
private int mLastPollRxLinkSpeed = -1;
@@ -753,6 +758,88 @@ public class WifiMetrics {
}
}
+ class WifiStatusBuilder {
+ private int mNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
+ private boolean mConnected;
+ private boolean mValidated;
+ private int mRssi;
+ private int mEstimatedTxKbps;
+ private int mEstimatedRxKbps;
+ private boolean mIsStuckDueToUserChoice;
+
+ public void setNetworkId(int networkId) {
+ mNetworkId = networkId;
+ }
+
+ public int getNetworkId() {
+ return mNetworkId;
+ }
+
+ public void setConnected(boolean connected) {
+ mConnected = connected;
+ }
+
+ public void setValidated(boolean validated) {
+ mValidated = validated;
+ }
+
+ public void setRssi(int rssi) {
+ mRssi = rssi;
+ }
+
+ public void setEstimatedTxKbps(int estimatedTxKbps) {
+ mEstimatedTxKbps = estimatedTxKbps;
+ }
+
+ public void setEstimatedRxKbps(int estimatedRxKbps) {
+ mEstimatedRxKbps = estimatedRxKbps;
+ }
+
+ public void setUserChoice(boolean userChoice) {
+ mIsStuckDueToUserChoice = userChoice;
+ }
+
+ public WifiStatus toProto() {
+ WifiStatus result = new WifiStatus();
+ result.isConnected = mConnected;
+ result.isValidated = mValidated;
+ result.lastRssi = mRssi;
+ result.estimatedTxKbps = mEstimatedTxKbps;
+ result.estimatedRxKbps = mEstimatedRxKbps;
+ result.isStuckDueToUserConnectChoice = mIsStuckDueToUserChoice;
+ return result;
+ }
+ }
+
+ private NetworkDisableReason convertToNetworkDisableReason(
+ WifiConfiguration config, Set<Integer> bssidBlocklistReasons) {
+ NetworkSelectionStatus status = config.getNetworkSelectionStatus();
+ NetworkDisableReason result = new NetworkDisableReason();
+ if (config.allowAutojoin) {
+ if (!status.isNetworkEnabled()) {
+ result.disableReason =
+ MetricsUtils.convertNetworkSelectionDisableReasonToWifiProtoEnum(
+ status.getNetworkSelectionDisableReason());
+ if (status.isNetworkPermanentlyDisabled()) {
+ result.configPermanentlyDisabled = true;
+ } else {
+ result.configTemporarilyDisabled = true;
+ }
+ }
+ } else {
+ result.disableReason = NetworkDisableReason.REASON_AUTO_JOIN_DISABLED;
+ result.configPermanentlyDisabled = true;
+ }
+
+ int[] convertedBssidBlockReasons = bssidBlocklistReasons.stream()
+ .mapToInt(i -> MetricsUtils.convertBssidBlocklistReasonToWifiProtoEnum(i))
+ .toArray();
+ if (convertedBssidBlockReasons.length > 0) {
+ result.bssidDisableReasons = convertedBssidBlockReasons;
+ }
+ return result;
+ }
+
class UserActionEventWithTime {
private UserActionEvent mUserActionEvent;
private long mWallClockTimeMs = 0; // wall clock time for debugging only
@@ -763,13 +850,11 @@ public class WifiMetrics {
mUserActionEvent.startTimeMillis = mClock.getElapsedSinceBootMillis();
mWallClockTimeMs = mClock.getWallClockMillis();
mUserActionEvent.targetNetworkInfo = targetNetworkInfo;
+ mUserActionEvent.wifiStatus = mWifiStatusBuilder.toProto();
}
UserActionEventWithTime(int eventType, int targetNetId) {
- mUserActionEvent = new UserActionEvent();
- mUserActionEvent.eventType = eventType;
- mUserActionEvent.startTimeMillis = mClock.getElapsedSinceBootMillis();
- mWallClockTimeMs = mClock.getWallClockMillis();
+ this(eventType, null);
if (targetNetId >= 0) {
WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(targetNetId);
if (config != null) {
@@ -777,6 +862,8 @@ public class WifiMetrics {
networkInfo.isEphemeral = config.isEphemeral();
networkInfo.isPasspoint = config.isPasspoint();
mUserActionEvent.targetNetworkInfo = networkInfo;
+ mUserActionEvent.networkDisableReason = convertToNetworkDisableReason(
+ config, mBssidBlocklistMonitor.getFailureReasonsForSsid(config.SSID));
}
}
}
@@ -824,6 +911,9 @@ public class WifiMetrics {
case UserActionEvent.EVENT_MANUAL_CONNECT:
eventType = "EVENT_MANUAL_CONNECT";
break;
+ case UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK:
+ eventType = "EVENT_ADD_OR_UPDATE_NETWORK";
+ break;
}
sb.append(" eventType=").append(eventType);
sb.append(" startTimeMillis=").append(mUserActionEvent.startTimeMillis);
@@ -832,6 +922,27 @@ public class WifiMetrics {
sb.append(" isEphemeral=").append(networkInfo.isEphemeral);
sb.append(" isPasspoint=").append(networkInfo.isPasspoint);
}
+ WifiStatus wifiStatus = mUserActionEvent.wifiStatus;
+ if (wifiStatus != null) {
+ sb.append("\nWifiStatus: isConnected=").append(wifiStatus.isConnected);
+ sb.append(" isValidated=").append(wifiStatus.isValidated);
+ sb.append(" lastRssi=").append(wifiStatus.lastRssi);
+ sb.append(" estimatedTxKbps=").append(wifiStatus.estimatedTxKbps);
+ sb.append(" estimatedRxKbps=").append(wifiStatus.estimatedRxKbps);
+ sb.append(" isStuckDueToUserConnectChoice=")
+ .append(wifiStatus.isStuckDueToUserConnectChoice);
+ }
+ NetworkDisableReason disableReason = mUserActionEvent.networkDisableReason;
+ if (disableReason != null) {
+ sb.append("\nNetworkDisableReason: DisableReason=")
+ .append(disableReason.disableReason);
+ sb.append(" configTemporarilyDisabled=")
+ .append(disableReason.configTemporarilyDisabled);
+ sb.append(" configPermanentlyDisabled=")
+ .append(disableReason.configPermanentlyDisabled);
+ sb.append(" bssidDisableReasons=")
+ .append(Arrays.toString(disableReason.bssidDisableReasons));
+ }
return sb.toString();
}
@@ -1607,6 +1718,7 @@ public class WifiMetrics {
if (!result) {
mScanResultRssiTimestampMillis = -1;
}
+ mWifiStatusBuilder.setConnected(result);
}
}
}
@@ -2084,6 +2196,8 @@ public class WifiMetrics {
mLastPollRxLinkSpeed = wifiInfo.getRxLinkSpeedMbps();
incrementTxLinkSpeedBandCount(mLastPollLinkSpeed, mLastPollFreq);
incrementRxLinkSpeedBandCount(mLastPollRxLinkSpeed, mLastPollFreq);
+ mWifiStatusBuilder.setRssi(mLastPollRssi);
+ mWifiStatusBuilder.setNetworkId(wifiInfo.getNetworkId());
}
/**
@@ -2259,6 +2373,8 @@ public class WifiMetrics {
mRxThroughputMbpsHistogramAbove2G.increment(rxThroughputKbps / 1000);
}
}
+ mWifiStatusBuilder.setEstimatedTxKbps(txThroughputKbps);
+ mWifiStatusBuilder.setEstimatedRxKbps(rxThroughputKbps);
}
}
@@ -4646,6 +4762,10 @@ public class WifiMetrics {
mWifiState = wifiState;
mWifiWins = (wifiState == WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
mWifiWinsUsabilityScore = (wifiState == WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
+ if (wifiState == WifiMetricsProto.WifiLog.WIFI_DISCONNECTED
+ || wifiState == WifiMetricsProto.WifiLog.WIFI_DISABLED) {
+ mWifiStatusBuilder = new WifiStatusBuilder();
+ }
}
}
@@ -4750,6 +4870,7 @@ public class WifiMetrics {
case StaEvent.TYPE_CMD_START_ROAM:
case StaEvent.TYPE_CONNECT_NETWORK:
case StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK:
+ mWifiStatusBuilder.setValidated(true);
case StaEvent.TYPE_FRAMEWORK_DISCONNECT:
case StaEvent.TYPE_SCORE_BREACH:
case StaEvent.TYPE_MAC_CHANGE:
@@ -6155,6 +6276,13 @@ public class WifiMetrics {
synchronized (mLock) {
if (networkId == WifiConfiguration.INVALID_NETWORK_ID) return;
mNetworkIdToNominatorId.put(networkId, nominatorId);
+
+ // user connect choice is preventing switcing off from the connected network
+ if (nominatorId
+ == WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED_USER_CONNECT_CHOICE
+ && mWifiStatusBuilder.getNetworkId() == networkId) {
+ mWifiStatusBuilder.setUserChoice(true);
+ }
}
}
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index a398eacaf..5019f54e8 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -802,8 +802,13 @@ public class WifiServiceImpl extends BaseWifiService {
Binder.restoreCallingIdentity(ident);
}
if (mWifiPermissionsUtil.checkNetworkSettingsPermission(Binder.getCallingUid())) {
- mWifiMetrics.logUserActionEvent(enable ? UserActionEvent.EVENT_TOGGLE_WIFI_ON
- : UserActionEvent.EVENT_TOGGLE_WIFI_OFF);
+ if (enable) {
+ mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_ON);
+ } else {
+ WifiInfo wifiInfo = mClientModeImpl.syncRequestConnectionInfo();
+ mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_OFF,
+ wifiInfo == null ? -1 : wifiInfo.getNetworkId());
+ }
}
mWifiMetrics.incrementNumWifiToggles(isPrivileged, enable);
mActiveModeWarden.wifiToggled();
@@ -4118,10 +4123,15 @@ public class WifiServiceImpl extends BaseWifiService {
throw new SecurityException(TAG + ": Permission denied");
}
mLog.info("connect uid=%").c(uid).flush();
- mClientModeImpl.connect(config, netId, binder, callback, callbackIdentifier, uid);
if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
- mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_MANUAL_CONNECT, netId);
+ if (config == null) {
+ mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_MANUAL_CONNECT, netId);
+ } else {
+ mWifiMetrics.logUserActionEvent(
+ UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK, config.networkId);
+ }
}
+ mClientModeImpl.connect(config, netId, binder, callback, callbackIdentifier, uid);
}
/**
@@ -4135,6 +4145,10 @@ public class WifiServiceImpl extends BaseWifiService {
throw new SecurityException(TAG + ": Permission denied");
}
mLog.info("save uid=%").c(Binder.getCallingUid()).flush();
+ if (mWifiPermissionsUtil.checkNetworkSettingsPermission(Binder.getCallingUid())) {
+ mWifiMetrics.logUserActionEvent(
+ UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK, config.networkId);
+ }
mClientModeImpl.save(
config, binder, callback, callbackIdentifier, Binder.getCallingUid());
}
diff --git a/service/java/com/android/server/wifi/util/MetricsUtils.java b/service/java/com/android/server/wifi/util/MetricsUtils.java
index 2c9c7f741..4c87aa237 100644
--- a/service/java/com/android/server/wifi/util/MetricsUtils.java
+++ b/service/java/com/android/server/wifi/util/MetricsUtils.java
@@ -16,8 +16,12 @@
package com.android.server.wifi.util;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.util.SparseIntArray;
+import com.android.server.wifi.BssidBlocklistMonitor;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.NetworkDisableReason;
+
/**
* Utilities for Metrics collections.
*/
@@ -188,4 +192,65 @@ public class MetricsUtils {
return protoArray;
}
+
+ /**
+ * Converts NetworkSelectionStatus.NetworkSelectionDisableReason to
+ * WifiMetricsProto.NetworkDisableReason.DisableReason
+ */
+ public static int convertNetworkSelectionDisableReasonToWifiProtoEnum(int reason) {
+ switch (reason) {
+ case NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
+ return NetworkDisableReason.REASON_ASSOCIATION_REJECTION;
+ case NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
+ return NetworkDisableReason.REASON_AUTHENTICATION_FAILURE;
+ case NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
+ return NetworkDisableReason.REASON_DHCP_FAILURE;
+ case NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY:
+ case NetworkSelectionStatus.DISABLED_NO_INTERNET_PERMANENT:
+ return NetworkDisableReason.REASON_NETWORK_VALIDATION_FAILURE;
+ case NetworkSelectionStatus.DISABLED_AUTHENTICATION_NO_CREDENTIALS:
+ return NetworkDisableReason.REASON_AUTHENTICATION_NO_CREDENTIALS;
+ case NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD:
+ return NetworkDisableReason.REASON_WRONG_PASSWORD;
+ case NetworkSelectionStatus.DISABLED_AUTHENTICATION_NO_SUBSCRIPTION:
+ return NetworkDisableReason.REASON_AUTHENTICATION_NO_SUBSCRIPTION;
+ default:
+ return NetworkDisableReason.REASON_UNKNOWN;
+ }
+ }
+
+ /**
+ * Converts BssidBlocklistMonitor.FailureReason to
+ * WifiMetricsProto.NetworkDisableReason.DisableReason
+ */
+ public static int convertBssidBlocklistReasonToWifiProtoEnum(int reason) {
+ switch (reason) {
+ case BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA:
+ return NetworkDisableReason.REASON_AP_UNABLE_TO_HANDLE_NEW_STA;
+ case BssidBlocklistMonitor.REASON_NETWORK_VALIDATION_FAILURE:
+ return NetworkDisableReason.REASON_NETWORK_VALIDATION_FAILURE;
+ case BssidBlocklistMonitor.REASON_WRONG_PASSWORD:
+ return NetworkDisableReason.REASON_WRONG_PASSWORD;
+ case BssidBlocklistMonitor.REASON_EAP_FAILURE:
+ return NetworkDisableReason.REASON_EAP_FAILURE;
+ case BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION:
+ return NetworkDisableReason.REASON_ASSOCIATION_REJECTION;
+ case BssidBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT:
+ return NetworkDisableReason.REASON_ASSOCIATION_TIMEOUT;
+ case BssidBlocklistMonitor.REASON_AUTHENTICATION_FAILURE:
+ return NetworkDisableReason.REASON_AUTHENTICATION_FAILURE;
+ case BssidBlocklistMonitor.REASON_DHCP_FAILURE:
+ return NetworkDisableReason.REASON_DHCP_FAILURE;
+ case BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT:
+ return NetworkDisableReason.REASON_ABNORMAL_DISCONNECT;
+ case BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_MBO_OCE:
+ return NetworkDisableReason.REASON_FRAMEWORK_DISCONNECT_MBO_OCE;
+ case BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT:
+ return NetworkDisableReason.REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT;
+ case BssidBlocklistMonitor.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE:
+ return NetworkDisableReason.REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE;
+ default:
+ return NetworkDisableReason.REASON_UNKNOWN;
+ }
+ }
}
diff --git a/service/proto/src/metrics.proto b/service/proto/src/metrics.proto
index 30b1690cc..a7f706fb8 100644
--- a/service/proto/src/metrics.proto
+++ b/service/proto/src/metrics.proto
@@ -3156,6 +3156,8 @@ message UserActionEvent {
EVENT_MANUAL_CONNECT = 11;
// User changes the metered setting to "detect automatically"
EVENT_CONFIGURE_METERED_STATUS_AUTO = 12;
+ // User adds a new network or updates configurations for an existing network.
+ EVENT_ADD_OR_UPDATE_NETWORK = 13;
}
// The type of user action
@@ -3167,6 +3169,34 @@ message UserActionEvent {
// Additional information on the target network for the action. This is not applicable and will
// be null for some actions such as EVENT_TOGGLE_WIFI_ON.
optional TargetNetworkInfo target_network_info = 3;
+
+ // Information about the currently connected network.
+ optional WifiStatus wifi_status = 4;
+
+ // Reasons why the network applicable for this user action is disabled.
+ // Normally this should be referring to the currently connected network.
+ // For a manual connection, this is referring to the user selected networks.
+ optional NetworkDisableReason network_disable_reason = 5;
+}
+
+message WifiStatus {
+ // Wifi is connected.
+ optional bool is_connected = 1;
+
+ // Wifi is validated
+ optional bool is_validated = 2;
+
+ // The last observed RSSI
+ optional sint32 last_rssi = 3;
+
+ // Estimated TX
+ optional int32 estimated_tx_kbps = 4;
+
+ // Estimated RX
+ optional int32 estimated_rx_kbps = 5;
+
+ // There is another candidate with better score, but user connect choice is preventing the switch.
+ optional bool is_stuck_due_to_user_connect_choice = 6;
}
// Additional information on a network
@@ -3178,6 +3208,56 @@ message TargetNetworkInfo {
optional bool is_passpoint = 2;
}
+// Reasons why a configuration or BSSID is disabled.
+message NetworkDisableReason {
+ enum DisableReason {
+ // Default value, should not be used.
+ REASON_UNKNOWN = 0;
+ // AP unable to accept more clients, a special kind of association rejection failure.
+ REASON_AP_UNABLE_TO_HANDLE_NEW_STA = 1;
+ // No internet
+ REASON_NETWORK_VALIDATION_FAILURE = 2;
+ // Password is incorrect, a special kind of authentication failure.
+ REASON_WRONG_PASSWORD = 3;
+ // Incorrect EAP credentials.
+ REASON_EAP_FAILURE = 4;
+ // Other association rejection failures.
+ REASON_ASSOCIATION_REJECTION = 5;
+ // Associated timeout failures.
+ REASON_ASSOCIATION_TIMEOUT = 6;
+ // Other authentication failures.
+ REASON_AUTHENTICATION_FAILURE = 7;
+ // DHCP failures.
+ REASON_DHCP_FAILURE = 8;
+ // Device disconnected shortly after connection.
+ REASON_ABNORMAL_DISCONNECT = 9;
+ // AP initiated disconnect for a given duration.
+ REASON_FRAMEWORK_DISCONNECT_MBO_OCE = 10;
+ // Avoid connecting to the failed AP when trying to reconnect on other available candidates.
+ REASON_FRAMEWORK_DISCONNECT_FAST_RECONNECT = 11;
+ // The connected scorer has disconnected this network.
+ REASON_FRAMEWORK_DISCONNECT_CONNECTED_SCORE = 12;
+ // The network is disabled due to absence of user credentials
+ REASON_AUTHENTICATION_NO_CREDENTIALS = 13;
+ // The network is disabled because service is not subscribed, a special kind of EAP failure.
+ REASON_AUTHENTICATION_NO_SUBSCRIPTION = 14;
+ // User disabled auto-join for this network.
+ REASON_AUTO_JOIN_DISABLED = 15;
+ }
+
+ // The reason for disabling a network.
+ optional DisableReason disable_reason = 1;
+
+ // The configuration is temporarily disabled.
+ optional bool config_temporarily_disabled = 2;
+
+ // The configuration is disabled until the user manually re-enables it.
+ optional bool config_permanently_disabled = 3;
+
+ // Reasons why 1 or more BSSIDs are temporarily disabled.
+ repeated DisableReason bssid_disable_reasons = 4;
+}
+
// Number of networks with a large change of connection/disconnection
// failure rate or high failure rate at high RSSI
message HealthMonitorFailureStats {
diff --git a/tests/wifitests/src/com/android/server/wifi/BssidBlocklistMonitorTest.java b/tests/wifitests/src/com/android/server/wifi/BssidBlocklistMonitorTest.java
index e3c8d52fa..d6333b363 100644
--- a/tests/wifitests/src/com/android/server/wifi/BssidBlocklistMonitorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/BssidBlocklistMonitorTest.java
@@ -776,4 +776,24 @@ public class BssidBlocklistMonitorTest {
simulateRssiUpdate(TEST_BSSID_1, TEST_SUFFICIENT_RSSI);
assertTrue(mBssidBlocklistMonitor.updateAndGetBssidBlocklist().contains(TEST_BSSID_1));
}
+
+ /**
+ * Verify the failure reasons for all blocked BSSIDs are retrieved.
+ */
+ @Test
+ public void testGetFailureReasonsForSsid() {
+ // Null input should not crash
+ mBssidBlocklistMonitor.getFailureReasonsForSsid(null).size();
+ assertEquals(0, mBssidBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1).size());
+ mBssidBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_1, TEST_SSID_1, 1000,
+ BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA, TEST_GOOD_RSSI);
+ mBssidBlocklistMonitor.blockBssidForDurationMs(TEST_BSSID_2, TEST_SSID_1, 1000,
+ BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT, TEST_GOOD_RSSI);
+
+ assertEquals(2, mBssidBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1).size());
+ assertTrue(mBssidBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1)
+ .contains(BssidBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA));
+ assertTrue(mBssidBlocklistMonitor.getFailureReasonsForSsid(TEST_SSID_1)
+ .contains(BssidBlocklistMonitor.REASON_ABNORMAL_DISCONNECT));
+ }
}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index d16ef3dcf..b4cd7592c 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -60,6 +60,7 @@ import android.net.wifi.SoftApCapability;
import android.net.wifi.SoftApConfiguration;
import android.net.wifi.SupplicantState;
import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.net.wifi.WifiEnterpriseConfig;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
@@ -98,6 +99,7 @@ import com.android.server.wifi.proto.nano.WifiMetricsProto.Int32Count;
import com.android.server.wifi.proto.nano.WifiMetricsProto.LinkProbeStats;
import com.android.server.wifi.proto.nano.WifiMetricsProto.LinkProbeStats.ExperimentProbeCounts;
import com.android.server.wifi.proto.nano.WifiMetricsProto.LinkProbeStats.LinkProbeFailureReasonCount;
+import com.android.server.wifi.proto.nano.WifiMetricsProto.NetworkDisableReason;
import com.android.server.wifi.proto.nano.WifiMetricsProto.NetworkSelectionExperimentDecisions;
import com.android.server.wifi.proto.nano.WifiMetricsProto.PasspointProfileTypeCount;
import com.android.server.wifi.proto.nano.WifiMetricsProto.PasspointProvisionStats;
@@ -129,9 +131,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -2399,9 +2403,8 @@ public class WifiMetricsTest extends WifiBaseTest {
int testNetworkId = 0;
long testStartTimeMillis = 123123L;
when(mClock.getElapsedSinceBootMillis()).thenReturn(testStartTimeMillis);
- WifiConfiguration config = mock(WifiConfiguration.class);
- when(config.isEphemeral()).thenReturn(true);
- when(config.isPasspoint()).thenReturn(true);
+ WifiConfiguration config = WifiConfigurationTestUtil.createPasspointNetwork();
+ config.ephemeral = true;
when(mWcm.getConfiguredNetwork(testNetworkId)).thenReturn(config);
mWifiMetrics.logUserActionEvent(testEventType, testNetworkId);
@@ -2414,6 +2417,118 @@ public class WifiMetricsTest extends WifiBaseTest {
assertEquals(testStartTimeMillis, userActionEvents[0].startTimeMillis);
assertEquals(true, userActionEvents[0].targetNetworkInfo.isEphemeral);
assertEquals(true, userActionEvents[0].targetNetworkInfo.isPasspoint);
+
+ // Verify that there are no disabled WifiConfiguration and BSSIDs
+ NetworkDisableReason networkDisableReason = userActionEvents[0].networkDisableReason;
+ assertEquals(NetworkDisableReason.REASON_UNKNOWN, networkDisableReason.disableReason);
+ assertEquals(false, networkDisableReason.configTemporarilyDisabled);
+ assertEquals(false, networkDisableReason.configPermanentlyDisabled);
+ assertEquals(0, networkDisableReason.bssidDisableReasons.length);
+ }
+
+ /**
+ * Verify the WifiStatus field in a UserActionEvent is populated correctly.
+ * @throws Exception
+ */
+ @Test
+ public void testLogWifiStatusInUserActionEvent() throws Exception {
+ // setups WifiStatus for information
+ int expectedRssi = -55;
+ int testNetworkId = 1;
+ int expectedTx = 1234;
+ int expectedRx = 2345;
+
+ WifiInfo wifiInfo = mock(WifiInfo.class);
+ when(wifiInfo.getRssi()).thenReturn(expectedRssi);
+ when(wifiInfo.getNetworkId()).thenReturn(testNetworkId);
+ mWifiMetrics.handlePollResult(wifiInfo);
+ mWifiMetrics.incrementThroughputKbpsCount(expectedTx, expectedRx, RSSI_POLL_FREQUENCY);
+ mWifiMetrics.setNominatorForNetwork(testNetworkId,
+ WifiMetricsProto.ConnectionEvent.NOMINATOR_SAVED_USER_CONNECT_CHOICE);
+
+ // generate a user action event and then verify fields
+ int testEventType = WifiMetricsProto.UserActionEvent.EVENT_FORGET_WIFI;
+ mWifiMetrics.logUserActionEvent(testEventType, testNetworkId);
+ dumpProtoAndDeserialize();
+
+ WifiMetricsProto.UserActionEvent[] userActionEvents = mDecodedProto.userActionEvents;
+ assertEquals(1, userActionEvents.length);
+ assertEquals(WifiMetricsProto.UserActionEvent.EVENT_FORGET_WIFI,
+ userActionEvents[0].eventType);
+ assertEquals(expectedRssi, userActionEvents[0].wifiStatus.lastRssi);
+ assertEquals(expectedTx, userActionEvents[0].wifiStatus.estimatedTxKbps);
+ assertEquals(expectedRx, userActionEvents[0].wifiStatus.estimatedRxKbps);
+ assertTrue(userActionEvents[0].wifiStatus.isStuckDueToUserConnectChoice);
+ }
+
+ /**
+ * verify NetworkDisableReason is populated properly when there exists a disabled
+ * WifiConfiguration and BSSID.
+ */
+ @Test
+ public void testNetworkDisableReasonInUserActionEvent() throws Exception {
+ // Setup a temporarily blocked config due to DISABLED_ASSOCIATION_REJECTION
+ WifiConfiguration testConfig = WifiConfigurationTestUtil.createOpenNetwork();
+ NetworkSelectionStatus status = testConfig.getNetworkSelectionStatus();
+ status.setNetworkSelectionStatus(
+ NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+ status.setNetworkSelectionDisableReason(
+ NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION);
+ when(mWcm.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(testConfig);
+
+ // Also setup the same BSSID level failure
+ Set<Integer> testBssidBlocklistReasons = new HashSet<>();
+ testBssidBlocklistReasons.add(BssidBlocklistMonitor.REASON_ASSOCIATION_REJECTION);
+ when(mBssidBlocklistMonitor.getFailureReasonsForSsid(anyString()))
+ .thenReturn(testBssidBlocklistReasons);
+
+ // Logging the user action event
+ mWifiMetrics.logUserActionEvent(WifiMetricsProto.UserActionEvent.EVENT_FORGET_WIFI,
+ TEST_NETWORK_ID);
+ dumpProtoAndDeserialize();
+
+ WifiMetricsProto.UserActionEvent[] userActionEvents = mDecodedProto.userActionEvents;
+ assertEquals(1, userActionEvents.length);
+ assertEquals(WifiMetricsProto.UserActionEvent.EVENT_FORGET_WIFI,
+ userActionEvents[0].eventType);
+ NetworkDisableReason networkDisableReason = userActionEvents[0].networkDisableReason;
+ assertEquals(NetworkDisableReason.REASON_ASSOCIATION_REJECTION,
+ networkDisableReason.disableReason);
+ assertEquals(true, networkDisableReason.configTemporarilyDisabled);
+ assertEquals(false, networkDisableReason.configPermanentlyDisabled);
+ assertEquals(1, networkDisableReason.bssidDisableReasons.length);
+ assertEquals(NetworkDisableReason.REASON_ASSOCIATION_REJECTION,
+ networkDisableReason.bssidDisableReasons[0]);
+ }
+
+ /**
+ * verify that auto-join disable overrides any other disable reasons in NetworkDisableReason.
+ */
+ @Test
+ public void testNetworkDisableReasonDisableAutojoinInUserActionEvent() throws Exception {
+ // Setup a temporarily blocked config due to DISABLED_ASSOCIATION_REJECTION
+ WifiConfiguration testConfig = WifiConfigurationTestUtil.createOpenNetwork();
+ NetworkSelectionStatus status = testConfig.getNetworkSelectionStatus();
+ status.setNetworkSelectionStatus(
+ NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED);
+ status.setNetworkSelectionDisableReason(
+ NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION);
+ when(mWcm.getConfiguredNetwork(TEST_NETWORK_ID)).thenReturn(testConfig);
+
+ // Disable autojoin
+ testConfig.allowAutojoin = false;
+
+ // Logging the user action event
+ mWifiMetrics.logUserActionEvent(WifiMetricsProto.UserActionEvent.EVENT_FORGET_WIFI,
+ TEST_NETWORK_ID);
+ dumpProtoAndDeserialize();
+
+ WifiMetricsProto.UserActionEvent[] userActionEvents = mDecodedProto.userActionEvents;
+ NetworkDisableReason networkDisableReason = userActionEvents[0].networkDisableReason;
+ assertEquals(NetworkDisableReason.REASON_AUTO_JOIN_DISABLED,
+ networkDisableReason.disableReason);
+ assertEquals(false, networkDisableReason.configTemporarilyDisabled);
+ assertEquals(true, networkDisableReason.configPermanentlyDisabled);
}
/**
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
index 6d1ae2927..22561a159 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
@@ -571,7 +571,8 @@ public class WifiServiceImplTest extends WifiBaseTest {
assertTrue(mWifiServiceImpl.setWifiEnabled(TEST_PACKAGE_NAME, false));
inorder.verify(mWifiMetrics).logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_ON);
inorder.verify(mWifiMetrics).incrementNumWifiToggles(eq(true), eq(true));
- inorder.verify(mWifiMetrics).logUserActionEvent(UserActionEvent.EVENT_TOGGLE_WIFI_OFF);
+ inorder.verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_TOGGLE_WIFI_OFF),
+ anyInt());
inorder.verify(mWifiMetrics).incrementNumWifiToggles(eq(true), eq(false));
}
@@ -3621,7 +3622,8 @@ public class WifiServiceImplTest extends WifiBaseTest {
mock(IActionListener.class), 0);
verify(mClientModeImpl).connect(any(WifiConfiguration.class), anyInt(),
any(Binder.class), any(IActionListener.class), anyInt(), anyInt());
- verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_MANUAL_CONNECT), anyInt());
+ verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK),
+ anyInt());
}
/**
@@ -3632,8 +3634,11 @@ public class WifiServiceImplTest extends WifiBaseTest {
public void testSaveNetworkWithPrivilegedPermission() throws Exception {
when(mContext.checkPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
+ when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
mWifiServiceImpl.save(mock(WifiConfiguration.class), mock(Binder.class),
mock(IActionListener.class), 0);
+ verify(mWifiMetrics).logUserActionEvent(eq(UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK),
+ anyInt());
verify(mClientModeImpl).save(any(WifiConfiguration.class),
any(Binder.class), any(IActionListener.class), anyInt(), anyInt());
}