Adding network disabled status to user action metrics

(cherry-picked from acd5659d05dacf1de79489c7740c87e95cbf5b03)
When a user action is triggered, take note of the reasons
WifiConfigurations and BSSIDs are disabled. This information will be
used to gain insight why we did not auto-connect to manaually
selected networks.

Updated-PDD: true
Bug: 165118962
Test: com.android.server.wifi
Test: Manual verification with dumpsys log

Change-Id: I7d3a0a933e4f9f45b20019ace3013e1661212fad
Merged-In: I7d3a0a933e4f9f45b20019ace3013e1661212fad
diff --git a/service/java/com/android/server/wifi/BssidBlocklistMonitor.java b/service/java/com/android/server/wifi/BssidBlocklistMonitor.java
index bc41aa9..d96fdb3 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.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 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 @@
     }
 
     /**
+     * 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 a5771f7..1ac361e 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.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.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;
@@ -114,6 +116,7 @@
 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;
@@ -808,6 +811,35 @@
         }
     }
 
+    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
@@ -830,6 +862,8 @@
                     networkInfo.isEphemeral = config.isEphemeral();
                     networkInfo.isPasspoint = config.isPasspoint();
                     mUserActionEvent.targetNetworkInfo = networkInfo;
+                    mUserActionEvent.networkDisableReason = convertToNetworkDisableReason(
+                            config, mBssidBlocklistMonitor.getFailureReasonsForSsid(config.SSID));
                 }
             }
         }
@@ -890,7 +924,24 @@
             }
             WifiStatus wifiStatus = mUserActionEvent.wifiStatus;
             if (wifiStatus != null) {
-                sb.append("\nWifiStatus=" + wifiStatus.toString());
+                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();
         }
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 20aea05..5019f54 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -802,8 +802,13 @@
             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,7 +4123,6 @@
             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)) {
             if (config == null) {
                 mWifiMetrics.logUserActionEvent(UserActionEvent.EVENT_MANUAL_CONNECT, netId);
@@ -4127,6 +4131,7 @@
                         UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK, config.networkId);
             }
         }
+        mClientModeImpl.connect(config, netId, binder, callback, callbackIdentifier, uid);
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/util/MetricsUtils.java b/service/java/com/android/server/wifi/util/MetricsUtils.java
index 2c9c7f7..4c87aa2 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 @@
 
         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 a9c759e..a7f706f 100644
--- a/service/proto/src/metrics.proto
+++ b/service/proto/src/metrics.proto
@@ -3172,6 +3172,11 @@
 
   // 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 {
@@ -3203,6 +3208,56 @@
   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 e3c8d52..d6333b3 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 @@
         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 0a5a512..b4cd759 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.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.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.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 @@
         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,13 @@
         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);
     }
 
     /**
@@ -2452,6 +2462,76 @@
     }
 
     /**
+     * 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);
+    }
+
+    /**
      * Test the logging of UserActionEvent with invalid network ID
      */
     @Test
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
index 49ccf9b..22561a1 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 @@
         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));
     }