content: /*
   * Copyright (C) 2010 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.WifiManager.WIFI_STATE_DISABLED;
  import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
  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 android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.app.ActivityManager;
  import android.bluetooth.BluetoothAdapter;
  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.pm.ApplicationInfo;
  import android.content.pm.PackageManager;
  import android.database.ContentObserver;
  import android.net.ConnectivityManager;
  import android.net.DhcpResults;
  import android.net.IpConfiguration;
  import android.net.KeepalivePacketData;
  import android.net.LinkProperties;
  import android.net.MacAddress;
  import android.net.MatchAllNetworkSpecifier;
  import android.net.NattKeepalivePacketData;
  import android.net.Network;
  import android.net.NetworkAgent;
  import android.net.NetworkCapabilities;
  import android.net.NetworkInfo;
  import android.net.NetworkInfo.DetailedState;
  import android.net.NetworkMisc;
  import android.net.NetworkUtils;
  import android.net.RouteInfo;
  import android.net.SocketKeepalive;
  import android.net.SocketKeepalive.InvalidPacketException;
  import android.net.StaticIpConfiguration;
  import android.net.TcpKeepalivePacketData;
  import android.net.ip.IIpClient;
  import android.net.ip.IpClientCallbacks;
  import android.net.ip.IpClientManager;
  import android.net.shared.ProvisioningConfiguration;
  import android.net.wifi.INetworkRequestMatchCallback;
  import android.net.wifi.RssiPacketCountInfo;
  import android.net.wifi.ScanResult;
  import android.net.wifi.SupplicantState;
  import android.net.wifi.WifiConfiguration;
  import android.net.wifi.WifiEnterpriseConfig;
  import android.net.wifi.WifiInfo;
  import android.net.wifi.WifiManager;
  import android.net.wifi.WifiManager.DeviceMobilityState;
  import android.net.wifi.WifiNetworkAgentSpecifier;
  import android.net.wifi.WifiSsid;
  import android.net.wifi.hotspot2.IProvisioningCallback;
  import android.net.wifi.hotspot2.OsuProvider;
  import android.net.wifi.hotspot2.PasspointConfiguration;
  import android.net.wifi.p2p.IWifiP2pManager;
  import android.os.BatteryStats;
  import android.os.Bundle;
  import android.os.ConditionVariable;
  import android.os.IBinder;
  import android.os.Looper;
  import android.os.Message;
  import android.os.Messenger;
  import android.os.PowerManager;
  import android.os.Process;
  import android.os.RemoteException;
  import android.os.UserHandle;
  import android.os.UserManager;
  import android.os.WorkSource;
  import android.provider.Settings;
  import android.system.OsConstants;
  import android.telephony.SubscriptionManager;
  import android.telephony.TelephonyManager;
  import android.text.TextUtils;
  import android.util.Log;
  import android.util.Pair;
  import android.util.SparseArray;
  import android.util.StatsLog;
  
  import com.android.internal.R;
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.app.IBatteryStats;
  import com.android.internal.util.AsyncChannel;
  import com.android.internal.util.MessageUtils;
  import com.android.internal.util.Protocol;
  import com.android.internal.util.State;
  import com.android.internal.util.StateMachine;
  import com.android.server.wifi.hotspot2.AnqpEvent;
  import com.android.server.wifi.hotspot2.IconEvent;
  import com.android.server.wifi.hotspot2.NetworkDetail;
  import com.android.server.wifi.hotspot2.PasspointManager;
  import com.android.server.wifi.hotspot2.WnmData;
  import com.android.server.wifi.nano.WifiMetricsProto;
  import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
  import com.android.server.wifi.nano.WifiMetricsProto.WifiIsUnusableEvent;
  import com.android.server.wifi.nano.WifiMetricsProto.WifiUsabilityStats;
  import com.android.server.wifi.p2p.WifiP2pServiceImpl;
  import com.android.server.wifi.util.NativeUtil;
  import com.android.server.wifi.util.TelephonyUtil;
  import com.android.server.wifi.util.TelephonyUtil.SimAuthRequestData;
  import com.android.server.wifi.util.TelephonyUtil.SimAuthResponseData;
  import com.android.server.wifi.util.WifiPermissionsUtil;
  import com.android.server.wifi.util.WifiPermissionsWrapper;
  
  import java.io.BufferedReader;
  import java.io.FileDescriptor;
  import java.io.FileNotFoundException;
  import java.io.FileReader;
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.net.Inet4Address;
  import java.net.Inet6Address;
  import java.net.InetAddress;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.concurrent.atomic.AtomicBoolean;
  import java.util.concurrent.atomic.AtomicInteger;
  
  /**
   * Implementation of ClientMode.  Event handling for Client mode logic is done here,
   * and all changes in connectivity state are initiated here.
   *
   * @hide
   */
  public class ClientModeImpl extends StateMachine {
  
      private static final String NETWORKTYPE = "WIFI";
      @VisibleForTesting public static final short NUM_LOG_RECS_NORMAL = 100;
      @VisibleForTesting public static final short NUM_LOG_RECS_VERBOSE_LOW_MEMORY = 200;
      @VisibleForTesting public static final short NUM_LOG_RECS_VERBOSE = 3000;
      private static final String TAG = "WifiClientModeImpl";
  
      private static final int ONE_HOUR_MILLI = 1000 * 60 * 60;
  
      private static final String GOOGLE_OUI = "DA-A1-19";
  
      private static final String EXTRA_OSU_ICON_QUERY_BSSID = "BSSID";
      private static final String EXTRA_OSU_ICON_QUERY_FILENAME = "FILENAME";
      private static final String EXTRA_OSU_PROVIDER = "OsuProvider";
      private static final String EXTRA_UID = "uid";
      private static final String EXTRA_PACKAGE_NAME = "PackageName";
      private static final String EXTRA_PASSPOINT_CONFIGURATION = "PasspointConfiguration";
      private static final int IPCLIENT_TIMEOUT_MS = 60_000;
  
      private boolean mVerboseLoggingEnabled = false;
      private final WifiPermissionsWrapper mWifiPermissionsWrapper;
  
      /* debug flag, indicating if handling of ASSOCIATION_REJECT ended up blacklisting
       * the corresponding BSSID.
       */
      private boolean mDidBlackListBSSID = false;
  
      /**
       * Log with error attribute
       *
       * @param s is string log
       */
      @Override
      protected void loge(String s) {
          Log.e(getName(), s);
      }
      @Override
      protected void logd(String s) {
          Log.d(getName(), s);
      }
      @Override
      protected void log(String s) {
          Log.d(getName(), s);
      }
      private final WifiMetrics mWifiMetrics;
      private final WifiInjector mWifiInjector;
      private final WifiMonitor mWifiMonitor;
      private final WifiNative mWifiNative;
      private final WifiPermissionsUtil mWifiPermissionsUtil;
      private final WifiConfigManager mWifiConfigManager;
      private final WifiConnectivityManager mWifiConnectivityManager;
      private ConnectivityManager mCm;
      private BaseWifiDiagnostics mWifiDiagnostics;
      private final boolean mP2pSupported;
      private final AtomicBoolean mP2pConnected = new AtomicBoolean(false);
      private boolean mTemporarilyDisconnectWifi = false;
      private final Clock mClock;
      private final PropertyService mPropertyService;
      private final BuildProperties mBuildProperties;
      private final WifiCountryCode mCountryCode;
      private final WifiScoreCard mWifiScoreCard;
      private final WifiScoreReport mWifiScoreReport;
      private final SarManager mSarManager;
      private final WifiTrafficPoller mWifiTrafficPoller;
      public WifiScoreReport getWifiScoreReport() {
          return mWifiScoreReport;
      }
      private final PasspointManager mPasspointManager;
      private final WifiDataStall mWifiDataStall;
      private final LinkProbeManager mLinkProbeManager;
  
      private final McastLockManagerFilterController mMcastLockManagerFilterController;
  
      private boolean mScreenOn = false;
  
      private String mInterfaceName;
  
      private int mLastSignalLevel = -1;
      private String mLastBssid;
      private int mLastNetworkId; // The network Id we successfully joined
  
      private boolean mIpReachabilityDisconnectEnabled = true;
  
      private void processRssiThreshold(byte curRssi, int reason,
              WifiNative.WifiRssiEventHandler rssiHandler) {
          if (curRssi == Byte.MAX_VALUE || curRssi == Byte.MIN_VALUE) {
              Log.wtf(TAG, "processRssiThreshold: Invalid rssi " + curRssi);
              return;
          }
          for (int i = 0; i < mRssiRanges.length; i++) {
              if (curRssi < mRssiRanges[i]) {
                  // Assume sorted values(ascending order) for rssi,
                  // bounded by high(127) and low(-128) at extremeties
                  byte maxRssi = mRssiRanges[i];
                  byte minRssi = mRssiRanges[i - 1];
                  // This value of hw has to be believed as this value is averaged and has breached
                  // the rssi thresholds and raised event to host. This would be eggregious if this
                  // value is invalid
                  mWifiInfo.setRssi(curRssi);
                  updateCapabilities();
                  int ret = startRssiMonitoringOffload(maxRssi, minRssi, rssiHandler);
                  Log.d(TAG, "Re-program RSSI thresholds for " + getWhatToString(reason)
                          + ": [" + minRssi + ", " + maxRssi + "], curRssi=" + curRssi
                          + " ret=" + ret);
                  break;
              }
          }
      }
  
      private boolean mEnableRssiPolling = false;
      // Accessed via Binder thread ({get,set}PollRssiIntervalMsecs), and ClientModeImpl thread.
      private volatile int mPollRssiIntervalMsecs = DEFAULT_POLL_RSSI_INTERVAL_MSECS;
      private int mRssiPollToken = 0;
      /* 3 operational states for STA operation: CONNECT_MODE, SCAN_ONLY_MODE, SCAN_ONLY_WIFI_OFF_MODE
      * In CONNECT_MODE, the STA can scan and connect to an access point
      * In SCAN_ONLY_MODE, the STA can only scan for access points
      * In SCAN_ONLY_WIFI_OFF_MODE, the STA can only scan for access points with wifi toggle being off
      */
      private int mOperationalMode = DISABLED_MODE;
  
      // variable indicating we are expecting a mode switch - do not attempt recovery for failures
      private boolean mModeChange = false;
  
      private ClientModeManager.Listener mClientModeCallback = null;
  
      private boolean mBluetoothConnectionActive = false;
  
      private PowerManager.WakeLock mSuspendWakeLock;
  
      /**
       * Interval in milliseconds between polling for RSSI and linkspeed information.
       * This is also used as the polling interval for WifiTrafficPoller, which updates
       * its data activity on every CMD_RSSI_POLL.
       */
      private static final int DEFAULT_POLL_RSSI_INTERVAL_MSECS = 3000;
  
      /**
       * Interval in milliseconds between receiving a disconnect event
       * while connected to a good AP, and handling the disconnect proper
       */
      private static final int LINK_FLAPPING_DEBOUNCE_MSEC = 4000;
  
      /**
       * Delay between supplicant restarts upon failure to establish connection
       */
      private static final int SUPPLICANT_RESTART_INTERVAL_MSECS = 5000;
  
      /**
       * Number of times we attempt to restart supplicant
       */
      private static final int SUPPLICANT_RESTART_TRIES = 5;
  
      /**
       * Value to set in wpa_supplicant "bssid" field when we don't want to restrict connection to
       * a specific AP.
       */
      public static final String SUPPLICANT_BSSID_ANY = "any";
  
      /**
       * The link properties of the wifi interface.
       * Do not modify this directly; use updateLinkProperties instead.
       */
      private LinkProperties mLinkProperties;
  
      /* Tracks sequence number on a periodic scan message */
      private int mPeriodicScanToken = 0;
  
      // Wakelock held during wifi start/stop and driver load/unload
      private PowerManager.WakeLock mWakeLock;
  
      private Context mContext;
  
      private final Object mDhcpResultsLock = new Object();
      private DhcpResults mDhcpResults;
  
      // NOTE: Do not return to clients - see syncRequestConnectionInfo()
      private final ExtendedWifiInfo mWifiInfo;
      private NetworkInfo mNetworkInfo;
      private SupplicantStateTracker mSupplicantStateTracker;
  
      // Indicates that framework is attempting to roam, set true on CMD_START_ROAM, set false when
      // wifi connects or fails to connect
      private boolean mIsAutoRoaming = false;
  
      // Roaming failure count
      private int mRoamFailCount = 0;
  
      // This is the BSSID we are trying to associate to, it can be set to SUPPLICANT_BSSID_ANY
      // if we havent selected a BSSID for joining.
      private String mTargetRoamBSSID = SUPPLICANT_BSSID_ANY;
      // This one is used to track the current target network ID. This is used for error
      // handling during connection setup since many error message from supplicant does not report
      // SSID Once connected, it will be set to invalid
      private int mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
      private long mLastDriverRoamAttempt = 0;
      private WifiConfiguration mTargetWifiConfiguration = null;
  
      int getPollRssiIntervalMsecs() {
          return mPollRssiIntervalMsecs;
      }
  
      void setPollRssiIntervalMsecs(int newPollIntervalMsecs) {
          mPollRssiIntervalMsecs = newPollIntervalMsecs;
      }
  
      /**
       * Method to clear {@link #mTargetRoamBSSID} and reset the the current connected network's
       * bssid in wpa_supplicant after a roam/connect attempt.
       */
      public boolean clearTargetBssid(String dbg) {
          WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
          if (config == null) {
              return false;
          }
          String bssid = SUPPLICANT_BSSID_ANY;
          if (config.BSSID != null) {
              bssid = config.BSSID;
              if (mVerboseLoggingEnabled) {
                  Log.d(TAG, "force BSSID to " + bssid + "due to config");
              }
          }
          if (mVerboseLoggingEnabled) {
              logd(dbg + " clearTargetBssid " + bssid + " key=" + config.configKey());
          }
          mTargetRoamBSSID = bssid;
          return mWifiNative.setConfiguredNetworkBSSID(mInterfaceName, bssid);
      }
  
      /**
       * Set Config's default BSSID (for association purpose) and {@link #mTargetRoamBSSID}
       * @param config config need set BSSID
       * @param bssid  default BSSID to assocaite with when connect to this network
       * @return false -- does not change the current default BSSID of the configure
       *         true -- change the  current default BSSID of the configur
       */
      private boolean setTargetBssid(WifiConfiguration config, String bssid) {
          if (config == null || bssid == null) {
              return false;
          }
          if (config.BSSID != null) {
              bssid = config.BSSID;
              if (mVerboseLoggingEnabled) {
                  Log.d(TAG, "force BSSID to " + bssid + "due to config");
              }
          }
          if (mVerboseLoggingEnabled) {
              Log.d(TAG, "setTargetBssid set to " + bssid + " key=" + config.configKey());
          }
          mTargetRoamBSSID = bssid;
          config.getNetworkSelectionStatus().setNetworkSelectionBSSID(bssid);
          return true;
      }
  
      private volatile IpClientManager mIpClient;
      private IpClientCallbacksImpl mIpClientCallbacks;
  
      // Channel for sending replies.
      private AsyncChannel mReplyChannel = new AsyncChannel();
  
      // Used to initiate a connection with WifiP2pService
      private AsyncChannel mWifiP2pChannel;
  
      private WifiNetworkFactory mNetworkFactory;
      private UntrustedWifiNetworkFactory mUntrustedNetworkFactory;
      @GuardedBy("mNetworkAgentLock")
      private WifiNetworkAgent mNetworkAgent;
      private final Object mNetworkAgentLock = new Object();
  
      private byte[] mRssiRanges;
  
      // Used to filter out requests we couldn't possibly satisfy.
      private final NetworkCapabilities mNetworkCapabilitiesFilter = new NetworkCapabilities();
  
      // Provide packet filter capabilities to ConnectivityService.
      private final NetworkMisc mNetworkMisc = new NetworkMisc();
  
      /* The base for wifi message types */
      static final int BASE = Protocol.BASE_WIFI;
  
      static final int CMD_BLUETOOTH_ADAPTER_STATE_CHANGE                 = BASE + 31;
  
      /* Supplicant commands */
      /* Add/update a network configuration */
      static final int CMD_ADD_OR_UPDATE_NETWORK                          = BASE + 52;
      /* Delete a network */
      static final int CMD_REMOVE_NETWORK                                 = BASE + 53;
      /* Enable a network. The device will attempt a connection to the given network. */
      static final int CMD_ENABLE_NETWORK                                 = BASE + 54;
      /* Get configured networks */
      static final int CMD_GET_CONFIGURED_NETWORKS                        = BASE + 59;
      /* Get adaptors */
      static final int CMD_GET_SUPPORTED_FEATURES                         = BASE + 61;
      /* Get configured networks with real preSharedKey */
      static final int CMD_GET_PRIVILEGED_CONFIGURED_NETWORKS             = BASE + 62;
      /* Get Link Layer Stats thru HAL */
      static final int CMD_GET_LINK_LAYER_STATS                           = BASE + 63;
      /* Supplicant commands after driver start*/
      /* Set operational mode. CONNECT, SCAN ONLY, SCAN_ONLY with Wi-Fi off mode */
      static final int CMD_SET_OPERATIONAL_MODE                           = BASE + 72;
      /* Disconnect from a network */
      static final int CMD_DISCONNECT                                     = BASE + 73;
      /* Reconnect to a network */
      static final int CMD_RECONNECT                                      = BASE + 74;
      /* Reassociate to a network */
      static final int CMD_REASSOCIATE                                    = BASE + 75;
  
      /* Controls suspend mode optimizations
       *
       * When high perf mode is enabled, suspend mode optimizations are disabled
       *
       * When high perf mode is disabled, suspend mode optimizations are enabled
       *
       * Suspend mode optimizations include:
       * - packet filtering
       * - turn off roaming
       * - DTIM wake up settings
       */
      static final int CMD_SET_HIGH_PERF_MODE                             = BASE + 77;
      /* Enables RSSI poll */
      static final int CMD_ENABLE_RSSI_POLL                               = BASE + 82;
      /* RSSI poll */
      static final int CMD_RSSI_POLL                                      = BASE + 83;
      /** Runs RSSI poll once */
      static final int CMD_ONESHOT_RSSI_POLL                              = BASE + 84;
      /* Enable suspend mode optimizations in the driver */
      static final int CMD_SET_SUSPEND_OPT_ENABLED                        = BASE + 86;
  
      /* Enable TDLS on a specific MAC address */
      static final int CMD_ENABLE_TDLS                                    = BASE + 92;
  
      /**
       * Watchdog for protecting against b/16823537
       * Leave time for 4-way handshake to succeed
       */
      static final int ROAM_GUARD_TIMER_MSEC = 15000;
  
      int mRoamWatchdogCount = 0;
      /* Roam state watchdog */
      static final int CMD_ROAM_WATCHDOG_TIMER                            = BASE + 94;
      /* Screen change intent handling */
      static final int CMD_SCREEN_STATE_CHANGED                           = BASE + 95;
  
      /* Disconnecting state watchdog */
      static final int CMD_DISCONNECTING_WATCHDOG_TIMER                   = BASE + 96;
  
      /* Remove a packages associated configurations */
      static final int CMD_REMOVE_APP_CONFIGURATIONS                      = BASE + 97;
  
      /* Disable an ephemeral network */
      static final int CMD_DISABLE_EPHEMERAL_NETWORK                      = BASE + 98;
  
      /* SIM is removed; reset any cached data for it */
      static final int CMD_RESET_SIM_NETWORKS                             = BASE + 101;
  
      /* OSU APIs */
      static final int CMD_QUERY_OSU_ICON                                 = BASE + 104;
  
      /* try to match a provider with current network */
      static final int CMD_MATCH_PROVIDER_NETWORK                         = BASE + 105;
  
      // Add or update a Passpoint configuration.
      static final int CMD_ADD_OR_UPDATE_PASSPOINT_CONFIG                 = BASE + 106;
  
      // Remove a Passpoint configuration.
      static final int CMD_REMOVE_PASSPOINT_CONFIG                        = BASE + 107;
  
      // Get the list of installed Passpoint configurations.
      static final int CMD_GET_PASSPOINT_CONFIGS                          = BASE + 108;
  
      // Get the list of OSU providers associated with a Passpoint network.
      static final int CMD_GET_MATCHING_OSU_PROVIDERS                     = BASE + 109;
  
      // Get the list of installed Passpoint configurations matched with OSU providers
      static final int CMD_GET_MATCHING_PASSPOINT_CONFIGS_FOR_OSU_PROVIDERS = BASE + 110;
  
      /* Commands from/to the SupplicantStateTracker */
      /* Reset the supplicant state tracker */
      static final int CMD_RESET_SUPPLICANT_STATE                         = BASE + 111;
  
      // Get the list of wifi configurations for installed Passpoint profiles
      static final int CMD_GET_WIFI_CONFIGS_FOR_PASSPOINT_PROFILES = BASE + 112;
  
      int mDisconnectingWatchdogCount = 0;
      static final int DISCONNECTING_GUARD_TIMER_MSEC = 5000;
  
      /**
       * Indicates the end of boot process, should be used to trigger load from config store,
       * initiate connection attempt, etc.
       * */
      static final int CMD_BOOT_COMPLETED                                 = BASE + 134;
      /**
       * Initialize ClientModeImpl. This is currently used to initialize the
       * {@link HalDeviceManager} module.
       */
      static final int CMD_INITIALIZE                                     = BASE + 135;
  
      /* We now have a valid IP configuration. */
      static final int CMD_IP_CONFIGURATION_SUCCESSFUL                    = BASE + 138;
      /* We no longer have a valid IP configuration. */
      static final int CMD_IP_CONFIGURATION_LOST                          = BASE + 139;
      /* Link configuration (IP address, DNS, ...) changes notified via netlink */
      static final int CMD_UPDATE_LINKPROPERTIES                          = BASE + 140;
  
      /* Supplicant is trying to associate to a given BSSID */
      static final int CMD_TARGET_BSSID                                   = BASE + 141;
  
      static final int CMD_START_CONNECT                                  = BASE + 143;
  
      private static final int NETWORK_STATUS_UNWANTED_DISCONNECT         = 0;
      private static final int NETWORK_STATUS_UNWANTED_VALIDATION_FAILED  = 1;
      private static final int NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN   = 2;
  
      static final int CMD_UNWANTED_NETWORK                               = BASE + 144;
  
      static final int CMD_START_ROAM                                     = BASE + 145;
  
      static final int CMD_ASSOCIATED_BSSID                               = BASE + 147;
  
      static final int CMD_NETWORK_STATUS                                 = BASE + 148;
  
      /* A layer 3 neighbor on the Wi-Fi link became unreachable. */
      static final int CMD_IP_REACHABILITY_LOST                           = BASE + 149;
  
      /* Remove a packages associated configrations */
      static final int CMD_REMOVE_USER_CONFIGURATIONS                     = BASE + 152;
  
      static final int CMD_ACCEPT_UNVALIDATED                             = BASE + 153;
  
      /* used to offload sending IP packet */
      static final int CMD_START_IP_PACKET_OFFLOAD                        = BASE + 160;
  
      /* used to stop offload sending IP packet */
      static final int CMD_STOP_IP_PACKET_OFFLOAD                         = BASE + 161;
  
      /* used to start rssi monitoring in hw */
      static final int CMD_START_RSSI_MONITORING_OFFLOAD                  = BASE + 162;
  
      /* used to stop rssi moniroting in hw */
      static final int CMD_STOP_RSSI_MONITORING_OFFLOAD                   = BASE + 163;
  
      /* used to indicated RSSI threshold breach in hw */
      static final int CMD_RSSI_THRESHOLD_BREACHED                        = BASE + 164;
  
      /* Enable/Disable WifiConnectivityManager */
      static final int CMD_ENABLE_WIFI_CONNECTIVITY_MANAGER               = BASE + 166;
  
  
      /* Get FQDN list for Passpoint profiles matched with a given scanResults */
      static final int CMD_GET_ALL_MATCHING_FQDNS_FOR_SCAN_RESULTS = BASE + 168;
  
      /**
       * Used to handle messages bounced between ClientModeImpl and IpClient.
       */
      static final int CMD_IPV4_PROVISIONING_SUCCESS                      = BASE + 200;
      static final int CMD_IPV4_PROVISIONING_FAILURE                      = BASE + 201;
  
      /* Push a new APF program to the HAL */
      static final int CMD_INSTALL_PACKET_FILTER                          = BASE + 202;
  
      /* Enable/disable fallback packet filtering */
      static final int CMD_SET_FALLBACK_PACKET_FILTERING                  = BASE + 203;
  
      /* Enable/disable Neighbor Discovery offload functionality. */
      static final int CMD_CONFIG_ND_OFFLOAD                              = BASE + 204;
  
      /* used to indicate that the foreground user was switched */
      static final int CMD_USER_SWITCH                                    = BASE + 205;
  
      /* used to indicate that the foreground user was switched */
      static final int CMD_USER_UNLOCK                                    = BASE + 206;
  
      /* used to indicate that the foreground user was switched */
      static final int CMD_USER_STOP                                      = BASE + 207;
  
      /* Read the APF program & data buffer */
      static final int CMD_READ_PACKET_FILTER                             = BASE + 208;
  
      /** Used to add packet filter to apf. */
      static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = BASE + 209;
  
      /** Used to remove packet filter from apf. */
      static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = BASE + 210;
  
      /* Indicates that diagnostics should time out a connection start event. */
      static final int CMD_DIAGS_CONNECT_TIMEOUT                          = BASE + 252;
  
      // Start subscription provisioning with a given provider
      private static final int CMD_START_SUBSCRIPTION_PROVISIONING        = BASE + 254;
  
      @VisibleForTesting
      static final int CMD_PRE_DHCP_ACTION                                = BASE + 255;
      private static final int CMD_PRE_DHCP_ACTION_COMPLETE               = BASE + 256;
      private static final int CMD_POST_DHCP_ACTION                       = BASE + 257;
  
      // For message logging.
      private static final Class[] sMessageClasses = {
              AsyncChannel.class, ClientModeImpl.class };
      private static final SparseArray<String> sGetWhatToString =
              MessageUtils.findMessageNames(sMessageClasses);
  
  
      /* Wifi state machine modes of operation */
      /* CONNECT_MODE - connect to any 'known' AP when it becomes available */
      public static final int CONNECT_MODE = 1;
      /* SCAN_ONLY_MODE - don't connect to any APs; scan, but only while apps hold lock */
      public static final int SCAN_ONLY_MODE = 2;
      /* SCAN_ONLY_WITH_WIFI_OFF - scan, but don't connect to any APs */
      public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE = 3;
      /* DISABLED_MODE - Don't connect, don't scan, don't be an AP */
      public static final int DISABLED_MODE = 4;
  
      private static final int SUCCESS = 1;
      private static final int FAILURE = -1;
  
      /* Tracks if suspend optimizations need to be disabled by DHCP,
       * screen or due to high perf mode.
       * When any of them needs to disable it, we keep the suspend optimizations
       * disabled
       */
      private int mSuspendOptNeedsDisabled = 0;
  
      private static final int SUSPEND_DUE_TO_DHCP = 1;
      private static final int SUSPEND_DUE_TO_HIGH_PERF = 1 << 1;
      private static final int SUSPEND_DUE_TO_SCREEN = 1 << 2;
  
      /**
       * Time window in milliseconds for which we send
       * {@link NetworkAgent#explicitlySelected(boolean, boolean)}
       * after connecting to the network which the user last selected.
       */
      @VisibleForTesting
      public static final int LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS = 30 * 1000;
  
      /* Tracks if user has enabled suspend optimizations through settings */
      private AtomicBoolean mUserWantsSuspendOpt = new AtomicBoolean(true);
  
      /* Tracks if user has enabled Connected Mac Randomization through settings */
  
      /**
       * Supplicant scan interval in milliseconds.
       * Comes from {@link Settings.Global#WIFI_SUPPLICANT_SCAN_INTERVAL_MS} or
       * from the default config if the setting is not set
       */
      private long mSupplicantScanIntervalMs;
  
      int mRunningBeaconCount = 0;
  
      /* Default parent state */
      private State mDefaultState = new DefaultState();
      /* Connecting to an access point */
      private State mConnectModeState = new ConnectModeState();
      /* Connected at 802.11 (L2) level */
      private State mL2ConnectedState = new L2ConnectedState();
      /* fetching IP after connection to access point (assoc+auth complete) */
      private State mObtainingIpState = new ObtainingIpState();
      /* Connected with IP addr */
      private State mConnectedState = new ConnectedState();
      /* Roaming */
      private State mRoamingState = new RoamingState();
      /* disconnect issued, waiting for network disconnect confirmation */
      private State mDisconnectingState = new DisconnectingState();
      /* Network is not connected, supplicant assoc+auth is not complete */
      private State mDisconnectedState = new DisconnectedState();
  
      /**
       * One of  {@link WifiManager#WIFI_STATE_DISABLED},
       * {@link WifiManager#WIFI_STATE_DISABLING},
       * {@link WifiManager#WIFI_STATE_ENABLED},
       * {@link WifiManager#WIFI_STATE_ENABLING},
       * {@link WifiManager#WIFI_STATE_UNKNOWN}
       */
      private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);
  
      /**
       * Work source to use to blame usage on the WiFi service
       */
      public static final WorkSource WIFI_WORK_SOURCE = new WorkSource(Process.WIFI_UID);
  
      /**
       * Keep track of whether WIFI is running.
       */
      private boolean mIsRunning = false;
  
      /**
       * Keep track of whether we last told the battery stats we had started.
       */
      private boolean mReportedRunning = false;
  
      /**
       * Most recently set source of starting WIFI.
       */
      private final WorkSource mRunningWifiUids = new WorkSource();
  
      /**
       * The last reported UIDs that were responsible for starting WIFI.
       */
      private final WorkSource mLastRunningWifiUids = new WorkSource();
  
      private TelephonyManager mTelephonyManager;
      private TelephonyManager getTelephonyManager() {
          if (mTelephonyManager == null) {
              mTelephonyManager = mWifiInjector.makeTelephonyManager();
          }
          return mTelephonyManager;
      }
  
      private final IBatteryStats mBatteryStats;
  
      private final String mTcpBufferSizes;
  
      // Used for debug and stats gathering
      private static int sScanAlarmIntentCount = 0;
  
      private FrameworkFacade mFacade;
      private WifiStateTracker mWifiStateTracker;
      private final BackupManagerProxy mBackupManagerProxy;
      private final WrongPasswordNotifier mWrongPasswordNotifier;
      private final ConnectionFailureNotifier mConnectionFailureNotifier;
      private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
      private boolean mConnectedMacRandomzationSupported;
      // Maximum duration to continue to log Wifi usability stats after a data stall is triggered.
      @VisibleForTesting
      public static final long DURATION_TO_WAIT_ADD_STATS_AFTER_DATA_STALL_MS = 30 * 1000;
      private long mDataStallTriggerTimeMs = -1;
      private int mLastStatusDataStall = WifiIsUnusableEvent.TYPE_UNKNOWN;
  
      public ClientModeImpl(Context context, FrameworkFacade facade, Looper looper,
                              UserManager userManager, WifiInjector wifiInjector,
                              BackupManagerProxy backupManagerProxy, WifiCountryCode countryCode,
                              WifiNative wifiNative, WrongPasswordNotifier wrongPasswordNotifier,
                              SarManager sarManager, WifiTrafficPoller wifiTrafficPoller,
                              LinkProbeManager linkProbeManager) {
          super(TAG, looper);
          mWifiInjector = wifiInjector;
          mWifiMetrics = mWifiInjector.getWifiMetrics();
          mClock = wifiInjector.getClock();
          mPropertyService = wifiInjector.getPropertyService();
          mBuildProperties = wifiInjector.getBuildProperties();
          mWifiScoreCard = wifiInjector.getWifiScoreCard();
          mContext = context;
          mFacade = facade;
          mWifiNative = wifiNative;
          mBackupManagerProxy = backupManagerProxy;
          mWrongPasswordNotifier = wrongPasswordNotifier;
          mSarManager = sarManager;
          mWifiTrafficPoller = wifiTrafficPoller;
          mLinkProbeManager = linkProbeManager;
  
          mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
          mBatteryStats = IBatteryStats.Stub.asInterface(mFacade.getService(
                  BatteryStats.SERVICE_NAME));
          mWifiStateTracker = wifiInjector.getWifiStateTracker();
          IBinder b = mFacade.getService(Context.NETWORKMANAGEMENT_SERVICE);
  
          mP2pSupported = mContext.getPackageManager().hasSystemFeature(
                  PackageManager.FEATURE_WIFI_DIRECT);
  
          mWifiPermissionsUtil = mWifiInjector.getWifiPermissionsUtil();
          mWifiConfigManager = mWifiInjector.getWifiConfigManager();
  
          mPasspointManager = mWifiInjector.getPasspointManager();
  
          mWifiMonitor = mWifiInjector.getWifiMonitor();
          mWifiDiagnostics = mWifiInjector.getWifiDiagnostics();
          mWifiPermissionsWrapper = mWifiInjector.getWifiPermissionsWrapper();
          mWifiDataStall = mWifiInjector.getWifiDataStall();
  
          mWifiInfo = new ExtendedWifiInfo();
          mSupplicantStateTracker =
                  mFacade.makeSupplicantStateTracker(context, mWifiConfigManager, getHandler());
          mWifiConnectivityManager = mWifiInjector.makeWifiConnectivityManager(this);
          mConnectionFailureNotifier = mWifiInjector.makeConnectionFailureNotifier(
                  mWifiConnectivityManager);
  
          mLinkProperties = new LinkProperties();
          mMcastLockManagerFilterController = new McastLockManagerFilterController();
  
          mNetworkInfo.setIsAvailable(false);
          mLastBssid = null;
          mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
          mLastSignalLevel = -1;
  
          mCountryCode = countryCode;
  
          mWifiScoreReport = new WifiScoreReport(mWifiInjector.getScoringParams(), mClock);
  
          mNetworkCapabilitiesFilter.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
          mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
          mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
          mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
          mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
          mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
          // TODO - needs to be a bit more dynamic
          mNetworkCapabilitiesFilter.setLinkUpstreamBandwidthKbps(1024 * 1024);
          mNetworkCapabilitiesFilter.setLinkDownstreamBandwidthKbps(1024 * 1024);
          mNetworkCapabilitiesFilter.setNetworkSpecifier(new MatchAllNetworkSpecifier());
          // Make the network factories.
          mNetworkFactory = mWifiInjector.makeWifiNetworkFactory(
                  mNetworkCapabilitiesFilter, mWifiConnectivityManager);
          // We can't filter untrusted network in the capabilities filter because a trusted
          // network would still satisfy a request that accepts untrusted ones.
          // We need a second network factory for untrusted network requests because we need a
          // different score filter for these requests.
          mUntrustedNetworkFactory = mWifiInjector.makeUntrustedWifiNetworkFactory(
                  mNetworkCapabilitiesFilter, mWifiConnectivityManager);
  
          mWifiNetworkSuggestionsManager = mWifiInjector.getWifiNetworkSuggestionsManager();
  
          IntentFilter filter = new IntentFilter();
          filter.addAction(Intent.ACTION_SCREEN_ON);
          filter.addAction(Intent.ACTION_SCREEN_OFF);
          mContext.registerReceiver(
                  new BroadcastReceiver() {
                      @Override
                      public void onReceive(Context context, Intent intent) {
                          String action = intent.getAction();
  
                          if (action.equals(Intent.ACTION_SCREEN_ON)) {
                              sendMessage(CMD_SCREEN_STATE_CHANGED, 1);
                          } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                              sendMessage(CMD_SCREEN_STATE_CHANGED, 0);
                          }
                      }
                  }, filter);
  
          mFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
                          Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED), false,
                  new ContentObserver(getHandler()) {
                      @Override
                      public void onChange(boolean selfChange) {
                          mUserWantsSuspendOpt.set(mFacade.getIntegerSetting(mContext,
                                  Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
                      }
                  });
  
          mUserWantsSuspendOpt.set(mFacade.getIntegerSetting(mContext,
                  Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
  
          PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
          mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getName());
  
          mSuspendWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WifiSuspend");
          mSuspendWakeLock.setReferenceCounted(false);
  
          mConnectedMacRandomzationSupported = mContext.getResources()
                  .getBoolean(R.bool.config_wifi_connected_mac_randomization_supported);
          mWifiInfo.setEnableConnectedMacRandomization(mConnectedMacRandomzationSupported);
          mWifiMetrics.setIsMacRandomizationOn(mConnectedMacRandomzationSupported);
  
          mTcpBufferSizes = mContext.getResources().getString(
                  R.string.config_wifi_tcp_buffers);
  
          // CHECKSTYLE:OFF IndentationCheck
          addState(mDefaultState);
              addState(mConnectModeState, mDefaultState);
                  addState(mL2ConnectedState, mConnectModeState);
                      addState(mObtainingIpState, mL2ConnectedState);
                      addState(mConnectedState, mL2ConnectedState);
                      addState(mRoamingState, mL2ConnectedState);
                  addState(mDisconnectingState, mConnectModeState);
                  addState(mDisconnectedState, mConnectModeState);
          // CHECKSTYLE:ON IndentationCheck
  
          setInitialState(mDefaultState);
  
          setLogRecSize(NUM_LOG_RECS_NORMAL);
          setLogOnlyTransitions(false);
      }
  
      @Override
      public void start() {
          super.start();
  
          PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
  
          // Learn the initial state of whether the screen is on.
          // We update this field when we receive broadcasts from the system.
          handleScreenStateChanged(powerManager.isInteractive());
      }
  
      private void registerForWifiMonitorEvents()  {
          mWifiMonitor.registerHandler(mInterfaceName, CMD_TARGET_BSSID, getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, CMD_ASSOCIATED_BSSID, getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ANQP_DONE_EVENT, getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ASSOCIATION_REJECTION_EVENT,
                  getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                  getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.GAS_QUERY_DONE_EVENT,
                  getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.GAS_QUERY_START_EVENT,
                  getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.HS20_REMEDIATION_EVENT,
                  getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_CONNECTION_EVENT,
                  getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_DISCONNECTION_EVENT,
                  getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.RX_HS20_ANQP_ICON_EVENT,
                  getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT,
                  getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUP_REQUEST_IDENTITY,
                  getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUP_REQUEST_SIM_AUTH,
                  getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ASSOCIATION_REJECTION_EVENT,
                  mWifiMetrics.getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.AUTHENTICATION_FAILURE_EVENT,
                  mWifiMetrics.getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_CONNECTION_EVENT,
                  mWifiMetrics.getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_DISCONNECTION_EVENT,
                  mWifiMetrics.getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT,
                  mWifiMetrics.getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, CMD_ASSOCIATED_BSSID,
                  mWifiMetrics.getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, CMD_TARGET_BSSID,
                  mWifiMetrics.getHandler());
          mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.NETWORK_CONNECTION_EVENT,
                  mWifiInjector.getWifiLastResortWatchdog().getHandler());
      }
  
      private void setMulticastFilter(boolean enabled) {
          if (mIpClient != null) {
              mIpClient.setMulticastFilter(enabled);
          }
      }
  
      /**
       * Class to implement the MulticastLockManager.FilterController callback.
       */
      class McastLockManagerFilterController implements WifiMulticastLockManager.FilterController {
          /**
           * Start filtering Multicast v4 packets
           */
          public void startFilteringMulticastPackets() {
              setMulticastFilter(true);
          }
  
          /**
           * Stop filtering Multicast v4 packets
           */
          public void stopFilteringMulticastPackets() {
              setMulticastFilter(false);
          }
      }
  
      class IpClientCallbacksImpl extends IpClientCallbacks {
          private final ConditionVariable mWaitForCreationCv = new ConditionVariable(false);
          private final ConditionVariable mWaitForStopCv = new ConditionVariable(false);
  
          @Override
          public void onIpClientCreated(IIpClient ipClient) {
              mIpClient = new IpClientManager(ipClient, getName());
              mWaitForCreationCv.open();
          }
  
          @Override
          public void onPreDhcpAction() {
              sendMessage(CMD_PRE_DHCP_ACTION);
          }
  
          @Override
          public void onPostDhcpAction() {
              sendMessage(CMD_POST_DHCP_ACTION);
          }
  
          @Override
          public void onNewDhcpResults(DhcpResults dhcpResults) {
              if (dhcpResults != null) {
                  sendMessage(CMD_IPV4_PROVISIONING_SUCCESS, dhcpResults);
              } else {
                  sendMessage(CMD_IPV4_PROVISIONING_FAILURE);
              }
          }
  
          @Override
          public void onProvisioningSuccess(LinkProperties newLp) {
              mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL);
              sendMessage(CMD_UPDATE_LINKPROPERTIES, newLp);
              sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);
          }
  
          @Override
          public void onProvisioningFailure(LinkProperties newLp) {
              mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_CONFIGURATION_LOST);
              sendMessage(CMD_IP_CONFIGURATION_LOST);
          }
  
          @Override
          public void onLinkPropertiesChange(LinkProperties newLp) {
              sendMessage(CMD_UPDATE_LINKPROPERTIES, newLp);
          }
  
          @Override
          public void onReachabilityLost(String logMsg) {
              mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_REACHABILITY_LOST);
              sendMessage(CMD_IP_REACHABILITY_LOST, logMsg);
          }
  
          @Override
          public void installPacketFilter(byte[] filter) {
              sendMessage(CMD_INSTALL_PACKET_FILTER, filter);
          }
  
          @Override
          public void startReadPacketFilter() {
              sendMessage(CMD_READ_PACKET_FILTER);
          }
  
          @Override
          public void setFallbackMulticastFilter(boolean enabled) {
              sendMessage(CMD_SET_FALLBACK_PACKET_FILTERING, enabled);
          }
  
          @Override
          public void setNeighborDiscoveryOffload(boolean enabled) {
              sendMessage(CMD_CONFIG_ND_OFFLOAD, (enabled ? 1 : 0));
          }
  
          @Override
          public void onQuit() {
              mWaitForStopCv.open();
          }
  
          boolean awaitCreation() {
              return mWaitForCreationCv.block(IPCLIENT_TIMEOUT_MS);
          }
  
          boolean awaitShutdown() {
              return mWaitForStopCv.block(IPCLIENT_TIMEOUT_MS);
          }
      }
  
      private void stopIpClient() {
          /* Restore power save and suspend optimizations */
          handlePostDhcpSetup();
          if (mIpClient != null) {
              mIpClient.stop();
          }
      }
  
      /**
       * Set wpa_supplicant log level using |mVerboseLoggingLevel| flag.
       */
      void setSupplicantLogLevel() {
          mWifiNative.setSupplicantLogLevel(mVerboseLoggingEnabled);
      }
  
      /**
       * Method to update logging level in wifi service related classes.
       *
       * @param verbose int logging level to use
       */
      public void enableVerboseLogging(int verbose) {
          if (verbose > 0) {
              mVerboseLoggingEnabled = true;
              setLogRecSize(ActivityManager.isLowRamDeviceStatic()
                      ? NUM_LOG_RECS_VERBOSE_LOW_MEMORY : NUM_LOG_RECS_VERBOSE);
          } else {
              mVerboseLoggingEnabled = false;
              setLogRecSize(NUM_LOG_RECS_NORMAL);
          }
          configureVerboseHalLogging(mVerboseLoggingEnabled);
          setSupplicantLogLevel();
          mCountryCode.enableVerboseLogging(verbose);
          mWifiScoreReport.enableVerboseLogging(mVerboseLoggingEnabled);
          mWifiDiagnostics.enableVerboseLogging(mVerboseLoggingEnabled);
          mWifiMonitor.enableVerboseLogging(verbose);
          mWifiNative.enableVerboseLogging(verbose);
          mWifiConfigManager.enableVerboseLogging(verbose);
          mSupplicantStateTracker.enableVerboseLogging(verbose);
          mPasspointManager.enableVerboseLogging(verbose);
          mNetworkFactory.enableVerboseLogging(verbose);
          mLinkProbeManager.enableVerboseLogging(mVerboseLoggingEnabled);
      }
  
      private static final String SYSTEM_PROPERTY_LOG_CONTROL_WIFIHAL = "log.tag.WifiHAL";
      private static final String LOGD_LEVEL_DEBUG = "D";
      private static final String LOGD_LEVEL_VERBOSE = "V";
      private void configureVerboseHalLogging(boolean enableVerbose) {
          if (mBuildProperties.isUserBuild()) {  // Verbose HAL logging not supported on user builds.
              return;
          }
          mPropertyService.set(SYSTEM_PROPERTY_LOG_CONTROL_WIFIHAL,
                  enableVerbose ? LOGD_LEVEL_VERBOSE : LOGD_LEVEL_DEBUG);
      }
  
      private boolean setRandomMacOui() {
          String oui = mContext.getResources().getString(R.string.config_wifi_random_mac_oui);
          if (TextUtils.isEmpty(oui)) {
              oui = GOOGLE_OUI;
          }
          String[] ouiParts = oui.split("-");
          byte[] ouiBytes = new byte[3];
          ouiBytes[0] = (byte) (Integer.parseInt(ouiParts[0], 16) & 0xFF);
          ouiBytes[1] = (byte) (Integer.parseInt(ouiParts[1], 16) & 0xFF);
          ouiBytes[2] = (byte) (Integer.parseInt(ouiParts[2], 16) & 0xFF);
  
          logd("Setting OUI to " + oui);
          return mWifiNative.setScanningMacOui(mInterfaceName, ouiBytes);
      }
  
      /**
       * Initiates connection to a network specified by the user/app. This method checks if the
       * requesting app holds the NETWORK_SETTINGS permission.
       *
       * @param netId Id network to initiate connection.
       * @param uid UID of the app requesting the connection.
       * @param forceReconnect Whether to force a connection even if we're connected to the same
       *                       network currently.
       */
      private boolean connectToUserSelectNetwork(int netId, int uid, boolean forceReconnect) {
          logd("connectToUserSelectNetwork netId " + netId + ", uid " + uid
                  + ", forceReconnect = " + forceReconnect);
          WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(netId);
          if (config == null) {
              loge("connectToUserSelectNetwork Invalid network Id=" + netId);
              return false;
          }
          if (!mWifiConfigManager.enableNetwork(netId, true, uid)
                  || !mWifiConfigManager.updateLastConnectUid(netId, uid)) {
              logi("connectToUserSelectNetwork Allowing uid " + uid
                      + " with insufficient permissions to connect=" + netId);
          } else if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
              // Note user connect choice here, so that it will be considered in the next network
              // selection.
              mWifiConnectivityManager.setUserConnectChoice(netId);
          }
          if (!forceReconnect && mWifiInfo.getNetworkId() == netId) {
              // We're already connected to the user specified network, don't trigger a
              // reconnection unless it was forced.
              logi("connectToUserSelectNetwork already connecting/connected=" + netId);
          } else {
              mWifiConnectivityManager.prepareForForcedConnection(netId);
              if (uid == Process.SYSTEM_UID) {
                  mWifiMetrics.setNominatorForNetwork(config.networkId,
                          WifiMetricsProto.ConnectionEvent.NOMINATOR_MANUAL);
              }
              startConnectToNetwork(netId, uid, SUPPLICANT_BSSID_ANY);
          }
          return true;
      }
  
      /**
       * ******************************************************
       * Methods exposed for public use
       * ******************************************************
       */
  
      /**
       * Retrieve a Messenger for the ClientModeImpl Handler
       *
       * @return Messenger
       */
      public Messenger getMessenger() {
          return new Messenger(getHandler());
      }
  
      // Last connect attempt is used to prevent scan requests:
      //  - for a period of 10 seconds after attempting to connect
      private long mLastConnectAttemptTimestamp = 0;
  
      // For debugging, keep track of last message status handling
      // TODO, find an equivalent mechanism as part of parent class
      private static final int MESSAGE_HANDLING_STATUS_PROCESSED = 2;
      private static final int MESSAGE_HANDLING_STATUS_OK = 1;
      private static final int MESSAGE_HANDLING_STATUS_UNKNOWN = 0;
      private static final int MESSAGE_HANDLING_STATUS_REFUSED = -1;
      private static final int MESSAGE_HANDLING_STATUS_FAIL = -2;
      private static final int MESSAGE_HANDLING_STATUS_OBSOLETE = -3;
      private static final int MESSAGE_HANDLING_STATUS_DEFERRED = -4;
      private static final int MESSAGE_HANDLING_STATUS_DISCARD = -5;
      private static final int MESSAGE_HANDLING_STATUS_LOOPED = -6;
      private static final int MESSAGE_HANDLING_STATUS_HANDLING_ERROR = -7;
  
      private int mMessageHandlingStatus = 0;
  
      private int mOnTime = 0;
      private int mTxTime = 0;
      private int mRxTime = 0;
  
      private int mOnTimeScreenStateChange = 0;
      private long mLastOntimeReportTimeStamp = 0;
      private long mLastScreenStateChangeTimeStamp = 0;
      private int mOnTimeLastReport = 0;
      private int mTxTimeLastReport = 0;
      private int mRxTimeLastReport = 0;
  
      private WifiLinkLayerStats mLastLinkLayerStats;
      private long mLastLinkLayerStatsUpdate = 0;
  
      String reportOnTime() {
          long now = mClock.getWallClockMillis();
          StringBuilder sb = new StringBuilder();
          // Report stats since last report
          int on = mOnTime - mOnTimeLastReport;
          mOnTimeLastReport = mOnTime;
          int tx = mTxTime - mTxTimeLastReport;
          mTxTimeLastReport = mTxTime;
          int rx = mRxTime - mRxTimeLastReport;
          mRxTimeLastReport = mRxTime;
          int period = (int) (now - mLastOntimeReportTimeStamp);
          mLastOntimeReportTimeStamp = now;
          sb.append(String.format("[on:%d tx:%d rx:%d period:%d]", on, tx, rx, period));
          // Report stats since Screen State Changed
          on = mOnTime - mOnTimeScreenStateChange;
          period = (int) (now - mLastScreenStateChangeTimeStamp);
          sb.append(String.format(" from screen [on:%d period:%d]", on, period));
          return sb.toString();
      }
  
      WifiLinkLayerStats getWifiLinkLayerStats() {
          if (mInterfaceName == null) {
              loge("getWifiLinkLayerStats called without an interface");
              return null;
          }
          mLastLinkLayerStatsUpdate = mClock.getWallClockMillis();
          WifiLinkLayerStats stats = mWifiNative.getWifiLinkLayerStats(mInterfaceName);
          if (stats != null) {
              mOnTime = stats.on_time;
              mTxTime = stats.tx_time;
              mRxTime = stats.rx_time;
              mRunningBeaconCount = stats.beacon_rx;
              mWifiInfo.updatePacketRates(stats, mLastLinkLayerStatsUpdate);
          } else {
              long mTxPkts = mFacade.getTxPackets(mInterfaceName);
              long mRxPkts = mFacade.getRxPackets(mInterfaceName);
              mWifiInfo.updatePacketRates(mTxPkts, mRxPkts, mLastLinkLayerStatsUpdate);
          }
          return stats;
      }
  
      private byte[] getDstMacForKeepalive(KeepalivePacketData packetData)
              throws InvalidPacketException {
          try {
              InetAddress gateway = RouteInfo.selectBestRoute(
                      mLinkProperties.getRoutes(), packetData.dstAddress).getGateway();
              String dstMacStr = macAddressFromRoute(gateway.getHostAddress());
              return NativeUtil.macAddressToByteArray(dstMacStr);
          } catch (NullPointerException | IllegalArgumentException e) {
              throw new InvalidPacketException(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
          }
      }
  
      private static int getEtherProtoForKeepalive(KeepalivePacketData packetData)
              throws InvalidPacketException {
          if (packetData.dstAddress instanceof Inet4Address) {
              return OsConstants.ETH_P_IP;
          } else if (packetData.dstAddress instanceof Inet6Address) {
              return OsConstants.ETH_P_IPV6;
          } else {
              throw new InvalidPacketException(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
          }
      }
  
      private int startWifiIPPacketOffload(int slot, KeepalivePacketData packetData,
              int intervalSeconds) {
          byte[] packet = null;
          byte[] dstMac = null;
          int proto = 0;
  
          try {
              packet = packetData.getPacket();
              dstMac = getDstMacForKeepalive(packetData);
              proto = getEtherProtoForKeepalive(packetData);
          } catch (InvalidPacketException e) {
              return e.error;
          }
  
          int ret = mWifiNative.startSendingOffloadedPacket(
                  mInterfaceName, slot, dstMac, packet, proto, intervalSeconds * 1000);
          if (ret != 0) {
              loge("startWifiIPPacketOffload(" + slot + ", " + intervalSeconds
                      + "): hardware error " + ret);
              return SocketKeepalive.ERROR_HARDWARE_ERROR;
          } else {
              return SocketKeepalive.SUCCESS;
          }
      }
  
      private int stopWifiIPPacketOffload(int slot) {
          int ret = mWifiNative.stopSendingOffloadedPacket(mInterfaceName, slot);
          if (ret != 0) {
              loge("stopWifiIPPacketOffload(" + slot + "): hardware error " + ret);
              return SocketKeepalive.ERROR_HARDWARE_ERROR;
          } else {
              return SocketKeepalive.SUCCESS;
          }
      }
  
      private int startRssiMonitoringOffload(byte maxRssi, byte minRssi,
              WifiNative.WifiRssiEventHandler rssiHandler) {
          return mWifiNative.startRssiMonitoring(mInterfaceName, maxRssi, minRssi, rssiHandler);
      }
  
      private int stopRssiMonitoringOffload() {
          return mWifiNative.stopRssiMonitoring(mInterfaceName);
      }
  
      /**
       * Temporary method that allows the active ClientModeManager to set the wifi state that is
       * retrieved by API calls. This will be removed when WifiServiceImpl no longer directly calls
       * this class (b/31479117).
       *
       * @param newState new state to set, invalid states are ignored.
       */
      public void setWifiStateForApiCalls(int newState) {
          switch (newState) {
              case WIFI_STATE_DISABLING:
              case WIFI_STATE_DISABLED:
              case WIFI_STATE_ENABLING:
              case WIFI_STATE_ENABLED:
              case WIFI_STATE_UNKNOWN:
                  if (mVerboseLoggingEnabled) {
                      Log.d(TAG, "setting wifi state to: " + newState);
                  }
                  mWifiState.set(newState);
                  return;
              default:
                  Log.d(TAG, "attempted to set an invalid state: " + newState);
                  return;
          }
      }
  
      /**
       * Method used by WifiServiceImpl to get the current state of Wifi (in client mode) for API
       * calls.  This will be removed when WifiService no longer directly calls this class
       * (b/31479117).
       */
      public int syncGetWifiState() {
          return mWifiState.get();
      }
  
      /**
       * Converts the current wifi state to a printable form.
       */
      public String syncGetWifiStateByName() {
          switch (mWifiState.get()) {
              case WIFI_STATE_DISABLING:
                  return "disabling";
              case WIFI_STATE_DISABLED:
                  return "disabled";
              case WIFI_STATE_ENABLING:
                  return "enabling";
              case WIFI_STATE_ENABLED:
                  return "enabled";
              case WIFI_STATE_UNKNOWN:
                  return "unknown state";
              default:
                  return "[invalid state]";
          }
      }
  
      public boolean isConnected() {
          return getCurrentState() == mConnectedState;
      }
  
      public boolean isDisconnected() {
          return getCurrentState() == mDisconnectedState;
      }
  
      /**
       * Method checking if supplicant is in a transient state
       *
       * @return boolean true if in transient state
       */
      public boolean isSupplicantTransientState() {
          SupplicantState supplicantState = mWifiInfo.getSupplicantState();
          if (supplicantState == SupplicantState.ASSOCIATING
                  || supplicantState == SupplicantState.AUTHENTICATING
                  || supplicantState == SupplicantState.FOUR_WAY_HANDSHAKE
                  || supplicantState == SupplicantState.GROUP_HANDSHAKE) {
  
              if (mVerboseLoggingEnabled) {
                  Log.d(TAG, "Supplicant is under transient state: " + supplicantState);
              }
              return true;
          } else {
              if (mVerboseLoggingEnabled) {
                  Log.d(TAG, "Supplicant is under steady state: " + supplicantState);
              }
          }
  
          return false;
      }
  
      /**
       * Get status information for the current connection, if any.
       *
       * @return a {@link WifiInfo} object containing information about the current connection
       */
      public WifiInfo syncRequestConnectionInfo() {
          WifiInfo result = new WifiInfo(mWifiInfo);
          return result;
      }
  
      /**
       * Method to retrieve the current WifiInfo
       *
       * @returns WifiInfo
       */
      public WifiInfo getWifiInfo() {
          return mWifiInfo;
      }
  
      /**
       * Blocking call to get the current DHCP results
       *
       * @return DhcpResults current results
       */
      public DhcpResults syncGetDhcpResults() {
          synchronized (mDhcpResultsLock) {
              return new DhcpResults(mDhcpResults);
          }
      }
  
      /**
       * When the underlying interface is destroyed, we must immediately tell connectivity service to
       * mark network agent as disconnected and stop the ip client.
       */
      public void handleIfaceDestroyed() {
          handleNetworkDisconnect();
      }
  
      /**
       * TODO: doc
       */
      public void setOperationalMode(int mode, String ifaceName) {
          if (mVerboseLoggingEnabled) {
              log("setting operational mode to " + String.valueOf(mode) + " for iface: " + ifaceName);
          }
          mModeChange = true;
          if (mode != CONNECT_MODE) {
              // we are disabling client mode...   need to exit connect mode now
              transitionTo(mDefaultState);
          } else {
              // do a quick sanity check on the iface name, make sure it isn't null
              if (ifaceName != null) {
                  mInterfaceName = ifaceName;
                  transitionTo(mDisconnectedState);
              } else {
                  Log.e(TAG, "supposed to enter connect mode, but iface is null -> DefaultState");
                  transitionTo(mDefaultState);
              }
          }
          // use the CMD_SET_OPERATIONAL_MODE to force the transitions before other messages are
          // handled.
          sendMessageAtFrontOfQueue(CMD_SET_OPERATIONAL_MODE);
      }
  
      /**
       * Initiates a system-level bugreport, in a non-blocking fashion.
       */
      public void takeBugReport(String bugTitle, String bugDetail) {
          mWifiDiagnostics.takeBugReport(bugTitle, bugDetail);
      }
  
      /**
       * Allow tests to confirm the operational mode for ClientModeImpl for testing.
       */
      @VisibleForTesting
      protected int getOperationalModeForTest() {
          return mOperationalMode;
      }
  
      /**
       * Retrieve the WifiMulticastLockManager.FilterController callback for registration.
       */
      protected WifiMulticastLockManager.FilterController getMcastLockManagerFilterController() {
          return mMcastLockManagerFilterController;
      }
  
      /**
       * Blocking method to retrieve the passpoint icon.
       *
       * @param channel AsyncChannel for the response
       * @param bssid representation of the bssid as a long
       * @param fileName name of the file
       *
       * @return boolean returning the result of the call
       */
      public boolean syncQueryPasspointIcon(AsyncChannel channel, long bssid, String fileName) {
          Bundle bundle = new Bundle();
          bundle.putLong(EXTRA_OSU_ICON_QUERY_BSSID, bssid);
          bundle.putString(EXTRA_OSU_ICON_QUERY_FILENAME, fileName);
          Message resultMsg = channel.sendMessageSynchronously(CMD_QUERY_OSU_ICON, bundle);
          int result = resultMsg.arg1;
          resultMsg.recycle();
          return result == 1;
      }
  
      /**
       * Blocking method to match the provider with the current network
       *
       * @param channel AsyncChannel to use for the response
       * @param fqdn
       * @return int returns message result
       */
      public int matchProviderWithCurrentNetwork(AsyncChannel channel, String fqdn) {
          Message resultMsg = channel.sendMessageSynchronously(CMD_MATCH_PROVIDER_NETWORK, fqdn);
          int result = resultMsg.arg1;
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Deauthenticate and set the re-authentication hold off time for the current network
       * @param holdoff hold off time in milliseconds
       * @param ess set if the hold off pertains to an ESS rather than a BSS
       */
      public void deauthenticateNetwork(AsyncChannel channel, long holdoff, boolean ess) {
          // TODO: This needs an implementation
      }
  
      /**
       * Method to disable an ephemeral config for an ssid
       *
       * @param ssid network name to disable
       */
      public void disableEphemeralNetwork(String ssid) {
          if (ssid != null) {
              sendMessage(CMD_DISABLE_EPHEMERAL_NETWORK, ssid);
          }
      }
  
      /**
       * Disconnect from Access Point
       */
      public void disconnectCommand() {
          sendMessage(CMD_DISCONNECT);
      }
  
      /**
       * Method to trigger a disconnect.
       *
       * @param uid UID of requesting caller
       * @param reason disconnect reason
       */
      public void disconnectCommand(int uid, int reason) {
          sendMessage(CMD_DISCONNECT, uid, reason);
      }
  
      /**
       * Initiate a reconnection to AP
       */
      public void reconnectCommand(WorkSource workSource) {
          sendMessage(CMD_RECONNECT, workSource);
      }
  
      /**
       * Initiate a re-association to AP
       */
      public void reassociateCommand() {
          sendMessage(CMD_REASSOCIATE);
      }
  
      /**
       * Checks for a null Message.
       *
       * This can happen with sendMessageSynchronously, for example if an
       * InterruptedException occurs. If this just happens once, silently
       * ignore it, because it is probably a side effect of shutting down.
       * If it happens a second time, generate a WTF.
       */
      private boolean messageIsNull(Message resultMsg) {
          if (resultMsg != null) return false;
          if (mNullMessageCounter.getAndIncrement() > 0) {
              Log.wtf(TAG, "Persistent null Message", new RuntimeException());
          }
          return true;
      }
      private AtomicInteger mNullMessageCounter = new AtomicInteger(0);
  
      /**
       * Add a network synchronously
       *
       * @return network id of the new network
       */
      public int syncAddOrUpdateNetwork(AsyncChannel channel, WifiConfiguration config) {
          Message resultMsg = channel.sendMessageSynchronously(CMD_ADD_OR_UPDATE_NETWORK, config);
          if (messageIsNull(resultMsg)) return WifiConfiguration.INVALID_NETWORK_ID;
          int result = resultMsg.arg1;
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Get configured networks synchronously
       *
       * @param channel
       * @return
       */
      public List<WifiConfiguration> syncGetConfiguredNetworks(int uuid, AsyncChannel channel,
              int targetUid) {
          Message resultMsg = channel.sendMessageSynchronously(CMD_GET_CONFIGURED_NETWORKS, uuid,
                  targetUid);
          if (messageIsNull(resultMsg)) return null;
          List<WifiConfiguration> result = (List<WifiConfiguration>) resultMsg.obj;
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Blocking call to get the current WifiConfiguration by a privileged caller so private data,
       * like the password, is not redacted.
       *
       * @param channel AsyncChannel to use for the response
       * @return List list of configured networks configs
       */
      public List<WifiConfiguration> syncGetPrivilegedConfiguredNetwork(AsyncChannel channel) {
          Message resultMsg = channel.sendMessageSynchronously(
                  CMD_GET_PRIVILEGED_CONFIGURED_NETWORKS);
          if (messageIsNull(resultMsg)) return null;
          List<WifiConfiguration> result = (List<WifiConfiguration>) resultMsg.obj;
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Returns the list of FQDN (Fully Qualified Domain Name) to installed Passpoint configurations.
       *
       * Return the map of all matching configurations with corresponding scanResults (or an empty map
       * if none).
       *
       * @param scanResults The list of scan results
       * @return Map that consists of FQDN (Fully Qualified Domain Name) and corresponding
       * scanResults per network type({@link WifiManager#PASSPOINT_HOME_NETWORK} and {@link
       * WifiManager#PASSPOINT_ROAMING_NETWORK}).
       */
      @NonNull
      Map<String, Map<Integer, List<ScanResult>>> syncGetAllMatchingFqdnsForScanResults(
              List<ScanResult> scanResults,
              AsyncChannel channel) {
          Message resultMsg = channel.sendMessageSynchronously(
                  CMD_GET_ALL_MATCHING_FQDNS_FOR_SCAN_RESULTS,
                  scanResults);
          if (messageIsNull(resultMsg)) return new HashMap<>();
          Map<String, Map<Integer, List<ScanResult>>> configs =
                  (Map<String, Map<Integer, List<ScanResult>>>) resultMsg.obj;
          resultMsg.recycle();
          return configs;
      }
  
      /**
       * Retrieve a list of {@link OsuProvider} associated with the given list of ScanResult
       * synchronously.
       *
       * @param scanResults a list of ScanResult that has Passpoint APs.
       * @param channel     Channel for communicating with the state machine
       * @return Map that consists of {@link OsuProvider} and a matching list of {@link ScanResult}.
       */
      @NonNull
      public Map<OsuProvider, List<ScanResult>> syncGetMatchingOsuProviders(
              List<ScanResult> scanResults,
              AsyncChannel channel) {
          Message resultMsg =
                  channel.sendMessageSynchronously(CMD_GET_MATCHING_OSU_PROVIDERS, scanResults);
          if (messageIsNull(resultMsg)) return new HashMap<>();
          Map<OsuProvider, List<ScanResult>> providers =
                  (Map<OsuProvider, List<ScanResult>>) resultMsg.obj;
          resultMsg.recycle();
          return providers;
      }
  
      /**
       * Returns the matching Passpoint configurations for given OSU(Online Sign-Up) Providers
       *
       * @param osuProviders a list of {@link OsuProvider}
       * @param channel  AsyncChannel to use for the response
       * @return Map that consists of {@link OsuProvider} and matching {@link PasspointConfiguration}.
       */
      @NonNull
      public Map<OsuProvider, PasspointConfiguration> syncGetMatchingPasspointConfigsForOsuProviders(
              List<OsuProvider> osuProviders, AsyncChannel channel) {
          Message resultMsg =
                  channel.sendMessageSynchronously(
                          CMD_GET_MATCHING_PASSPOINT_CONFIGS_FOR_OSU_PROVIDERS, osuProviders);
          if (messageIsNull(resultMsg)) return new HashMap<>();
          Map<OsuProvider, PasspointConfiguration> result =
                  (Map<OsuProvider, PasspointConfiguration>) resultMsg.obj;
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Returns the corresponding wifi configurations for given FQDN (Fully Qualified Domain Name)
       * list.
       *
       * An empty list will be returned when no match is found.
       *
       * @param fqdnList a list of FQDN
       * @param channel  AsyncChannel to use for the response
       * @return List of {@link WifiConfiguration} converted from
       * {@link com.android.server.wifi.hotspot2.PasspointProvider}
       */
      @NonNull
      public List<WifiConfiguration> syncGetWifiConfigsForPasspointProfiles(List<String> fqdnList,
              AsyncChannel channel) {
          Message resultMsg =
                  channel.sendMessageSynchronously(
                          CMD_GET_WIFI_CONFIGS_FOR_PASSPOINT_PROFILES, fqdnList);
          if (messageIsNull(resultMsg)) return new ArrayList<>();
          List<WifiConfiguration> result = (List<WifiConfiguration>) resultMsg.obj;
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Add or update a Passpoint configuration synchronously.
       *
       * @param channel Channel for communicating with the state machine
       * @param config The configuration to add or update
       * @param packageName Package name of the app adding/updating {@code config}.
       * @return true on success
       */
      public boolean syncAddOrUpdatePasspointConfig(AsyncChannel channel,
              PasspointConfiguration config, int uid, String packageName) {
          Bundle bundle = new Bundle();
          bundle.putInt(EXTRA_UID, uid);
          bundle.putString(EXTRA_PACKAGE_NAME, packageName);
          bundle.putParcelable(EXTRA_PASSPOINT_CONFIGURATION, config);
          Message resultMsg = channel.sendMessageSynchronously(CMD_ADD_OR_UPDATE_PASSPOINT_CONFIG,
                  bundle);
          if (messageIsNull(resultMsg)) return false;
          boolean result = (resultMsg.arg1 == SUCCESS);
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Remove a Passpoint configuration synchronously.
       *
       * @param channel Channel for communicating with the state machine
       * @param privileged Whether the caller is a privileged entity
       * @param fqdn The FQDN of the Passpoint configuration to remove
       * @return true on success
       */
      public boolean syncRemovePasspointConfig(AsyncChannel channel, boolean privileged,
              String fqdn) {
          Message resultMsg = channel.sendMessageSynchronously(CMD_REMOVE_PASSPOINT_CONFIG,
                  privileged ? 1 : 0, 0, fqdn);
          if (messageIsNull(resultMsg)) return false;
          boolean result = (resultMsg.arg1 == SUCCESS);
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Get the list of installed Passpoint configurations synchronously.
       *
       * @param channel Channel for communicating with the state machine
       * @param privileged Whether the caller is a privileged entity
       * @return List of {@link PasspointConfiguration}
       */
      public List<PasspointConfiguration> syncGetPasspointConfigs(AsyncChannel channel,
              boolean privileged) {
          Message resultMsg = channel.sendMessageSynchronously(CMD_GET_PASSPOINT_CONFIGS,
                  privileged ? 1 : 0);
          if (messageIsNull(resultMsg)) return null;
          List<PasspointConfiguration> result = (List<PasspointConfiguration>) resultMsg.obj;
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Start subscription provisioning synchronously
       *
       * @param provider {@link OsuProvider} the provider to provision with
       * @param callback {@link IProvisioningCallback} callback for provisioning status
       * @return boolean true indicates provisioning was started, false otherwise
       */
      public boolean syncStartSubscriptionProvisioning(int callingUid, OsuProvider provider,
              IProvisioningCallback callback, AsyncChannel channel) {
          Message msg = Message.obtain();
          msg.what = CMD_START_SUBSCRIPTION_PROVISIONING;
          msg.arg1 = callingUid;
          msg.obj = callback;
          msg.getData().putParcelable(EXTRA_OSU_PROVIDER, provider);
          Message resultMsg = channel.sendMessageSynchronously(msg);
          if (messageIsNull(resultMsg)) return false;
          boolean result = resultMsg.arg1 != 0;
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Get the supported feature set synchronously
       */
      public long syncGetSupportedFeatures(AsyncChannel channel) {
          Message resultMsg = channel.sendMessageSynchronously(CMD_GET_SUPPORTED_FEATURES);
          if (messageIsNull(resultMsg)) return 0;
          long supportedFeatureSet = ((Long) resultMsg.obj).longValue();
          resultMsg.recycle();
  
          // Mask the feature set against system properties.
          boolean rttSupported = mContext.getPackageManager().hasSystemFeature(
                  PackageManager.FEATURE_WIFI_RTT);
          if (!rttSupported) {
              supportedFeatureSet &=
                      ~(WifiManager.WIFI_FEATURE_D2D_RTT | WifiManager.WIFI_FEATURE_D2AP_RTT);
          }
  
          return supportedFeatureSet;
      }
  
      /**
       * Get link layers stats for adapter synchronously
       */
      public WifiLinkLayerStats syncGetLinkLayerStats(AsyncChannel channel) {
          Message resultMsg = channel.sendMessageSynchronously(CMD_GET_LINK_LAYER_STATS);
          if (messageIsNull(resultMsg)) return null;
          WifiLinkLayerStats result = (WifiLinkLayerStats) resultMsg.obj;
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Delete a network
       *
       * @param networkId id of the network to be removed
       */
      public boolean syncRemoveNetwork(AsyncChannel channel, int networkId) {
          Message resultMsg = channel.sendMessageSynchronously(CMD_REMOVE_NETWORK, networkId);
          if (messageIsNull(resultMsg)) return false;
          boolean result = (resultMsg.arg1 != FAILURE);
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Enable a network
       *
       * @param netId         network id of the network
       * @param disableOthers true, if all other networks have to be disabled
       * @return {@code true} if the operation succeeds, {@code false} otherwise
       */
      public boolean syncEnableNetwork(AsyncChannel channel, int netId, boolean disableOthers) {
          Message resultMsg = channel.sendMessageSynchronously(CMD_ENABLE_NETWORK, netId,
                  disableOthers ? 1 : 0);
          if (messageIsNull(resultMsg)) return false;
          boolean result = (resultMsg.arg1 != FAILURE);
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Disable a network
       *
       * @param netId network id of the network
       * @return {@code true} if the operation succeeds, {@code false} otherwise
       */
      public boolean syncDisableNetwork(AsyncChannel channel, int netId) {
          Message resultMsg = channel.sendMessageSynchronously(WifiManager.DISABLE_NETWORK, netId);
          boolean result = (resultMsg.what != WifiManager.DISABLE_NETWORK_FAILED);
          if (messageIsNull(resultMsg)) return false;
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Method to enable/disable RSSI polling
       * @param enabled boolean idicating if polling should start
       */
      public void enableRssiPolling(boolean enabled) {
          sendMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0);
      }
  
      /**
       * Set high performance mode of operation.
       * Enabling would set active power mode and disable suspend optimizations;
       * disabling would set auto power mode and enable suspend optimizations
       *
       * @param enable true if enable, false otherwise
       */
      public void setHighPerfModeEnabled(boolean enable) {
          sendMessage(CMD_SET_HIGH_PERF_MODE, enable ? 1 : 0, 0);
      }
  
  
      /**
       * reset cached SIM credential data
       */
      public synchronized void resetSimAuthNetworks(boolean simPresent) {
          sendMessage(CMD_RESET_SIM_NETWORKS, simPresent ? 1 : 0);
      }
  
      /**
       * Get Network object of current wifi network
       * @return Network object of current wifi network
       */
      public Network getCurrentNetwork() {
          synchronized (mNetworkAgentLock) {
              if (mNetworkAgent != null) {
                  return new Network(mNetworkAgent.netId);
              } else {
                  return null;
              }
          }
      }
  
      /**
       * Enable TDLS for a specific MAC address
       */
      public void enableTdls(String remoteMacAddress, boolean enable) {
          int enabler = enable ? 1 : 0;
          sendMessage(CMD_ENABLE_TDLS, enabler, 0, remoteMacAddress);
      }
  
      /**
       * Send a message indicating bluetooth adapter connection state changed
       */
      public void sendBluetoothAdapterStateChange(int state) {
          sendMessage(CMD_BLUETOOTH_ADAPTER_STATE_CHANGE, state, 0);
      }
  
      /**
       * Send a message indicating a package has been uninstalled.
       */
      public void removeAppConfigs(String packageName, int uid) {
          // Build partial AppInfo manually - package may not exist in database any more
          ApplicationInfo ai = new ApplicationInfo();
          ai.packageName = packageName;
          ai.uid = uid;
          sendMessage(CMD_REMOVE_APP_CONFIGURATIONS, ai);
      }
  
      /**
       * Send a message indicating a user has been removed.
       */
      public void removeUserConfigs(int userId) {
          sendMessage(CMD_REMOVE_USER_CONFIGURATIONS, userId);
      }
  
      /**
       * Update the BatteryStats WorkSource.
       */
      public void updateBatteryWorkSource(WorkSource newSource) {
          synchronized (mRunningWifiUids) {
              try {
                  if (newSource != null) {
                      mRunningWifiUids.set(newSource);
                  }
                  if (mIsRunning) {
                      if (mReportedRunning) {
                          // If the work source has changed since last time, need
                          // to remove old work from battery stats.
                          if (!mLastRunningWifiUids.equals(mRunningWifiUids)) {
                              mBatteryStats.noteWifiRunningChanged(mLastRunningWifiUids,
                                      mRunningWifiUids);
                              mLastRunningWifiUids.set(mRunningWifiUids);
                          }
                      } else {
                          // Now being started, report it.
                          mBatteryStats.noteWifiRunning(mRunningWifiUids);
                          mLastRunningWifiUids.set(mRunningWifiUids);
                          mReportedRunning = true;
                      }
                  } else {
                      if (mReportedRunning) {
                          // Last reported we were running, time to stop.
                          mBatteryStats.noteWifiStopped(mLastRunningWifiUids);
                          mLastRunningWifiUids.clear();
                          mReportedRunning = false;
                      }
                  }
                  mWakeLock.setWorkSource(newSource);
              } catch (RemoteException ignore) {
              }
          }
      }
  
      /**
       * Trigger dump on the class IpClient object.
       */
      public void dumpIpClient(FileDescriptor fd, PrintWriter pw, String[] args) {
          if (mIpClient != null) {
              // All dumpIpClient does is print this log message.
              // TODO: consider deleting this, since it's not useful.
              pw.println("IpClient logs have moved to dumpsys network_stack");
          }
      }
  
      @Override
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          super.dump(fd, pw, args);
          mSupplicantStateTracker.dump(fd, pw, args);
          pw.println("mLinkProperties " + mLinkProperties);
          pw.println("mWifiInfo " + mWifiInfo);
          pw.println("mDhcpResults " + mDhcpResults);
          pw.println("mNetworkInfo " + mNetworkInfo);
          pw.println("mLastSignalLevel " + mLastSignalLevel);
          pw.println("mLastBssid " + mLastBssid);
          pw.println("mLastNetworkId " + mLastNetworkId);
          pw.println("mOperationalMode " + mOperationalMode);
          pw.println("mUserWantsSuspendOpt " + mUserWantsSuspendOpt);
          pw.println("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
          mCountryCode.dump(fd, pw, args);
          mNetworkFactory.dump(fd, pw, args);
          mUntrustedNetworkFactory.dump(fd, pw, args);
          pw.println("Wlan Wake Reasons:" + mWifiNative.getWlanWakeReasonCount());
          pw.println();
  
          mWifiConfigManager.dump(fd, pw, args);
          pw.println();
          mPasspointManager.dump(pw);
          pw.println();
          mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_USER_ACTION);
          mWifiDiagnostics.dump(fd, pw, args);
          dumpIpClient(fd, pw, args);
          mWifiConnectivityManager.dump(fd, pw, args);
          mWifiInjector.getWakeupController().dump(fd, pw, args);
          mLinkProbeManager.dump(fd, pw, args);
          mWifiInjector.getWifiLastResortWatchdog().dump(fd, pw, args);
      }
  
      /**
       * Trigger message to handle boot completed event.
       */
      public void handleBootCompleted() {
          sendMessage(CMD_BOOT_COMPLETED);
      }
  
      /**
       * Trigger message to handle user switch event.
       */
      public void handleUserSwitch(int userId) {
          sendMessage(CMD_USER_SWITCH, userId);
      }
  
      /**
       * Trigger message to handle user unlock event.
       */
      public void handleUserUnlock(int userId) {
          sendMessage(CMD_USER_UNLOCK, userId);
      }
  
      /**
       * Trigger message to handle user stop event.
       */
      public void handleUserStop(int userId) {
          sendMessage(CMD_USER_STOP, userId);
      }
  
      /**
       * ******************************************************
       * Internal private functions
       * ******************************************************
       */
  
      private void logStateAndMessage(Message message, State state) {
          mMessageHandlingStatus = 0;
          if (mVerboseLoggingEnabled) {
              logd(" " + state.getClass().getSimpleName() + " " + getLogRecString(message));
          }
      }
  
      @Override
      protected boolean recordLogRec(Message msg) {
          switch (msg.what) {
              case CMD_RSSI_POLL:
                  return mVerboseLoggingEnabled;
              default:
                  return true;
          }
      }
  
      /**
       * Return the additional string to be logged by LogRec, default
       *
       * @param msg that was processed
       * @return information to be logged as a String
       */
      @Override
      protected String getLogRecString(Message msg) {
          WifiConfiguration config;
          Long now;
          String report;
          String key;
          StringBuilder sb = new StringBuilder();
          sb.append("screen=").append(mScreenOn ? "on" : "off");
          if (mMessageHandlingStatus != MESSAGE_HANDLING_STATUS_UNKNOWN) {
              sb.append("(").append(mMessageHandlingStatus).append(")");
          }
          if (msg.sendingUid > 0 && msg.sendingUid != Process.WIFI_UID) {
              sb.append(" uid=" + msg.sendingUid);
          }
          switch (msg.what) {
              case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  StateChangeResult stateChangeResult = (StateChangeResult) msg.obj;
                  if (stateChangeResult != null) {
                      sb.append(stateChangeResult.toString());
                  }
                  break;
              case WifiManager.SAVE_NETWORK:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  config = (WifiConfiguration) msg.obj;
                  if (config != null) {
                      sb.append(" ").append(config.configKey());
                      sb.append(" nid=").append(config.networkId);
                      if (config.hiddenSSID) {
                          sb.append(" hidden");
                      }
                      if (config.preSharedKey != null
                              && !config.preSharedKey.equals("*")) {
                          sb.append(" hasPSK");
                      }
                      if (config.ephemeral) {
                          sb.append(" ephemeral");
                      }
                      if (config.selfAdded) {
                          sb.append(" selfAdded");
                      }
                      sb.append(" cuid=").append(config.creatorUid);
                      sb.append(" suid=").append(config.lastUpdateUid);
                  }
                  break;
              case WifiManager.FORGET_NETWORK:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  config = (WifiConfiguration) msg.obj;
                  if (config != null) {
                      sb.append(" ").append(config.configKey());
                      sb.append(" nid=").append(config.networkId);
                      if (config.hiddenSSID) {
                          sb.append(" hidden");
                      }
                      if (config.preSharedKey != null) {
                          sb.append(" hasPSK");
                      }
                      if (config.ephemeral) {
                          sb.append(" ephemeral");
                      }
                      if (config.selfAdded) {
                          sb.append(" selfAdded");
                      }
                      sb.append(" cuid=").append(config.creatorUid);
                      sb.append(" suid=").append(config.lastUpdateUid);
                      WifiConfiguration.NetworkSelectionStatus netWorkSelectionStatus =
                              config.getNetworkSelectionStatus();
                      sb.append(" ajst=").append(
                              netWorkSelectionStatus.getNetworkStatusString());
                  }
                  break;
              case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
                  sb.append(" ");
                  sb.append(" timedOut=" + Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  String bssid = (String) msg.obj;
                  if (bssid != null && bssid.length() > 0) {
                      sb.append(" ");
                      sb.append(bssid);
                  }
                  sb.append(" blacklist=" + Boolean.toString(mDidBlackListBSSID));
                  break;
              case WifiMonitor.NETWORK_CONNECTION_EVENT:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  sb.append(" ").append(mLastBssid);
                  sb.append(" nid=").append(mLastNetworkId);
                  config = getCurrentWifiConfiguration();
                  if (config != null) {
                      sb.append(" ").append(config.configKey());
                  }
                  key = mWifiConfigManager.getLastSelectedNetworkConfigKey();
                  if (key != null) {
                      sb.append(" last=").append(key);
                  }
                  break;
              case CMD_TARGET_BSSID:
              case CMD_ASSOCIATED_BSSID:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  if (msg.obj != null) {
                      sb.append(" BSSID=").append((String) msg.obj);
                  }
                  if (mTargetRoamBSSID != null) {
                      sb.append(" Target=").append(mTargetRoamBSSID);
                  }
                  sb.append(" roam=").append(Boolean.toString(mIsAutoRoaming));
                  break;
              case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                  if (msg.obj != null) {
                      sb.append(" ").append((String) msg.obj);
                  }
                  sb.append(" nid=").append(msg.arg1);
                  sb.append(" reason=").append(msg.arg2);
                  if (mLastBssid != null) {
                      sb.append(" lastbssid=").append(mLastBssid);
                  }
                  if (mWifiInfo.getFrequency() != -1) {
                      sb.append(" freq=").append(mWifiInfo.getFrequency());
                      sb.append(" rssi=").append(mWifiInfo.getRssi());
                  }
                  break;
              case CMD_RSSI_POLL:
              case CMD_ONESHOT_RSSI_POLL:
              case CMD_UNWANTED_NETWORK:
              case WifiManager.RSSI_PKTCNT_FETCH:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  if (mWifiInfo.getSSID() != null) {
                      if (mWifiInfo.getSSID() != null) {
                          sb.append(" ").append(mWifiInfo.getSSID());
                      }
                  }
                  if (mWifiInfo.getBSSID() != null) {
                      sb.append(" ").append(mWifiInfo.getBSSID());
                  }
                  sb.append(" rssi=").append(mWifiInfo.getRssi());
                  sb.append(" f=").append(mWifiInfo.getFrequency());
                  sb.append(" sc=").append(mWifiInfo.score);
                  sb.append(" link=").append(mWifiInfo.getLinkSpeed());
                  sb.append(String.format(" tx=%.1f,", mWifiInfo.txSuccessRate));
                  sb.append(String.format(" %.1f,", mWifiInfo.txRetriesRate));
                  sb.append(String.format(" %.1f ", mWifiInfo.txBadRate));
                  sb.append(String.format(" rx=%.1f", mWifiInfo.rxSuccessRate));
                  sb.append(String.format(" bcn=%d", mRunningBeaconCount));
                  report = reportOnTime();
                  if (report != null) {
                      sb.append(" ").append(report);
                  }
                  sb.append(String.format(" score=%d", mWifiInfo.score));
                  break;
              case CMD_START_CONNECT:
              case WifiManager.CONNECT_NETWORK:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  config = mWifiConfigManager.getConfiguredNetwork(msg.arg1);
                  if (config != null) {
                      sb.append(" ").append(config.configKey());
                  }
                  if (mTargetRoamBSSID != null) {
                      sb.append(" ").append(mTargetRoamBSSID);
                  }
                  sb.append(" roam=").append(Boolean.toString(mIsAutoRoaming));
                  config = getCurrentWifiConfiguration();
                  if (config != null) {
                      sb.append(config.configKey());
                  }
                  break;
              case CMD_START_ROAM:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  ScanResult result = (ScanResult) msg.obj;
                  if (result != null) {
                      now = mClock.getWallClockMillis();
                      sb.append(" bssid=").append(result.BSSID);
                      sb.append(" rssi=").append(result.level);
                      sb.append(" freq=").append(result.frequency);
                      if (result.seen > 0 && result.seen < now) {
                          sb.append(" seen=").append(now - result.seen);
                      } else {
                          // Somehow the timestamp for this scan result is inconsistent
                          sb.append(" !seen=").append(result.seen);
                      }
                  }
                  if (mTargetRoamBSSID != null) {
                      sb.append(" ").append(mTargetRoamBSSID);
                  }
                  sb.append(" roam=").append(Boolean.toString(mIsAutoRoaming));
                  sb.append(" fail count=").append(Integer.toString(mRoamFailCount));
                  break;
              case CMD_ADD_OR_UPDATE_NETWORK:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  if (msg.obj != null) {
                      config = (WifiConfiguration) msg.obj;
                      sb.append(" ").append(config.configKey());
                      sb.append(" prio=").append(config.priority);
                      sb.append(" status=").append(config.status);
                      if (config.BSSID != null) {
                          sb.append(" ").append(config.BSSID);
                      }
                      WifiConfiguration curConfig = getCurrentWifiConfiguration();
                      if (curConfig != null) {
                          if (curConfig.configKey().equals(config.configKey())) {
                              sb.append(" is current");
                          } else {
                              sb.append(" current=").append(curConfig.configKey());
                              sb.append(" prio=").append(curConfig.priority);
                              sb.append(" status=").append(curConfig.status);
                          }
                      }
                  }
                  break;
              case WifiManager.DISABLE_NETWORK:
              case CMD_ENABLE_NETWORK:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  key = mWifiConfigManager.getLastSelectedNetworkConfigKey();
                  if (key != null) {
                      sb.append(" last=").append(key);
                  }
                  config = mWifiConfigManager.getConfiguredNetwork(msg.arg1);
                  if (config != null && (key == null || !config.configKey().equals(key))) {
                      sb.append(" target=").append(key);
                  }
                  break;
              case CMD_GET_CONFIGURED_NETWORKS:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  sb.append(" num=").append(mWifiConfigManager.getConfiguredNetworks().size());
                  break;
              case CMD_PRE_DHCP_ACTION:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  sb.append(" txpkts=").append(mWifiInfo.txSuccess);
                  sb.append(",").append(mWifiInfo.txBad);
                  sb.append(",").append(mWifiInfo.txRetries);
                  break;
              case CMD_POST_DHCP_ACTION:
                  if (mLinkProperties != null) {
                      sb.append(" ");
                      sb.append(getLinkPropertiesSummary(mLinkProperties));
                  }
                  break;
              case WifiP2pServiceImpl.P2P_CONNECTION_CHANGED:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  if (msg.obj != null) {
                      NetworkInfo info = (NetworkInfo) msg.obj;
                      NetworkInfo.State state = info.getState();
                      NetworkInfo.DetailedState detailedState = info.getDetailedState();
                      if (state != null) {
                          sb.append(" st=").append(state);
                      }
                      if (detailedState != null) {
                          sb.append("/").append(detailedState);
                      }
                  }
                  break;
              case CMD_IP_CONFIGURATION_LOST:
                  int count = -1;
                  WifiConfiguration c = getCurrentWifiConfiguration();
                  if (c != null) {
                      count = c.getNetworkSelectionStatus().getDisableReasonCounter(
                              WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE);
                  }
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  sb.append(" failures: ");
                  sb.append(Integer.toString(count));
                  sb.append("/");
                  sb.append(Integer.toString(mFacade.getIntegerSetting(
                          mContext, Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT, 0)));
                  if (mWifiInfo.getBSSID() != null) {
                      sb.append(" ").append(mWifiInfo.getBSSID());
                  }
                  sb.append(String.format(" bcn=%d", mRunningBeaconCount));
                  break;
              case CMD_UPDATE_LINKPROPERTIES:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  if (mLinkProperties != null) {
                      sb.append(" ");
                      sb.append(getLinkPropertiesSummary(mLinkProperties));
                  }
                  break;
              case CMD_IP_REACHABILITY_LOST:
                  if (msg.obj != null) {
                      sb.append(" ").append((String) msg.obj);
                  }
                  break;
              case CMD_INSTALL_PACKET_FILTER:
                  sb.append(" len=" + ((byte[]) msg.obj).length);
                  break;
              case CMD_SET_FALLBACK_PACKET_FILTERING:
                  sb.append(" enabled=" + (boolean) msg.obj);
                  break;
              case CMD_ROAM_WATCHDOG_TIMER:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  sb.append(" cur=").append(mRoamWatchdogCount);
                  break;
              case CMD_DISCONNECTING_WATCHDOG_TIMER:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  sb.append(" cur=").append(mDisconnectingWatchdogCount);
                  break;
              case CMD_START_RSSI_MONITORING_OFFLOAD:
              case CMD_STOP_RSSI_MONITORING_OFFLOAD:
              case CMD_RSSI_THRESHOLD_BREACHED:
                  sb.append(" rssi=");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" thresholds=");
                  sb.append(Arrays.toString(mRssiRanges));
                  break;
              case CMD_USER_SWITCH:
                  sb.append(" userId=");
                  sb.append(Integer.toString(msg.arg1));
                  break;
              case CMD_IPV4_PROVISIONING_SUCCESS:
                  sb.append(" ");
                  sb.append(/* DhcpResults */ msg.obj);
                  break;
              default:
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg1));
                  sb.append(" ");
                  sb.append(Integer.toString(msg.arg2));
                  break;
          }
  
          return sb.toString();
      }
  
      @Override
      protected String getWhatToString(int what) {
          String s = sGetWhatToString.get(what);
          if (s != null) {
              return s;
          }
          switch (what) {
              case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
                  s = "CMD_CHANNEL_HALF_CONNECTED";
                  break;
              case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
                  s = "CMD_CHANNEL_DISCONNECTED";
                  break;
              case WifiManager.DISABLE_NETWORK:
                  s = "DISABLE_NETWORK";
                  break;
              case WifiManager.CONNECT_NETWORK:
                  s = "CONNECT_NETWORK";
                  break;
              case WifiManager.SAVE_NETWORK:
                  s = "SAVE_NETWORK";
                  break;
              case WifiManager.FORGET_NETWORK:
                  s = "FORGET_NETWORK";
                  break;
              case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                  s = "SUPPLICANT_STATE_CHANGE_EVENT";
                  break;
              case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                  s = "AUTHENTICATION_FAILURE_EVENT";
                  break;
              case WifiMonitor.SUP_REQUEST_IDENTITY:
                  s = "SUP_REQUEST_IDENTITY";
                  break;
              case WifiMonitor.NETWORK_CONNECTION_EVENT:
                  s = "NETWORK_CONNECTION_EVENT";
                  break;
              case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                  s = "NETWORK_DISCONNECTION_EVENT";
                  break;
              case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
                  s = "ASSOCIATION_REJECTION_EVENT";
                  break;
              case WifiMonitor.ANQP_DONE_EVENT:
                  s = "ANQP_DONE_EVENT";
                  break;
              case WifiMonitor.RX_HS20_ANQP_ICON_EVENT:
                  s = "RX_HS20_ANQP_ICON_EVENT";
                  break;
              case WifiMonitor.GAS_QUERY_DONE_EVENT:
                  s = "GAS_QUERY_DONE_EVENT";
                  break;
              case WifiMonitor.HS20_REMEDIATION_EVENT:
                  s = "HS20_REMEDIATION_EVENT";
                  break;
              case WifiMonitor.GAS_QUERY_START_EVENT:
                  s = "GAS_QUERY_START_EVENT";
                  break;
              case WifiP2pServiceImpl.GROUP_CREATING_TIMED_OUT:
                  s = "GROUP_CREATING_TIMED_OUT";
                  break;
              case WifiP2pServiceImpl.P2P_CONNECTION_CHANGED:
                  s = "P2P_CONNECTION_CHANGED";
                  break;
              case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
                  s = "DISCONNECT_WIFI_REQUEST";
                  break;
              case WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE:
                  s = "DISCONNECT_WIFI_RESPONSE";
                  break;
              case WifiP2pServiceImpl.SET_MIRACAST_MODE:
                  s = "SET_MIRACAST_MODE";
                  break;
              case WifiP2pServiceImpl.BLOCK_DISCOVERY:
                  s = "BLOCK_DISCOVERY";
                  break;
              case WifiManager.RSSI_PKTCNT_FETCH:
                  s = "RSSI_PKTCNT_FETCH";
                  break;
              default:
                  s = "what:" + Integer.toString(what);
                  break;
          }
          return s;
      }
  
      private void handleScreenStateChanged(boolean screenOn) {
          mScreenOn = screenOn;
          if (mVerboseLoggingEnabled) {
              logd(" handleScreenStateChanged Enter: screenOn=" + screenOn
                      + " mUserWantsSuspendOpt=" + mUserWantsSuspendOpt
                      + " state " + getCurrentState().getName()
                      + " suppState:" + mSupplicantStateTracker.getSupplicantStateName());
          }
          enableRssiPolling(screenOn);
          if (mUserWantsSuspendOpt.get()) {
              int shouldReleaseWakeLock = 0;
              if (screenOn) {
                  sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, shouldReleaseWakeLock);
              } else {
                  if (isConnected()) {
                      // Allow 2s for suspend optimizations to be set
                      mSuspendWakeLock.acquire(2000);
                      shouldReleaseWakeLock = 1;
                  }
                  sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, shouldReleaseWakeLock);
              }
          }
  
          getWifiLinkLayerStats();
          mOnTimeScreenStateChange = mOnTime;
          mLastScreenStateChangeTimeStamp = mLastLinkLayerStatsUpdate;
  
          mWifiMetrics.setScreenState(screenOn);
  
          mWifiConnectivityManager.handleScreenStateChanged(screenOn);
          mNetworkFactory.handleScreenStateChanged(screenOn);
  
          WifiLockManager wifiLockManager = mWifiInjector.getWifiLockManager();
          if (wifiLockManager == null) {
              Log.w(TAG, "WifiLockManager not initialized, skipping screen state notification");
          } else {
              wifiLockManager.handleScreenStateChanged(screenOn);
          }
  
          mSarManager.handleScreenStateChanged(screenOn);
  
          if (mVerboseLoggingEnabled) log("handleScreenStateChanged Exit: " + screenOn);
      }
  
      private boolean checkAndSetConnectivityInstance() {
          if (mCm == null) {
              mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
          }
          if (mCm == null) {
              Log.e(TAG, "Cannot retrieve connectivity service");
              return false;
          }
          return true;
      }
  
      private void setSuspendOptimizationsNative(int reason, boolean enabled) {
          if (mVerboseLoggingEnabled) {
              log("setSuspendOptimizationsNative: " + reason + " " + enabled
                      + " -want " + mUserWantsSuspendOpt.get()
                      + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
                      + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
                      + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
                      + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
          }
          //mWifiNative.setSuspendOptimizations(enabled);
  
          if (enabled) {
              mSuspendOptNeedsDisabled &= ~reason;
              /* None of dhcp, screen or highperf need it disabled and user wants it enabled */
              if (mSuspendOptNeedsDisabled == 0 && mUserWantsSuspendOpt.get()) {
                  if (mVerboseLoggingEnabled) {
                      log("setSuspendOptimizationsNative do it " + reason + " " + enabled
                              + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
                              + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
                              + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
                              + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
                  }
                  mWifiNative.setSuspendOptimizations(mInterfaceName, true);
              }
          } else {
              mSuspendOptNeedsDisabled |= reason;
              mWifiNative.setSuspendOptimizations(mInterfaceName, false);
          }
      }
  
      /**
       * Makes a record of the user intent about suspend optimizations.
       */
      private void setSuspendOptimizations(int reason, boolean enabled) {
          if (mVerboseLoggingEnabled) log("setSuspendOptimizations: " + reason + " " + enabled);
          if (enabled) {
              mSuspendOptNeedsDisabled &= ~reason;
          } else {
              mSuspendOptNeedsDisabled |= reason;
          }
          if (mVerboseLoggingEnabled) log("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
      }
  
      /*
       * Fetch RSSI, linkspeed, and frequency on current connection
       */
      private void fetchRssiLinkSpeedAndFrequencyNative() {
          WifiNative.SignalPollResult pollResult = mWifiNative.signalPoll(mInterfaceName);
          if (pollResult == null) {
              return;
          }
  
          int newRssi = pollResult.currentRssi;
          int newTxLinkSpeed = pollResult.txBitrate;
          int newFrequency = pollResult.associationFrequency;
          int newRxLinkSpeed = pollResult.rxBitrate;
  
          if (mVerboseLoggingEnabled) {
              logd("fetchRssiLinkSpeedAndFrequencyNative rssi=" + newRssi
                      + " TxLinkspeed=" + newTxLinkSpeed + " freq=" + newFrequency
                      + " RxLinkSpeed=" + newRxLinkSpeed);
          }
  
          if (newRssi > WifiInfo.INVALID_RSSI && newRssi < WifiInfo.MAX_RSSI) {
              // screen out invalid values
              /* some implementations avoid negative values by adding 256
               * so we need to adjust for that here.
               */
              if (newRssi > 0) {
                  Log.wtf(TAG, "Error! +ve value RSSI: " + newRssi);
                  newRssi -= 256;
              }
              mWifiInfo.setRssi(newRssi);
              /*
               * Rather then sending the raw RSSI out every time it
               * changes, we precalculate the signal level that would
               * be displayed in the status bar, and only send the
               * broadcast if that much more coarse-grained number
               * changes. This cuts down greatly on the number of
               * broadcasts, at the cost of not informing others
               * interested in RSSI of all the changes in signal
               * level.
               */
              int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, WifiManager.RSSI_LEVELS);
              if (newSignalLevel != mLastSignalLevel) {
                  updateCapabilities();
                  sendRssiChangeBroadcast(newRssi);
              }
              mLastSignalLevel = newSignalLevel;
          } else {
              mWifiInfo.setRssi(WifiInfo.INVALID_RSSI);
              updateCapabilities();
          }
          /*
           * set Tx link speed only if it is valid
           */
          if (newTxLinkSpeed > 0) {
              mWifiInfo.setLinkSpeed(newTxLinkSpeed);
              mWifiInfo.setTxLinkSpeedMbps(newTxLinkSpeed);
          }
          /*
           * set Rx link speed only if it is valid
           */
          if (newRxLinkSpeed > 0) {
              mWifiInfo.setRxLinkSpeedMbps(newRxLinkSpeed);
          }
          if (newFrequency > 0) {
              mWifiInfo.setFrequency(newFrequency);
          }
          mWifiConfigManager.updateScanDetailCacheFromWifiInfo(mWifiInfo);
          /*
           * Increment various performance metrics
           */
          mWifiMetrics.handlePollResult(mWifiInfo);
      }
  
      // Polling has completed, hence we won't have a score anymore
      private void cleanWifiScore() {
          mWifiInfo.txBadRate = 0;
          mWifiInfo.txSuccessRate = 0;
          mWifiInfo.txRetriesRate = 0;
          mWifiInfo.rxSuccessRate = 0;
          mWifiScoreReport.reset();
          mLastLinkLayerStats = null;
      }
  
      private void updateLinkProperties(LinkProperties newLp) {
          if (mVerboseLoggingEnabled) {
              log("Link configuration changed for netId: " + mLastNetworkId
                      + " old: " + mLinkProperties + " new: " + newLp);
          }
          // We own this instance of LinkProperties because IpClient passes us a copy.
          mLinkProperties = newLp;
          if (mNetworkAgent != null) {
              mNetworkAgent.sendLinkProperties(mLinkProperties);
          }
  
          if (getNetworkDetailedState() == DetailedState.CONNECTED) {
              // If anything has changed and we're already connected, send out a notification.
              // TODO: Update all callers to use NetworkCallbacks and delete this.
              sendLinkConfigurationChangedBroadcast();
          }
  
          if (mVerboseLoggingEnabled) {
              StringBuilder sb = new StringBuilder();
              sb.append("updateLinkProperties nid: " + mLastNetworkId);
              sb.append(" state: " + getNetworkDetailedState());
  
              if (mLinkProperties != null) {
                  sb.append(" ");
                  sb.append(getLinkPropertiesSummary(mLinkProperties));
              }
              logd(sb.toString());
          }
      }
  
      /**
       * Clears all our link properties.
       */
      private void clearLinkProperties() {
          // Clear the link properties obtained from DHCP. The only caller of this
          // function has already called IpClient#stop(), which clears its state.
          synchronized (mDhcpResultsLock) {
              if (mDhcpResults != null) {
                  mDhcpResults.clear();
              }
          }
  
          // Now clear the merged link properties.
          mLinkProperties.clear();
          if (mNetworkAgent != null) mNetworkAgent.sendLinkProperties(mLinkProperties);
      }
  
      private void sendRssiChangeBroadcast(final int newRssi) {
          try {
              mBatteryStats.noteWifiRssiChanged(newRssi);
          } catch (RemoteException e) {
              // Won't happen.
          }
          StatsLog.write(StatsLog.WIFI_SIGNAL_STRENGTH_CHANGED,
                  WifiManager.calculateSignalLevel(newRssi, WifiManager.RSSI_LEVELS));
  
          Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION);
          intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
          intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi);
          mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
                  android.Manifest.permission.ACCESS_WIFI_STATE);
      }
  
      private void sendNetworkStateChangeBroadcast(String bssid) {
          Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
          intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
          NetworkInfo networkInfo = new NetworkInfo(mNetworkInfo);
          networkInfo.setExtraInfo(null);
          intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
          //TODO(b/69974497) This should be non-sticky, but settings needs fixing first.
          mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
      }
  
      private void sendLinkConfigurationChangedBroadcast() {
          Intent intent = new Intent(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
          intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
          intent.putExtra(WifiManager.EXTRA_LINK_PROPERTIES, new LinkProperties(mLinkProperties));
          mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
      }
  
      /**
       * Helper method used to send state about supplicant - This is NOT information about the current
       * wifi connection state.
       *
       * TODO: b/79504296 This broadcast has been deprecated and should be removed
       */
      private void sendSupplicantConnectionChangedBroadcast(boolean connected) {
          Intent intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
          intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
          intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, connected);
          mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
      }
  
      /**
       * Record the detailed state of a network.
       *
       * @param state the new {@code DetailedState}
       */
      private boolean setNetworkDetailedState(NetworkInfo.DetailedState state) {
          boolean hidden = false;
  
          if (mIsAutoRoaming) {
              // There is generally a confusion in the system about colluding
              // WiFi Layer 2 state (as reported by supplicant) and the Network state
              // which leads to multiple confusion.
              //
              // If link is roaming, we already have an IP address
              // as well we were connected and are doing L2 cycles of
              // reconnecting or renewing IP address to check that we still have it
              // This L2 link flapping should ne be reflected into the Network state
              // which is the state of the WiFi Network visible to Layer 3 and applications
              // Note that once roaming is completed, we will
              // set the Network state to where it should be, or leave it as unchanged
              //
              hidden = true;
          }
          if (mVerboseLoggingEnabled) {
              log("setDetailed state, old ="
                      + mNetworkInfo.getDetailedState() + " and new state=" + state
                      + " hidden=" + hidden);
          }
          if (hidden) {
              return false;
          }
  
          if (state != mNetworkInfo.getDetailedState()) {
              mNetworkInfo.setDetailedState(state, null, null);
              if (mNetworkAgent != null) {
                  mNetworkAgent.sendNetworkInfo(mNetworkInfo);
              }
              sendNetworkStateChangeBroadcast(null);
              return true;
          }
          return false;
      }
  
      private DetailedState getNetworkDetailedState() {
          return mNetworkInfo.getDetailedState();
      }
  
      private SupplicantState handleSupplicantStateChange(Message message) {
          StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
          SupplicantState state = stateChangeResult.state;
          mWifiScoreCard.noteSupplicantStateChanging(mWifiInfo, state);
          // Supplicant state change
          // [31-13] Reserved for future use
          // [8 - 0] Supplicant state (as defined in SupplicantState.java)
          // 50023 supplicant_state_changed (custom|1|5)
          mWifiInfo.setSupplicantState(state);
          // Network id and SSID are only valid when we start connecting
          if (SupplicantState.isConnecting(state)) {
              mWifiInfo.setNetworkId(stateChangeResult.networkId);
              mWifiInfo.setBSSID(stateChangeResult.BSSID);
              mWifiInfo.setSSID(stateChangeResult.wifiSsid);
          } else {
              // Reset parameters according to WifiInfo.reset()
              mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
              mWifiInfo.setBSSID(null);
              mWifiInfo.setSSID(null);
          }
          updateL2KeyAndGroupHint();
          // SSID might have been updated, so call updateCapabilities
          updateCapabilities();
  
          final WifiConfiguration config = getCurrentWifiConfiguration();
          if (config != null) {
              mWifiInfo.setEphemeral(config.ephemeral);
              mWifiInfo.setTrusted(config.trusted);
              mWifiInfo.setOsuAp(config.osu);
              if (config.fromWifiNetworkSpecifier || config.fromWifiNetworkSuggestion) {
                  mWifiInfo.setNetworkSuggestionOrSpecifierPackageName(config.creatorName);
              }
  
              // Set meteredHint if scan result says network is expensive
              ScanDetailCache scanDetailCache = mWifiConfigManager.getScanDetailCacheForNetwork(
                      config.networkId);
              if (scanDetailCache != null) {
                  ScanDetail scanDetail = scanDetailCache.getScanDetail(stateChangeResult.BSSID);
                  if (scanDetail != null) {
                      mWifiInfo.setFrequency(scanDetail.getScanResult().frequency);
                      NetworkDetail networkDetail = scanDetail.getNetworkDetail();
                      if (networkDetail != null
                              && networkDetail.getAnt() == NetworkDetail.Ant.ChargeablePublic) {
                          mWifiInfo.setMeteredHint(true);
                      }
                  }
              }
          }
  
          mSupplicantStateTracker.sendMessage(Message.obtain(message));
          mWifiScoreCard.noteSupplicantStateChanged(mWifiInfo);
          return state;
      }
  
      /**
       * Tells IpClient what L2Key and GroupHint to use for IpMemoryStore.
       */
      private void updateL2KeyAndGroupHint() {
          if (mIpClient != null) {
              Pair<String, String> p = mWifiScoreCard.getL2KeyAndGroupHint(mWifiInfo);
              if (!p.equals(mLastL2KeyAndGroupHint)) {
                  if (mIpClient.setL2KeyAndGroupHint(p.first, p.second)) {
                      mLastL2KeyAndGroupHint = p;
                  } else {
                      mLastL2KeyAndGroupHint = null;
                  }
              }
          }
      }
      private @Nullable Pair<String, String> mLastL2KeyAndGroupHint = null;
  
      /**
       * Resets the Wi-Fi Connections by clearing any state, resetting any sockets
       * using the interface, stopping DHCP & disabling interface
       */
      private void handleNetworkDisconnect() {
          if (mVerboseLoggingEnabled) {
              log("handleNetworkDisconnect: Stopping DHCP and clearing IP"
                      + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
                      + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
                      + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
                      + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
          }
  
          WifiConfiguration wifiConfig = getCurrentWifiConfiguration();
          if (wifiConfig != null) {
              ScanResultMatchInfo matchInfo = ScanResultMatchInfo.fromWifiConfiguration(wifiConfig);
              mWifiInjector.getWakeupController().setLastDisconnectInfo(matchInfo);
              mWifiNetworkSuggestionsManager.handleDisconnect(wifiConfig, getCurrentBSSID());
          }
  
          stopRssiMonitoringOffload();
  
          clearTargetBssid("handleNetworkDisconnect");
  
          stopIpClient();
  
          /* Reset data structures */
          mWifiScoreReport.reset();
          mWifiInfo.reset();
          /* Reset roaming parameters */
          mIsAutoRoaming = false;
  
          setNetworkDetailedState(DetailedState.DISCONNECTED);
          synchronized (mNetworkAgentLock) {
              if (mNetworkAgent != null) {
                  mNetworkAgent.sendNetworkInfo(mNetworkInfo);
                  mNetworkAgent = null;
              }
          }
  
          /* Clear network properties */
          clearLinkProperties();
  
          /* Cend event to CM & network change broadcast */
          sendNetworkStateChangeBroadcast(mLastBssid);
  
          mLastBssid = null;
          mLastLinkLayerStats = null;
          registerDisconnected();
          mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
          mWifiScoreCard.resetConnectionState();
          updateL2KeyAndGroupHint();
      }
  
      void handlePreDhcpSetup() {
          if (!mBluetoothConnectionActive) {
              /*
               * There are problems setting the Wi-Fi driver's power
               * mode to active when bluetooth coexistence mode is
               * enabled or sense.
               * <p>
               * We set Wi-Fi to active mode when
               * obtaining an IP address because we've found
               * compatibility issues with some routers with low power
               * mode.
               * <p>
               * In order for this active power mode to properly be set,
               * we disable coexistence mode until we're done with
               * obtaining an IP address.  One exception is if we
               * are currently connected to a headset, since disabling
               * coexistence would interrupt that connection.
               */
              // Disable the coexistence mode
              mWifiNative.setBluetoothCoexistenceMode(
                      mInterfaceName, WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
          }
  
          // Disable power save and suspend optimizations during DHCP
          // Note: The order here is important for now. Brcm driver changes
          // power settings when we control suspend mode optimizations.
          // TODO: Remove this comment when the driver is fixed.
          setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, false);
          setPowerSave(false);
  
          // Update link layer stats
          getWifiLinkLayerStats();
  
          if (mWifiP2pChannel != null) {
              /* P2p discovery breaks dhcp, shut it down in order to get through this */
              Message msg = new Message();
              msg.what = WifiP2pServiceImpl.BLOCK_DISCOVERY;
              msg.arg1 = WifiP2pServiceImpl.ENABLED;
              msg.arg2 = CMD_PRE_DHCP_ACTION_COMPLETE;
              msg.obj = ClientModeImpl.this;
              mWifiP2pChannel.sendMessage(msg);
          } else {
              // If the p2p service is not running, we can proceed directly.
              sendMessage(CMD_PRE_DHCP_ACTION_COMPLETE);
          }
      }
  
      void handlePostDhcpSetup() {
          /* Restore power save and suspend optimizations */
          setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, true);
          setPowerSave(true);
  
          p2pSendMessage(WifiP2pServiceImpl.BLOCK_DISCOVERY, WifiP2pServiceImpl.DISABLED);
  
          // Set the coexistence mode back to its default value
          mWifiNative.setBluetoothCoexistenceMode(
                  mInterfaceName, WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
      }
  
      /**
       * Set power save mode
       *
       * @param ps true to enable power save (default behavior)
       *           false to disable power save.
       * @return true for success, false for failure
       */
      public boolean setPowerSave(boolean ps) {
          if (mInterfaceName != null) {
              if (mVerboseLoggingEnabled) {
                  Log.d(TAG, "Setting power save for: " + mInterfaceName + " to: " + ps);
              }
              mWifiNative.setPowerSave(mInterfaceName, ps);
          } else {
              Log.e(TAG, "Failed to setPowerSave, interfaceName is null");
              return false;
          }
          return true;
      }
  
      /**
       * Set low latency mode
       *
       * @param enabled true to enable low latency
       *                false to disable low latency (default behavior).
       * @return true for success, false for failure
       */
      public boolean setLowLatencyMode(boolean enabled) {
          if (mVerboseLoggingEnabled) {
              Log.d(TAG, "Setting low latency mode to " + enabled);
          }
          if (!mWifiNative.setLowLatencyMode(enabled)) {
              Log.e(TAG, "Failed to setLowLatencyMode");
              return false;
          }
          return true;
      }
  
      @VisibleForTesting
      public static final long DIAGS_CONNECT_TIMEOUT_MILLIS = 60 * 1000;
      /**
       * Inform other components that a new connection attempt is starting.
       */
      private void reportConnectionAttemptStart(
              WifiConfiguration config, String targetBSSID, int roamType) {
          mWifiMetrics.startConnectionEvent(config, targetBSSID, roamType);
          mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_STARTED);
          mWrongPasswordNotifier.onNewConnectionAttempt();
          removeMessages(CMD_DIAGS_CONNECT_TIMEOUT);
          sendMessageDelayed(CMD_DIAGS_CONNECT_TIMEOUT, DIAGS_CONNECT_TIMEOUT_MILLIS);
      }
  
      private void handleConnectionAttemptEndForDiagnostics(int level2FailureCode) {
          switch (level2FailureCode) {
              case WifiMetrics.ConnectionEvent.FAILURE_NONE:
                  break;
              case WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED:
                  // WifiDiagnostics doesn't care about pre-empted connections, or cases
                  // where we failed to initiate a connection attempt with supplicant.
                  break;
              default:
                  removeMessages(CMD_DIAGS_CONNECT_TIMEOUT);
                  mWifiDiagnostics.reportConnectionEvent(WifiDiagnostics.CONNECTION_EVENT_FAILED);
          }
      }
  
      /**
       * Inform other components (WifiMetrics, WifiDiagnostics, WifiConnectivityManager, etc.) that
       * the current connection attempt has concluded.
       */
      private void reportConnectionAttemptEnd(int level2FailureCode, int connectivityFailureCode,
              int level2FailureReason) {
          if (level2FailureCode != WifiMetrics.ConnectionEvent.FAILURE_NONE) {
              mWifiScoreCard.noteConnectionFailure(mWifiInfo,
                      level2FailureCode, connectivityFailureCode);
          }
          boolean isAssociationRejection = level2FailureCode
                  == WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION;
          boolean isAuthenticationFailure = level2FailureCode
                  == WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE
                  && level2FailureReason != WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD;
          if ((isAssociationRejection || isAuthenticationFailure)
                  && mWifiConfigManager.isInFlakyRandomizationSsidHotlist(mTargetNetworkId)) {
              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);
          if (configuration != null) {
              mNetworkFactory.handleConnectionAttemptEnded(level2FailureCode, configuration);
              mWifiNetworkSuggestionsManager.handleConnectionAttemptEnded(
                      level2FailureCode, configuration, getCurrentBSSID());
          }
          handleConnectionAttemptEndForDiagnostics(level2FailureCode);
      }
  
      private void handleIPv4Success(DhcpResults dhcpResults) {
          if (mVerboseLoggingEnabled) {
              logd("handleIPv4Success <" + dhcpResults.toString() + ">");
              logd("link address " + dhcpResults.ipAddress);
          }
  
          Inet4Address addr;
          synchronized (mDhcpResultsLock) {
              mDhcpResults = dhcpResults;
              addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
          }
  
          if (mIsAutoRoaming) {
              int previousAddress = mWifiInfo.getIpAddress();
              int newAddress = NetworkUtils.inetAddressToInt(addr);
              if (previousAddress != newAddress) {
                  logd("handleIPv4Success, roaming and address changed"
                          + mWifiInfo + " got: " + addr);
              }
          }
  
          mWifiInfo.setInetAddress(addr);
  
          final WifiConfiguration config = getCurrentWifiConfiguration();
          if (config != null) {
              mWifiInfo.setEphemeral(config.ephemeral);
              mWifiInfo.setTrusted(config.trusted);
          }
  
          // Set meteredHint if DHCP result says network is metered
          if (dhcpResults.hasMeteredHint()) {
              mWifiInfo.setMeteredHint(true);
          }
  
          updateCapabilities(config);
      }
  
      private void handleSuccessfulIpConfiguration() {
          mLastSignalLevel = -1; // Force update of signal strength
          WifiConfiguration c = getCurrentWifiConfiguration();
          if (c != null) {
              // Reset IP failure tracking
              c.getNetworkSelectionStatus().clearDisableReasonCounter(
                      WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE);
  
              // Tell the framework whether the newly connected network is trusted or untrusted.
              updateCapabilities(c);
          }
          mWifiScoreCard.noteIpConfiguration(mWifiInfo);
      }
  
      private void handleIPv4Failure() {
          // TODO: Move this to provisioning failure, not DHCP failure.
          // DHCPv4 failure is expected on an IPv6-only network.
          mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_DHCP_FAILURE);
          if (mVerboseLoggingEnabled) {
              int count = -1;
              WifiConfiguration config = getCurrentWifiConfiguration();
              if (config != null) {
                  count = config.getNetworkSelectionStatus().getDisableReasonCounter(
                          WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE);
              }
              log("DHCP failure count=" + count);
          }
          reportConnectionAttemptEnd(
                  WifiMetrics.ConnectionEvent.FAILURE_DHCP,
                  WifiMetricsProto.ConnectionEvent.HLF_DHCP,
                  WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
          synchronized (mDhcpResultsLock) {
              if (mDhcpResults != null) {
                  mDhcpResults.clear();
              }
          }
          if (mVerboseLoggingEnabled) {
              logd("handleIPv4Failure");
          }
      }
  
      private void handleIpConfigurationLost() {
          mWifiInfo.setInetAddress(null);
          mWifiInfo.setMeteredHint(false);
  
          mWifiConfigManager.updateNetworkSelectionStatus(mLastNetworkId,
                  WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE);
  
          /* DHCP times out after about 30 seconds, we do a
           * disconnect thru supplicant, we will let autojoin retry connecting to the network
           */
          mWifiNative.disconnect(mInterfaceName);
      }
  
      private void handleIpReachabilityLost() {
          mWifiScoreCard.noteIpReachabilityLost(mWifiInfo);
          mWifiInfo.setInetAddress(null);
          mWifiInfo.setMeteredHint(false);
  
          // Disconnect via supplicant, and let autojoin retry connecting to the network.
          mWifiNative.disconnect(mInterfaceName);
      }
  
      /*
       * Read a MAC address in /proc/arp/table, used by ClientModeImpl
       * so as to record MAC address of default gateway.
       **/
      private String macAddressFromRoute(String ipAddress) {
          String macAddress = null;
          BufferedReader reader = null;
          try {
              reader = new BufferedReader(new FileReader("/proc/net/arp"));
  
              // Skip over the line bearing column titles
              String line = reader.readLine();
  
              while ((line = reader.readLine()) != null) {
                  String[] tokens = line.split("[ ]+");
                  if (tokens.length < 6) {
                      continue;
                  }
  
                  // ARP column format is
                  // Address HWType HWAddress Flags Mask IFace
                  String ip = tokens[0];
                  String mac = tokens[3];
  
                  if (ipAddress.equals(ip)) {
                      macAddress = mac;
                      break;
                  }
              }
  
              if (macAddress == null) {
                  loge("Did not find remoteAddress {" + ipAddress + "} in /proc/net/arp");
              }
  
          } catch (FileNotFoundException e) {
              loge("Could not open /proc/net/arp to lookup mac address");
          } catch (IOException e) {
              loge("Could not read /proc/net/arp to lookup mac address");
          } finally {
              try {
                  if (reader != null) {
                      reader.close();
                  }
              } catch (IOException e) {
                  // Do nothing
              }
          }
          return macAddress;
  
      }
  
      /**
       * Determine if the specified auth failure is considered to be a permanent wrong password
       * failure. The criteria for such failure is when wrong password error is detected
       * and the network had never been connected before.
       *
       * For networks that have previously connected successfully, we consider wrong password
       * failures to be temporary, to be on the conservative side.  Since this might be the
       * case where we are trying to connect to a wrong network (e.g. A network with same SSID
       * but different password).
       */
      private boolean isPermanentWrongPasswordFailure(int networkId, int reasonCode) {
          if (reasonCode != WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD) {
              return false;
          }
          WifiConfiguration network = mWifiConfigManager.getConfiguredNetwork(networkId);
          if (network != null && network.getNetworkSelectionStatus().getHasEverConnected()) {
              return false;
          }
          return true;
      }
  
      void registerNetworkFactory() {
          if (!checkAndSetConnectivityInstance()) return;
          mNetworkFactory.register();
          mUntrustedNetworkFactory.register();
      }
  
      /**
       * ClientModeImpl needs to enable/disable other services when wifi is in client mode.  This
       * method allows ClientModeImpl to get these additional system services.
       *
       * At this time, this method is used to setup variables for P2P service and Wifi Aware.
       */
      private void getAdditionalWifiServiceInterfaces() {
          // First set up Wifi Direct
          if (mP2pSupported) {
              IBinder s1 = mFacade.getService(Context.WIFI_P2P_SERVICE);
              WifiP2pServiceImpl wifiP2pServiceImpl =
                      (WifiP2pServiceImpl) IWifiP2pManager.Stub.asInterface(s1);
  
              if (wifiP2pServiceImpl != null) {
                  mWifiP2pChannel = new AsyncChannel();
                  mWifiP2pChannel.connect(mContext, getHandler(),
                          wifiP2pServiceImpl.getP2pStateMachineMessenger());
              }
          }
      }
  
       /**
       * Dynamically change the MAC address to use the locally randomized
       * MAC address generated for each network.
       * @param config WifiConfiguration with mRandomizedMacAddress to change into. If the address
       * is masked out or not set, it will generate a new random MAC address.
       */
      private void configureRandomizedMacAddress(WifiConfiguration config) {
          if (config == null) {
              Log.e(TAG, "No config to change MAC address to");
              return;
          }
          String currentMacString = mWifiNative.getMacAddress(mInterfaceName);
          MacAddress currentMac = currentMacString == null ? null :
                  MacAddress.fromString(currentMacString);
          MacAddress newMac = config.getOrCreateRandomizedMacAddress();
          mWifiConfigManager.setNetworkRandomizedMacAddress(config.networkId, newMac);
          if (!WifiConfiguration.isValidMacAddressForRandomization(newMac)) {
              Log.wtf(TAG, "Config generated an invalid MAC address");
          } else if (newMac.equals(currentMac)) {
              Log.d(TAG, "No changes in MAC address");
          } else {
              mWifiMetrics.logStaEvent(StaEvent.TYPE_MAC_CHANGE, config);
              boolean setMacSuccess =
                      mWifiNative.setMacAddress(mInterfaceName, newMac);
              Log.d(TAG, "ConnectedMacRandomization SSID(" + config.getPrintableSsid()
                      + "). setMacAddress(" + newMac.toString() + ") from "
                      + currentMacString + " = " + setMacSuccess);
          }
      }
  
      /**
       * Sets the current MAC to the factory MAC address.
       */
      private void setCurrentMacToFactoryMac(WifiConfiguration config) {
          MacAddress factoryMac = mWifiNative.getFactoryMacAddress(mInterfaceName);
          if (factoryMac == null) {
              Log.e(TAG, "Fail to set factory MAC address. Factory MAC is null.");
              return;
          }
          String currentMacStr = mWifiNative.getMacAddress(mInterfaceName);
          if (!TextUtils.equals(currentMacStr, factoryMac.toString())) {
              if (mWifiNative.setMacAddress(mInterfaceName, factoryMac)) {
                  mWifiMetrics.logStaEvent(StaEvent.TYPE_MAC_CHANGE, config);
              } else {
                  Log.e(TAG, "Failed to set MAC address to " + "'" + factoryMac.toString() + "'");
              }
          }
      }
  
      /**
       * Helper method to check if Connected MAC Randomization is supported - onDown events are
       * skipped if this feature is enabled (b/72459123).
       *
       * @return boolean true if Connected MAC randomization is supported, false otherwise
       */
      public boolean isConnectedMacRandomizationEnabled() {
          return mConnectedMacRandomzationSupported;
      }
  
      /**
       * Helper method allowing ClientModeManager to report an error (interface went down) and trigger
       * recovery.
       *
       * @param reason int indicating the SelfRecovery failure type.
       */
      public void failureDetected(int reason) {
          // report a failure
          mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_STA_IFACE_DOWN);
      }
  
      /********************************************************
       * HSM states
       *******************************************************/
  
      class DefaultState extends State {
  
          @Override
          public boolean processMessage(Message message) {
              boolean handleStatus = HANDLED;
  
              switch (message.what) {
                  case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
                      AsyncChannel ac = (AsyncChannel) message.obj;
                      if (ac == mWifiP2pChannel) {
                          if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
                              p2pSendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
                          } else {
                              // TODO: We should probably do some cleanup or attempt a retry
                              // b/34283611
                              loge("WifiP2pService connection failure, error=" + message.arg1);
                          }
                      } else {
                          loge("got HALF_CONNECTED for unknown channel");
                      }
                      break;
                  }
                  case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
                      AsyncChannel ac = (AsyncChannel) message.obj;
                      if (ac == mWifiP2pChannel) {
                          loge("WifiP2pService channel lost, message.arg1 =" + message.arg1);
                          //TODO: Re-establish connection to state machine after a delay (b/34283611)
                          // mWifiP2pChannel.connect(mContext, getHandler(),
                          // mWifiP2pManager.getMessenger());
                      }
                      break;
                  }
                  case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE:
                      mBluetoothConnectionActive =
                              (message.arg1 != BluetoothAdapter.STATE_DISCONNECTED);
                      break;
                  case CMD_ENABLE_NETWORK:
                      boolean disableOthers = message.arg2 == 1;
                      int netId = message.arg1;
                      boolean ok = mWifiConfigManager.enableNetwork(
                              netId, disableOthers, message.sendingUid);
                      if (!ok) {
                          mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                      }
                      replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
                      break;
                  case CMD_ADD_OR_UPDATE_NETWORK:
                      WifiConfiguration config = (WifiConfiguration) message.obj;
                      NetworkUpdateResult result =
                              mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
                      if (!result.isSuccess()) {
                          mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                      }
                      replyToMessage(message, message.what, result.getNetworkId());
                      break;
                  case CMD_REMOVE_NETWORK:
                      deleteNetworkConfigAndSendReply(message, false);
                      break;
                  case CMD_GET_CONFIGURED_NETWORKS:
                      replyToMessage(message, message.what,
                              mWifiConfigManager.getSavedNetworks(message.arg2));
                      break;
                  case CMD_GET_PRIVILEGED_CONFIGURED_NETWORKS:
                      replyToMessage(message, message.what,
                              mWifiConfigManager.getConfiguredNetworksWithPasswords());
                      break;
                  case CMD_ENABLE_RSSI_POLL:
                      mEnableRssiPolling = (message.arg1 == 1);
                      break;
                  case CMD_SET_HIGH_PERF_MODE:
                      if (message.arg1 == 1) {
                          setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, false);
                      } else {
                          setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, true);
                      }
                      break;
                  case CMD_INITIALIZE:
                      ok = mWifiNative.initialize();
                      mPasspointManager.initializeProvisioner(
                              mWifiInjector.getWifiServiceHandlerThread().getLooper());
                      replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
                      break;
                  case CMD_BOOT_COMPLETED:
                      // get other services that we need to manage
                      getAdditionalWifiServiceInterfaces();
                      new MemoryStoreImpl(mContext, mWifiInjector, mWifiScoreCard).start();
                      if (!mWifiConfigManager.loadFromStore()) {
                          Log.e(TAG, "Failed to load from config store");
                      }
                      registerNetworkFactory();
                      break;
                  case CMD_SCREEN_STATE_CHANGED:
                      handleScreenStateChanged(message.arg1 != 0);
                      break;
                  case CMD_DISCONNECT:
                  case CMD_RECONNECT:
                  case CMD_REASSOCIATE:
                  case WifiMonitor.NETWORK_CONNECTION_EVENT:
                  case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                  case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                  case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                  case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
                  case CMD_RSSI_POLL:
                  case CMD_ONESHOT_RSSI_POLL:
                  case CMD_PRE_DHCP_ACTION:
                  case CMD_PRE_DHCP_ACTION_COMPLETE:
                  case CMD_POST_DHCP_ACTION:
                  case WifiMonitor.SUP_REQUEST_IDENTITY:
                  case WifiMonitor.SUP_REQUEST_SIM_AUTH:
                  case CMD_TARGET_BSSID:
                  case CMD_START_CONNECT:
                  case CMD_START_ROAM:
                  case CMD_ASSOCIATED_BSSID:
                  case CMD_UNWANTED_NETWORK:
                  case CMD_DISCONNECTING_WATCHDOG_TIMER:
                  case CMD_ROAM_WATCHDOG_TIMER:
                  case CMD_DISABLE_EPHEMERAL_NETWORK:
                      mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                      break;
                  case CMD_SET_OPERATIONAL_MODE:
                      // using the CMD_SET_OPERATIONAL_MODE (sent at front of queue) to trigger the
                      // state transitions performed in setOperationalMode.
                      break;
                  case CMD_SET_SUSPEND_OPT_ENABLED:
                      if (message.arg1 == 1) {
                          if (message.arg2 == 1) {
                              mSuspendWakeLock.release();
                          }
                          setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, true);
                      } else {
                          setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, false);
                      }
                      break;
                  case WifiManager.CONNECT_NETWORK:
                      replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                              WifiManager.BUSY);
                      break;
                  case WifiManager.FORGET_NETWORK:
                      deleteNetworkConfigAndSendReply(message, true);
                      break;
                  case WifiManager.SAVE_NETWORK:
                      saveNetworkConfigAndSendReply(message);
                      break;
                  case WifiManager.DISABLE_NETWORK:
                      replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
                              WifiManager.BUSY);
                      break;
                  case WifiManager.RSSI_PKTCNT_FETCH:
                      replyToMessage(message, WifiManager.RSSI_PKTCNT_FETCH_FAILED,
                              WifiManager.BUSY);
                      break;
                  case CMD_GET_SUPPORTED_FEATURES:
                      long featureSet = (mWifiNative.getSupportedFeatureSet(mInterfaceName));
                      replyToMessage(message, message.what, Long.valueOf(featureSet));
                      break;
                  case CMD_GET_LINK_LAYER_STATS:
                      // Not supported hence reply with error message
                      replyToMessage(message, message.what, null);
                      break;
                  case WifiP2pServiceImpl.P2P_CONNECTION_CHANGED:
                      NetworkInfo info = (NetworkInfo) message.obj;
                      mP2pConnected.set(info.isConnected());
                      break;
                  case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
                      mTemporarilyDisconnectWifi = (message.arg1 == 1);
                      replyToMessage(message, WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE);
                      break;
                  /* Link configuration (IP address, DNS, ...) changes notified via netlink */
                  case CMD_UPDATE_LINKPROPERTIES:
                      updateLinkProperties((LinkProperties) message.obj);
                      break;
                  case CMD_GET_MATCHING_OSU_PROVIDERS:
                      replyToMessage(message, message.what, new HashMap<>());
                      break;
                  case CMD_GET_MATCHING_PASSPOINT_CONFIGS_FOR_OSU_PROVIDERS:
                      replyToMessage(message, message.what,
                              new HashMap<OsuProvider, PasspointConfiguration>());
                      break;
                  case CMD_GET_WIFI_CONFIGS_FOR_PASSPOINT_PROFILES:
                      replyToMessage(message, message.what, new ArrayList<>());
                      break;
                  case CMD_START_SUBSCRIPTION_PROVISIONING:
                      replyToMessage(message, message.what, 0);
                      break;
                  case CMD_IP_CONFIGURATION_SUCCESSFUL:
                  case CMD_IP_CONFIGURATION_LOST:
                  case CMD_IP_REACHABILITY_LOST:
                      mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                      break;
                  case CMD_REMOVE_APP_CONFIGURATIONS:
                      deferMessage(message);
                      break;
                  case CMD_REMOVE_USER_CONFIGURATIONS:
                      deferMessage(message);
                      break;
                  case CMD_START_IP_PACKET_OFFLOAD:
                      /* fall-through */
                  case CMD_STOP_IP_PACKET_OFFLOAD:
                  case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF:
                  case CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF:
                      if (mNetworkAgent != null) {
                          mNetworkAgent.onSocketKeepaliveEvent(message.arg1,
                                  SocketKeepalive.ERROR_INVALID_NETWORK);
                      }
                      break;
                  case CMD_START_RSSI_MONITORING_OFFLOAD:
                      mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                      break;
                  case CMD_STOP_RSSI_MONITORING_OFFLOAD:
                      mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                      break;
                  case CMD_USER_SWITCH:
                      Set<Integer> removedNetworkIds =
                              mWifiConfigManager.handleUserSwitch(message.arg1);
                      if (removedNetworkIds.contains(mTargetNetworkId)
                              || removedNetworkIds.contains(mLastNetworkId)) {
                          // Disconnect and let autojoin reselect a new network
                          sendMessage(CMD_DISCONNECT);
                      }
                      break;
                  case CMD_USER_UNLOCK:
                      mWifiConfigManager.handleUserUnlock(message.arg1);
                      break;
                  case CMD_USER_STOP:
                      mWifiConfigManager.handleUserStop(message.arg1);
                      break;
                  case CMD_QUERY_OSU_ICON:
                  case CMD_MATCH_PROVIDER_NETWORK:
                      /* reply with arg1 = 0 - it returns API failure to the calling app
                       * (message.what is not looked at)
                       */
                      replyToMessage(message, message.what);
                      break;
                  case CMD_ADD_OR_UPDATE_PASSPOINT_CONFIG:
                      Bundle bundle = (Bundle) message.obj;
                      int addResult = mPasspointManager.addOrUpdateProvider(bundle.getParcelable(
                              EXTRA_PASSPOINT_CONFIGURATION),
                              bundle.getInt(EXTRA_UID),
                              bundle.getString(EXTRA_PACKAGE_NAME))
                              ? SUCCESS : FAILURE;
                      replyToMessage(message, message.what, addResult);
                      break;
                  case CMD_REMOVE_PASSPOINT_CONFIG:
                      int removeResult = mPasspointManager.removeProvider(
                              message.sendingUid, message.arg1 == 1, (String) message.obj)
                              ? SUCCESS : FAILURE;
                      replyToMessage(message, message.what, removeResult);
                      break;
                  case CMD_GET_PASSPOINT_CONFIGS:
                      replyToMessage(message, message.what, mPasspointManager.getProviderConfigs(
                              message.sendingUid, message.arg1 == 1));
                      break;
                  case CMD_RESET_SIM_NETWORKS:
                      /* Defer this message until supplicant is started. */
                      mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
                      deferMessage(message);
                      break;
                  case CMD_INSTALL_PACKET_FILTER:
                      mWifiNative.installPacketFilter(mInterfaceName, (byte[]) message.obj);
                      break;
                  case CMD_READ_PACKET_FILTER:
                      byte[] data = mWifiNative.readPacketFilter(mInterfaceName);
                      if (mIpClient != null) {
                          mIpClient.readPacketFilterComplete(data);
                      }
                      break;
                  case CMD_SET_FALLBACK_PACKET_FILTERING:
                      if ((boolean) message.obj) {
                          mWifiNative.startFilteringMulticastV4Packets(mInterfaceName);
                      } else {
                          mWifiNative.stopFilteringMulticastV4Packets(mInterfaceName);
                      }
                      break;
                  case CMD_DIAGS_CONNECT_TIMEOUT:
                      mWifiDiagnostics.reportConnectionEvent(
                              BaseWifiDiagnostics.CONNECTION_EVENT_TIMEOUT);
                      break;
                  case CMD_GET_ALL_MATCHING_FQDNS_FOR_SCAN_RESULTS:
                      replyToMessage(message, message.what, new HashMap<>());
                      break;
                  case 0:
                      // We want to notice any empty messages (with what == 0) that might crop up.
                      // For example, we may have recycled a message sent to multiple handlers.
                      Log.wtf(TAG, "Error! empty message encountered");
                      break;
                  default:
                      loge("Error! unhandled message" + message);
                      break;
              }
  
              if (handleStatus == HANDLED) {
                  logStateAndMessage(message, this);
              }
  
              return handleStatus;
          }
      }
  
      /**
       * Helper method to start other services and get state ready for client mode
       */
      private void setupClientMode() {
          Log.d(TAG, "setupClientMode() ifacename = " + mInterfaceName);
  
          setHighPerfModeEnabled(false);
  
          mWifiStateTracker.updateState(WifiStateTracker.INVALID);
          mIpClientCallbacks = new IpClientCallbacksImpl();
          mFacade.makeIpClient(mContext, mInterfaceName, mIpClientCallbacks);
          if (!mIpClientCallbacks.awaitCreation()) {
              loge("Timeout waiting for IpClient");
          }
  
          setMulticastFilter(true);
          registerForWifiMonitorEvents();
          mWifiInjector.getWifiLastResortWatchdog().clearAllFailureCounts();
          setSupplicantLogLevel();
  
          // reset state related to supplicant starting
          mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
          // Initialize data structures
          mLastBssid = null;
          mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
          mLastSignalLevel = -1;
          mWifiInfo.setMacAddress(mWifiNative.getMacAddress(mInterfaceName));
          // TODO: b/79504296 This broadcast has been deprecated and should be removed
          sendSupplicantConnectionChangedBroadcast(true);
  
          mWifiNative.setExternalSim(mInterfaceName, true);
  
          setRandomMacOui();
          mCountryCode.setReadyForChange(true);
  
          mWifiDiagnostics.startPktFateMonitoring(mInterfaceName);
          mWifiDiagnostics.startLogging(mInterfaceName);
  
          mIsRunning = true;
          updateBatteryWorkSource(null);
  
          /**
           * Enable bluetooth coexistence scan mode when bluetooth connection is active.
           * When this mode is on, some of the low-level scan parameters used by the
           * driver are changed to reduce interference with bluetooth
           */
          mWifiNative.setBluetoothCoexistenceScanMode(mInterfaceName, mBluetoothConnectionActive);
  
          // initialize network state
          setNetworkDetailedState(DetailedState.DISCONNECTED);
  
          // Disable legacy multicast filtering, which on some chipsets defaults to enabled.
          // Legacy IPv6 multicast filtering blocks ICMPv6 router advertisements which breaks IPv6
          // provisioning. Legacy IPv4 multicast filtering may be re-enabled later via
          // IpClient.Callback.setFallbackMulticastFilter()
          mWifiNative.stopFilteringMulticastV4Packets(mInterfaceName);
          mWifiNative.stopFilteringMulticastV6Packets(mInterfaceName);
  
          // Set the right suspend mode settings
          mWifiNative.setSuspendOptimizations(mInterfaceName, mSuspendOptNeedsDisabled == 0
                  && mUserWantsSuspendOpt.get());
  
          setPowerSave(true);
  
          // Disable wpa_supplicant from auto reconnecting.
          mWifiNative.enableStaAutoReconnect(mInterfaceName, false);
          // STA has higher priority over P2P
          mWifiNative.setConcurrencyPriority(true);
      }
  
      /**
       * Helper method to stop external services and clean up state from client mode.
       */
      private void stopClientMode() {
          // exiting supplicant started state is now only applicable to client mode
          mWifiDiagnostics.stopLogging(mInterfaceName);
  
          mIsRunning = false;
          updateBatteryWorkSource(null);
  
          if (mIpClient != null && mIpClient.shutdown()) {
              // Block to make sure IpClient has really shut down, lest cleanup
              // race with, say, bringup code over in tethering.
              mIpClientCallbacks.awaitShutdown();
          }
          mNetworkInfo.setIsAvailable(false);
          if (mNetworkAgent != null) mNetworkAgent.sendNetworkInfo(mNetworkInfo);
          mCountryCode.setReadyForChange(false);
          mInterfaceName = null;
          // TODO: b/79504296 This broadcast has been deprecated and should be removed
          sendSupplicantConnectionChangedBroadcast(false);
  
          // Let's remove any ephemeral or passpoint networks.
          mWifiConfigManager.removeAllEphemeralOrPasspointConfiguredNetworks();
      }
  
      void registerConnected() {
          if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
              mWifiConfigManager.updateNetworkAfterConnect(mLastNetworkId);
              // Notify PasspointManager of Passpoint network connected event.
              WifiConfiguration currentNetwork = getCurrentWifiConfiguration();
              if (currentNetwork != null && currentNetwork.isPasspoint()) {
                  mPasspointManager.onPasspointNetworkConnected(currentNetwork.FQDN);
              }
          }
      }
  
      void registerDisconnected() {
          if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
              mWifiConfigManager.updateNetworkAfterDisconnect(mLastNetworkId);
          }
      }
  
      /**
       * Returns WifiConfiguration object corresponding to the currently connected network, null if
       * not connected.
       */
      public WifiConfiguration getCurrentWifiConfiguration() {
          if (mLastNetworkId == WifiConfiguration.INVALID_NETWORK_ID) {
              return null;
          }
          return mWifiConfigManager.getConfiguredNetwork(mLastNetworkId);
      }
  
      private WifiConfiguration getTargetWifiConfiguration() {
          if (mTargetNetworkId == WifiConfiguration.INVALID_NETWORK_ID) {
              return null;
          }
          return mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
      }
  
      ScanResult getCurrentScanResult() {
          WifiConfiguration config = getCurrentWifiConfiguration();
          if (config == null) {
              return null;
          }
          String bssid = mWifiInfo.getBSSID();
          if (bssid == null) {
              bssid = mTargetRoamBSSID;
          }
          ScanDetailCache scanDetailCache =
                  mWifiConfigManager.getScanDetailCacheForNetwork(config.networkId);
  
          if (scanDetailCache == null) {
              return null;
          }
  
          return scanDetailCache.getScanResult(bssid);
      }
  
      String getCurrentBSSID() {
          return mLastBssid;
      }
  
      class ConnectModeState extends State {
  
          @Override
          public void enter() {
              Log.d(TAG, "entering ConnectModeState: ifaceName = " + mInterfaceName);
              mOperationalMode = CONNECT_MODE;
              setupClientMode();
              if (!mWifiNative.removeAllNetworks(mInterfaceName)) {
                  loge("Failed to remove networks on entering connect mode");
              }
              mWifiInfo.reset();
              mWifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
  
              mWifiInjector.getWakeupController().reset();
  
              mNetworkInfo.setIsAvailable(true);
              if (mNetworkAgent != null) mNetworkAgent.sendNetworkInfo(mNetworkInfo);
  
              // initialize network state
              setNetworkDetailedState(DetailedState.DISCONNECTED);
  
              // Inform WifiConnectivityManager that Wifi is enabled
              mWifiConnectivityManager.setWifiEnabled(true);
              mNetworkFactory.setWifiState(true);
              // Inform metrics that Wifi is Enabled (but not yet connected)
              mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
              mWifiMetrics.logStaEvent(StaEvent.TYPE_WIFI_ENABLED);
              // Inform sar manager that wifi is Enabled
              mSarManager.setClientWifiState(WifiManager.WIFI_STATE_ENABLED);
              mWifiScoreCard.noteSupplicantStateChanged(mWifiInfo);
          }
  
          @Override
          public void exit() {
              mOperationalMode = DISABLED_MODE;
              // Let the system know that wifi is not available since we are exiting client mode.
              mNetworkInfo.setIsAvailable(false);
              if (mNetworkAgent != null) mNetworkAgent.sendNetworkInfo(mNetworkInfo);
  
              // Inform WifiConnectivityManager that Wifi is disabled
              mWifiConnectivityManager.setWifiEnabled(false);
              mNetworkFactory.setWifiState(false);
              // Inform metrics that Wifi is being disabled (Toggled, airplane enabled, etc)
              mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_DISABLED);
              mWifiMetrics.logStaEvent(StaEvent.TYPE_WIFI_DISABLED);
              // Inform scorecard that wifi is being disabled
              mWifiScoreCard.noteWifiDisabled(mWifiInfo);
              // Inform sar manager that wifi is being disabled
              mSarManager.setClientWifiState(WifiManager.WIFI_STATE_DISABLED);
  
              if (!mWifiNative.removeAllNetworks(mInterfaceName)) {
                  loge("Failed to remove networks on exiting connect mode");
              }
              mWifiInfo.reset();
              mWifiInfo.setSupplicantState(SupplicantState.DISCONNECTED);
              mWifiScoreCard.noteSupplicantStateChanged(mWifiInfo);
              stopClientMode();
          }
  
          @Override
          public boolean processMessage(Message message) {
              WifiConfiguration config;
              int netId;
              boolean ok;
              boolean didDisconnect;
              String bssid;
              String ssid;
              NetworkUpdateResult result;
              Set<Integer> removedNetworkIds;
              int reasonCode;
              boolean timedOut;
              boolean handleStatus = HANDLED;
  
              switch (message.what) {
                  case WifiMonitor.ASSOCIATION_REJECTION_EVENT:
                      mWifiDiagnostics.captureBugReportData(
                              WifiDiagnostics.REPORT_REASON_ASSOC_FAILURE);
                      mDidBlackListBSSID = false;
                      bssid = (String) message.obj;
                      timedOut = message.arg1 > 0;
                      reasonCode = message.arg2;
                      Log.d(TAG, "Association Rejection event: bssid=" + bssid + " reason code="
                              + reasonCode + " timedOut=" + Boolean.toString(timedOut));
                      if (bssid == null || TextUtils.isEmpty(bssid)) {
                          // If BSSID is null, use the target roam BSSID
                          bssid = mTargetRoamBSSID;
                      }
                      if (bssid != null) {
                          // If we have a BSSID, tell configStore to black list it
                          mDidBlackListBSSID = mWifiConnectivityManager.trackBssid(bssid, false,
                              reasonCode);
                      }
                      mWifiConfigManager.updateNetworkSelectionStatus(mTargetNetworkId,
                              WifiConfiguration.NetworkSelectionStatus
                              .DISABLED_ASSOCIATION_REJECTION);
                      mWifiConfigManager.setRecentFailureAssociationStatus(mTargetNetworkId,
                              reasonCode);
                      mSupplicantStateTracker.sendMessage(WifiMonitor.ASSOCIATION_REJECTION_EVENT);
                      // If rejection occurred while Metrics is tracking a ConnnectionEvent, end it.
                      reportConnectionAttemptEnd(
                              timedOut
                                      ? WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT
                                      : WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION,
                              WifiMetricsProto.ConnectionEvent.HLF_NONE,
                              WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
                      mWifiInjector.getWifiLastResortWatchdog()
                              .noteConnectionFailureAndTriggerIfNeeded(
                                      getTargetSsid(), bssid,
                                      WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
                      break;
                  case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
                      mWifiDiagnostics.captureBugReportData(
                              WifiDiagnostics.REPORT_REASON_AUTH_FAILURE);
                      mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT);
                      int disableReason = WifiConfiguration.NetworkSelectionStatus
                              .DISABLED_AUTHENTICATION_FAILURE;
                      reasonCode = message.arg1;
                      // Check if this is a permanent wrong password failure.
                      if (isPermanentWrongPasswordFailure(mTargetNetworkId, reasonCode)) {
                          disableReason = WifiConfiguration.NetworkSelectionStatus
                                  .DISABLED_BY_WRONG_PASSWORD;
                          WifiConfiguration targetedNetwork =
                                  mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
                          if (targetedNetwork != null) {
                              mWrongPasswordNotifier.onWrongPasswordError(
                                      targetedNetwork.SSID);
                          }
                      } else if (reasonCode == WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE) {
                          int errorCode = message.arg2;
                          handleEapAuthFailure(mTargetNetworkId, errorCode);
                          if (errorCode == WifiNative.EAP_SIM_NOT_SUBSCRIBED) {
                              disableReason = WifiConfiguration.NetworkSelectionStatus
                                  .DISABLED_AUTHENTICATION_NO_SUBSCRIPTION;
                          }
                      }
                      mWifiConfigManager.updateNetworkSelectionStatus(
                              mTargetNetworkId, disableReason);
                      mWifiConfigManager.clearRecentFailureReason(mTargetNetworkId);
  
                      //If failure occurred while Metrics is tracking a ConnnectionEvent, end it.
                      int level2FailureReason;
                      switch (reasonCode) {
                          case WifiManager.ERROR_AUTH_FAILURE_NONE:
                              level2FailureReason =
                                      WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_NONE;
                              break;
                          case WifiManager.ERROR_AUTH_FAILURE_TIMEOUT:
                              level2FailureReason =
                                      WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_TIMEOUT;
                              break;
                          case WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD:
                              level2FailureReason =
                                      WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_WRONG_PSWD;
                              break;
                          case WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE:
                              level2FailureReason =
                                      WifiMetricsProto.ConnectionEvent.AUTH_FAILURE_EAP_FAILURE;
                              break;
                          default:
                              level2FailureReason =
                                      WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN;
                              break;
                      }
                      reportConnectionAttemptEnd(
                              WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE,
                              WifiMetricsProto.ConnectionEvent.HLF_NONE,
                              level2FailureReason);
                      if (reasonCode != WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD) {
                          mWifiInjector.getWifiLastResortWatchdog()
                                  .noteConnectionFailureAndTriggerIfNeeded(
                                          getTargetSsid(), mTargetRoamBSSID,
                                          WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
                      }
                      break;
                  case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                      SupplicantState state = handleSupplicantStateChange(message);
  
                      // Supplicant can fail to report a NETWORK_DISCONNECTION_EVENT
                      // when authentication times out after a successful connection,
                      // we can figure this from the supplicant state. If supplicant
                      // state is DISCONNECTED, but the mNetworkInfo says we are not
                      // disconnected, we need to handle a disconnection
                      if (state == SupplicantState.DISCONNECTED
                              && mNetworkInfo.getState() != NetworkInfo.State.DISCONNECTED) {
                          if (mVerboseLoggingEnabled) {
                              log("Missed CTRL-EVENT-DISCONNECTED, disconnect");
                          }
                          handleNetworkDisconnect();
                          transitionTo(mDisconnectedState);
                      }
  
                      // If we have COMPLETED a connection to a BSSID, start doing
                      // DNAv4/DNAv6 -style probing for on-link neighbors of
                      // interest (e.g. routers); harmless if none are configured.
                      if (state == SupplicantState.COMPLETED) {
                          if (mIpClient != null) {
                              mIpClient.confirmConfiguration();
                          }
                          mWifiScoreReport.noteIpCheck();
                      }
                      break;
                  case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
                      if (message.arg1 == 1) {
                          mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                                  StaEvent.DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST);
                          mWifiNative.disconnect(mInterfaceName);
                          mTemporarilyDisconnectWifi = true;
                      } else {
                          mWifiNative.reconnect(mInterfaceName);
                          mTemporarilyDisconnectWifi = false;
                      }
                      break;
                  case CMD_REMOVE_NETWORK:
                      if (!deleteNetworkConfigAndSendReply(message, false)) {
                          // failed to remove the config and caller was notified
                          mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                          break;
                      }
                      //  we successfully deleted the network config
                      netId = message.arg1;
                      if (netId == mTargetNetworkId || netId == mLastNetworkId) {
                          // Disconnect and let autojoin reselect a new network
                          sendMessage(CMD_DISCONNECT);
                      }
                      break;
                  case CMD_ENABLE_NETWORK:
                      boolean disableOthers = message.arg2 == 1;
                      netId = message.arg1;
                      if (disableOthers) {
                          // If the app has all the necessary permissions, this will trigger a connect
                          // attempt.
                          ok = connectToUserSelectNetwork(netId, message.sendingUid, false);
                      } else {
                          ok = mWifiConfigManager.enableNetwork(netId, false, message.sendingUid);
                      }
                      if (!ok) {
                          mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                      }
                      replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
                      break;
                  case WifiManager.DISABLE_NETWORK:
                      netId = message.arg1;
                      if (mWifiConfigManager.disableNetwork(netId, message.sendingUid)) {
                          replyToMessage(message, WifiManager.DISABLE_NETWORK_SUCCEEDED);
                          if (netId == mTargetNetworkId || netId == mLastNetworkId) {
                              // Disconnect and let autojoin reselect a new network
                              sendMessage(CMD_DISCONNECT);
                          }
                      } else {
                          loge("Failed to disable network");
                          mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                          replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
                                  WifiManager.ERROR);
                      }
                      break;
                  case CMD_DISABLE_EPHEMERAL_NETWORK:
                      config = mWifiConfigManager.disableEphemeralNetwork((String) message.obj);
                      if (config != null) {
                          if (config.networkId == mTargetNetworkId
                                  || config.networkId == mLastNetworkId) {
                              // Disconnect and let autojoin reselect a new network
                              sendMessage(CMD_DISCONNECT);
                          }
                      }
                      break;
                  case WifiMonitor.SUP_REQUEST_IDENTITY:
                      netId = message.arg2;
                      boolean identitySent = false;
                      // For SIM & AKA/AKA' EAP method Only, get identity from ICC
                      if (mTargetWifiConfiguration != null
                              && mTargetWifiConfiguration.networkId == netId
                              && TelephonyUtil.isSimConfig(mTargetWifiConfiguration)) {
                          // Pair<identity, encrypted identity>
                          Pair<String, String> identityPair =
                                  TelephonyUtil.getSimIdentity(getTelephonyManager(),
                                          new TelephonyUtil(), mTargetWifiConfiguration,
                                          mWifiInjector.getCarrierNetworkConfig());
                          Log.i(TAG, "SUP_REQUEST_IDENTITY: identityPair=" + identityPair);
                          if (identityPair != null && identityPair.first != null) {
                              mWifiNative.simIdentityResponse(mInterfaceName, netId,
                                      identityPair.first, identityPair.second);
                              identitySent = true;
                          } else {
                              Log.e(TAG, "Unable to retrieve identity from Telephony");
                          }
                      }
  
                      if (!identitySent) {
                          // Supplicant lacks credentials to connect to that network, hence black list
                          ssid = (String) message.obj;
                          if (mTargetWifiConfiguration != null && ssid != null
                                  && mTargetWifiConfiguration.SSID != null
                                  && mTargetWifiConfiguration.SSID.equals("\"" + ssid + "\"")) {
                              mWifiConfigManager.updateNetworkSelectionStatus(
                                      mTargetWifiConfiguration.networkId,
                                      WifiConfiguration.NetworkSelectionStatus
                                              .DISABLED_AUTHENTICATION_NO_CREDENTIALS);
                          }
                          mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                                  StaEvent.DISCONNECT_GENERIC);
                          mWifiNative.disconnect(mInterfaceName);
                      }
                      break;
                  case WifiMonitor.SUP_REQUEST_SIM_AUTH:
                      logd("Received SUP_REQUEST_SIM_AUTH");
                      SimAuthRequestData requestData = (SimAuthRequestData) message.obj;
                      if (requestData != null) {
                          if (requestData.protocol == WifiEnterpriseConfig.Eap.SIM) {
                              handleGsmAuthRequest(requestData);
                          } else if (requestData.protocol == WifiEnterpriseConfig.Eap.AKA
                                  || requestData.protocol == WifiEnterpriseConfig.Eap.AKA_PRIME) {
                              handle3GAuthRequest(requestData);
                          }
                      } else {
                          loge("Invalid SIM auth request");
                      }
                      break;
                  case CMD_GET_MATCHING_OSU_PROVIDERS:
                      replyToMessage(message, message.what,
                              mPasspointManager.getMatchingOsuProviders(
                                      (List<ScanResult>) message.obj));
                      break;
                  case CMD_GET_MATCHING_PASSPOINT_CONFIGS_FOR_OSU_PROVIDERS:
                      replyToMessage(message, message.what,
                              mPasspointManager.getMatchingPasspointConfigsForOsuProviders(
                                      (List<OsuProvider>) message.obj));
                      break;
                  case CMD_GET_WIFI_CONFIGS_FOR_PASSPOINT_PROFILES:
                      replyToMessage(message, message.what,
                              mPasspointManager.getWifiConfigsForPasspointProfiles(
                                      (List<String>) message.obj));
  
                      break;
                  case CMD_START_SUBSCRIPTION_PROVISIONING:
                      IProvisioningCallback callback = (IProvisioningCallback) message.obj;
                      OsuProvider provider =
                              (OsuProvider) message.getData().getParcelable(EXTRA_OSU_PROVIDER);
                      int res = mPasspointManager.startSubscriptionProvisioning(
                                      message.arg1, provider, callback) ? 1 : 0;
                      replyToMessage(message, message.what, res);
                      break;
                  case CMD_RECONNECT:
                      WorkSource workSource = (WorkSource) message.obj;
                      mWifiConnectivityManager.forceConnectivityScan(workSource);
                      break;
                  case CMD_REASSOCIATE:
                      mLastConnectAttemptTimestamp = mClock.getWallClockMillis();
                      mWifiNative.reassociate(mInterfaceName);
                      break;
                  case CMD_START_ROAM:
                      mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                      break;
                  case CMD_START_CONNECT:
                      /* connect command coming from auto-join */
                      netId = message.arg1;
                      int uid = message.arg2;
                      bssid = (String) message.obj;
  
                      if (!hasConnectionRequests()) {
                          if (mNetworkAgent == null) {
                              loge("CMD_START_CONNECT but no requests and not connected,"
                                      + " bailing");
                              break;
                          } else if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
                              loge("CMD_START_CONNECT but no requests and connected, but app "
                                      + "does not have sufficient permissions, bailing");
                              break;
                          }
                      }
                      config = mWifiConfigManager.getConfiguredNetworkWithoutMasking(netId);
                      logd("CMD_START_CONNECT sup state "
                              + mSupplicantStateTracker.getSupplicantStateName()
                              + " my state " + getCurrentState().getName()
                              + " nid=" + Integer.toString(netId)
                              + " roam=" + Boolean.toString(mIsAutoRoaming));
                      if (config == null) {
                          loge("CMD_START_CONNECT and no config, bail out...");
                          break;
                      }
                      // Update scorecard while there is still state from existing connection
                      mWifiScoreCard.noteConnectionAttempt(mWifiInfo);
                      mTargetNetworkId = netId;
                      setTargetBssid(config, bssid);
  
                      reportConnectionAttemptStart(config, mTargetRoamBSSID,
                              WifiMetricsProto.ConnectionEvent.ROAM_UNRELATED);
                      if (config.macRandomizationSetting
                              == WifiConfiguration.RANDOMIZATION_PERSISTENT
                              && mConnectedMacRandomzationSupported) {
                          configureRandomizedMacAddress(config);
                      } else {
                          setCurrentMacToFactoryMac(config);
                      }
  
                      String currentMacAddress = mWifiNative.getMacAddress(mInterfaceName);
                      mWifiInfo.setMacAddress(currentMacAddress);
                      Log.i(TAG, "Connecting with " + currentMacAddress + " as the mac address");
  
                      if (config.enterpriseConfig != null
                              && TelephonyUtil.isSimEapMethod(config.enterpriseConfig.getEapMethod())
                              && mWifiInjector.getCarrierNetworkConfig()
                                      .isCarrierEncryptionInfoAvailable()
                              && TextUtils.isEmpty(config.enterpriseConfig.getAnonymousIdentity())) {
                          String anonAtRealm = TelephonyUtil.getAnonymousIdentityWith3GppRealm(
                                  getTelephonyManager());
                          // Use anonymous@<realm> when pseudonym is not available
                          config.enterpriseConfig.setAnonymousIdentity(anonAtRealm);
                      }
  
                      if (mWifiNative.connectToNetwork(mInterfaceName, config)) {
                          mWifiInjector.getWifiLastResortWatchdog().noteStartConnectTime();
                          mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT, config);
                          mLastConnectAttemptTimestamp = mClock.getWallClockMillis();
                          mTargetWifiConfiguration = config;
                          mIsAutoRoaming = false;
                          if (getCurrentState() != mDisconnectedState) {
                              transitionTo(mDisconnectingState);
                          }
                      } else {
                          loge("CMD_START_CONNECT Failed to start connection to network " + config);
                          reportConnectionAttemptEnd(
                                  WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
                                  WifiMetricsProto.ConnectionEvent.HLF_NONE,
                                  WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
                          replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                                  WifiManager.ERROR);
                          break;
                      }
                      break;
                  case CMD_REMOVE_APP_CONFIGURATIONS:
                      removedNetworkIds =
                              mWifiConfigManager.removeNetworksForApp((ApplicationInfo) message.obj);
                      if (removedNetworkIds.contains(mTargetNetworkId)
                              || removedNetworkIds.contains(mLastNetworkId)) {
                          // Disconnect and let autojoin reselect a new network.
                          sendMessage(CMD_DISCONNECT);
                      }
                      break;
                  case CMD_REMOVE_USER_CONFIGURATIONS:
                      removedNetworkIds =
                              mWifiConfigManager.removeNetworksForUser((Integer) message.arg1);
                      if (removedNetworkIds.contains(mTargetNetworkId)
                              || removedNetworkIds.contains(mLastNetworkId)) {
                          // Disconnect and let autojoin reselect a new network.
                          sendMessage(CMD_DISCONNECT);
                      }
                      break;
                  case WifiManager.CONNECT_NETWORK:
                      /**
                       * The connect message can contain a network id passed as arg1 on message or
                       * or a config passed as obj on message.
                       * For a new network, a config is passed to create and connect.
                       * For an existing network, a network id is passed
                       */
                      netId = message.arg1;
                      config = (WifiConfiguration) message.obj;
                      boolean hasCredentialChanged = false;
                      // New network addition.
                      if (config != null) {
                          result = mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
                          if (!result.isSuccess()) {
                              loge("CONNECT_NETWORK adding/updating config=" + config + " failed");
                              mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                              replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                                      WifiManager.ERROR);
                              break;
                          }
                          netId = result.getNetworkId();
                          hasCredentialChanged = result.hasCredentialChanged();
                      }
                      if (!connectToUserSelectNetwork(
                              netId, message.sendingUid, hasCredentialChanged)) {
                          mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                          replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                                  WifiManager.NOT_AUTHORIZED);
                          break;
                      }
                      mWifiMetrics.logStaEvent(StaEvent.TYPE_CONNECT_NETWORK, config);
                      broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
                      replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
                      break;
                  case WifiManager.SAVE_NETWORK:
                      result = saveNetworkConfigAndSendReply(message);
                      netId = result.getNetworkId();
                      if (result.isSuccess() && mWifiInfo.getNetworkId() == netId) {
                          if (result.hasCredentialChanged()) {
                              config = (WifiConfiguration) message.obj;
                              // The network credentials changed and we're connected to this network,
                              // start a new connection with the updated credentials.
                              logi("SAVE_NETWORK credential changed for config=" + config.configKey()
                                      + ", Reconnecting.");
                              startConnectToNetwork(netId, message.sendingUid, SUPPLICANT_BSSID_ANY);
                          } else {
                              if (result.hasProxyChanged()) {
                                  if (mIpClient != null) {
                                      log("Reconfiguring proxy on connection");
                                      mIpClient.setHttpProxy(
                                              getCurrentWifiConfiguration().getHttpProxy());
                                  }
                              }
                              if (result.hasIpChanged()) {
                                  // The current connection configuration was changed
                                  // We switched from DHCP to static or from static to DHCP, or the
                                  // static IP address has changed.
                                  log("Reconfiguring IP on connection");
                                  // TODO(b/36576642): clear addresses and disable IPv6
                                  // to simplify obtainingIpState.
                                  transitionTo(mObtainingIpState);
                              }
                          }
                      }
                      break;
                  case WifiManager.FORGET_NETWORK:
                      if (!deleteNetworkConfigAndSendReply(message, true)) {
                          // Caller was notified of failure, nothing else to do
                          break;
                      }
                      // the network was deleted
                      netId = message.arg1;
                      if (netId == mTargetNetworkId || netId == mLastNetworkId) {
                          // Disconnect and let autojoin reselect a new network
                          sendMessage(CMD_DISCONNECT);
                      }
                      break;
                  case CMD_ASSOCIATED_BSSID:
                      // This is where we can confirm the connection BSSID. Use it to find the
                      // right ScanDetail to populate metrics.
                      String someBssid = (String) message.obj;
                      if (someBssid != null) {
                          // Get the ScanDetail associated with this BSSID.
                          ScanDetailCache scanDetailCache =
                                  mWifiConfigManager.getScanDetailCacheForNetwork(mTargetNetworkId);
                          if (scanDetailCache != null) {
                              mWifiMetrics.setConnectionScanDetail(scanDetailCache.getScanDetail(
                                      someBssid));
                          }
                      }
                      handleStatus = NOT_HANDLED;
                      break;
                  case WifiMonitor.NETWORK_CONNECTION_EVENT:
                      if (mVerboseLoggingEnabled) log("Network connection established");
                      mLastNetworkId = message.arg1;
                      mWifiConfigManager.clearRecentFailureReason(mLastNetworkId);
                      mLastBssid = (String) message.obj;
                      reasonCode = message.arg2;
                      // TODO: This check should not be needed after ClientModeImpl refactor.
                      // Currently, the last connected network configuration is left in
                      // wpa_supplicant, this may result in wpa_supplicant initiating connection
                      // to it after a config store reload. Hence the old network Id lookups may not
                      // work, so disconnect the network and let network selector reselect a new
                      // network.
                      config = getCurrentWifiConfiguration();
                      if (config != null) {
                          mWifiInfo.setBSSID(mLastBssid);
                          mWifiInfo.setNetworkId(mLastNetworkId);
                          mWifiInfo.setMacAddress(mWifiNative.getMacAddress(mInterfaceName));
  
                          ScanDetailCache scanDetailCache =
                                  mWifiConfigManager.getScanDetailCacheForNetwork(config.networkId);
                          if (scanDetailCache != null && mLastBssid != null) {
                              ScanResult scanResult = scanDetailCache.getScanResult(mLastBssid);
                              if (scanResult != null) {
                                  mWifiInfo.setFrequency(scanResult.frequency);
                              }
                          }
                          mWifiConnectivityManager.trackBssid(mLastBssid, true, reasonCode);
  
                          // We need to get the updated pseudonym from supplicant for EAP-SIM/AKA/AKA'
                          if (config.enterpriseConfig != null
                                  && TelephonyUtil.isSimEapMethod(
                                          config.enterpriseConfig.getEapMethod())) {
                              String anonymousIdentity =
                                      mWifiNative.getEapAnonymousIdentity(mInterfaceName);
                              if (!TextUtils.isEmpty(anonymousIdentity)
                                      && !TelephonyUtil
                                      .isAnonymousAtRealmIdentity(anonymousIdentity)) {
                                  String decoratedPseudonym = TelephonyUtil
                                          .decoratePseudonymWith3GppRealm(getTelephonyManager(),
                                                  anonymousIdentity);
                                  if (decoratedPseudonym != null) {
                                      anonymousIdentity = decoratedPseudonym;
                                  }
                                  if (mVerboseLoggingEnabled) {
                                      log("EAP Pseudonym: " + anonymousIdentity);
                                  }
                                  // Save the pseudonym only if it is a real one
                                  config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
                              } else {
                                  // Clear any stored pseudonyms
                                  config.enterpriseConfig.setAnonymousIdentity(null);
                              }
                              mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID);
                          }
                          sendNetworkStateChangeBroadcast(mLastBssid);
                          transitionTo(mObtainingIpState);
                      } else {
                          logw("Connected to unknown networkId " + mLastNetworkId
                                  + ", disconnecting...");
                          sendMessage(CMD_DISCONNECT);
                      }
                      break;
                  case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                      // Calling handleNetworkDisconnect here is redundant because we might already
                      // have called it when leaving L2ConnectedState to go to disconnecting state
                      // or thru other path
                      // We should normally check the mWifiInfo or mLastNetworkId so as to check
                      // if they are valid, and only in this case call handleNEtworkDisconnect,
                      // TODO: this should be fixed for a L MR release
                      // The side effect of calling handleNetworkDisconnect twice is that a bunch of
                      // idempotent commands are executed twice (stopping Dhcp, enabling the SPS mode
                      // at the chip etc...
                      if (mVerboseLoggingEnabled) log("ConnectModeState: Network connection lost ");
                      handleNetworkDisconnect();
                      transitionTo(mDisconnectedState);
                      break;
                  case CMD_QUERY_OSU_ICON:
                      mPasspointManager.queryPasspointIcon(
                              ((Bundle) message.obj).getLong(EXTRA_OSU_ICON_QUERY_BSSID),
                              ((Bundle) message.obj).getString(EXTRA_OSU_ICON_QUERY_FILENAME));
                      break;
                  case CMD_MATCH_PROVIDER_NETWORK:
                      // TODO(b/31065385): Passpoint config management.
                      replyToMessage(message, message.what, 0);
                      break;
                  case CMD_ADD_OR_UPDATE_PASSPOINT_CONFIG:
                      Bundle bundle = (Bundle) message.obj;
                      PasspointConfiguration passpointConfig = bundle.getParcelable(
                              EXTRA_PASSPOINT_CONFIGURATION);
                      if (mPasspointManager.addOrUpdateProvider(passpointConfig,
                              bundle.getInt(EXTRA_UID),
                              bundle.getString(EXTRA_PACKAGE_NAME))) {
                          String fqdn = passpointConfig.getHomeSp().getFqdn();
                          if (isProviderOwnedNetwork(mTargetNetworkId, fqdn)
                                  || isProviderOwnedNetwork(mLastNetworkId, fqdn)) {
                              logd("Disconnect from current network since its provider is updated");
                              sendMessage(CMD_DISCONNECT);
                          }
                          replyToMessage(message, message.what, SUCCESS);
                      } else {
                          replyToMessage(message, message.what, FAILURE);
                      }
                      break;
                  case CMD_REMOVE_PASSPOINT_CONFIG:
                      String fqdn = (String) message.obj;
                      if (mPasspointManager.removeProvider(
                              message.sendingUid, message.arg1 == 1, fqdn)) {
                          if (isProviderOwnedNetwork(mTargetNetworkId, fqdn)
                                  || isProviderOwnedNetwork(mLastNetworkId, fqdn)) {
                              logd("Disconnect from current network since its provider is removed");
                              sendMessage(CMD_DISCONNECT);
                          }
                          mWifiConfigManager.removePasspointConfiguredNetwork(fqdn);
                          replyToMessage(message, message.what, SUCCESS);
                      } else {
                          replyToMessage(message, message.what, FAILURE);
                      }
                      break;
                  case CMD_GET_ALL_MATCHING_FQDNS_FOR_SCAN_RESULTS:
                      replyToMessage(message, message.what,
                              mPasspointManager.getAllMatchingFqdnsForScanResults(
                                      (List<ScanResult>) message.obj));
                      break;
                  case CMD_TARGET_BSSID:
                      // Trying to associate to this BSSID
                      if (message.obj != null) {
                          mTargetRoamBSSID = (String) message.obj;
                      }
                      break;
                  case CMD_GET_LINK_LAYER_STATS:
                      WifiLinkLayerStats stats = getWifiLinkLayerStats();
                      replyToMessage(message, message.what, stats);
                      break;
                  case CMD_RESET_SIM_NETWORKS:
                      log("resetting EAP-SIM/AKA/AKA' networks since SIM was changed");
                      boolean simPresent = message.arg1 == 1;
                      if (!simPresent) {
                          mPasspointManager.removeEphemeralProviders();
                          mWifiConfigManager.resetSimNetworks();
                      }
                      break;
                  case CMD_BLUETOOTH_ADAPTER_STATE_CHANGE:
                      mBluetoothConnectionActive = (message.arg1
                              != BluetoothAdapter.STATE_DISCONNECTED);
                      mWifiNative.setBluetoothCoexistenceScanMode(
                              mInterfaceName, mBluetoothConnectionActive);
                      break;
                  case CMD_SET_SUSPEND_OPT_ENABLED:
                      if (message.arg1 == 1) {
                          setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, true);
                          if (message.arg2 == 1) {
                              mSuspendWakeLock.release();
                          }
                      } else {
                          setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, false);
                      }
                      break;
                  case CMD_SET_HIGH_PERF_MODE:
                      if (message.arg1 == 1) {
                          setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, false);
                      } else {
                          setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, true);
                      }
                      break;
                  case CMD_ENABLE_TDLS:
                      if (message.obj != null) {
                          String remoteAddress = (String) message.obj;
                          boolean enable = (message.arg1 == 1);
                          mWifiNative.startTdls(mInterfaceName, remoteAddress, enable);
                      }
                      break;
                  case WifiMonitor.ANQP_DONE_EVENT:
                      // TODO(zqiu): remove this when switch over to wificond for ANQP requests.
                      mPasspointManager.notifyANQPDone((AnqpEvent) message.obj);
                      break;
                  case CMD_STOP_IP_PACKET_OFFLOAD: {
                      int slot = message.arg1;
                      int ret = stopWifiIPPacketOffload(slot);
                      if (mNetworkAgent != null) {
                          mNetworkAgent.onSocketKeepaliveEvent(slot, ret);
                      }
                      break;
                  }
                  case WifiMonitor.RX_HS20_ANQP_ICON_EVENT:
                      // TODO(zqiu): remove this when switch over to wificond for icon requests.
                      mPasspointManager.notifyIconDone((IconEvent) message.obj);
                      break;
                  case WifiMonitor.HS20_REMEDIATION_EVENT:
                      // TODO(zqiu): remove this when switch over to wificond for WNM frames
                      // monitoring.
                      mPasspointManager.receivedWnmFrame((WnmData) message.obj);
                      break;
                  case CMD_CONFIG_ND_OFFLOAD:
                      final boolean enabled = (message.arg1 > 0);
                      mWifiNative.configureNeighborDiscoveryOffload(mInterfaceName, enabled);
                      break;
                  case CMD_ENABLE_WIFI_CONNECTIVITY_MANAGER:
                      mWifiConnectivityManager.enable(message.arg1 == 1 ? true : false);
                      break;
                  default:
                      handleStatus = NOT_HANDLED;
                      break;
              }
  
              if (handleStatus == HANDLED) {
                  logStateAndMessage(message, this);
              }
  
              return handleStatus;
          }
      }
  
      private WifiNetworkAgentSpecifier createNetworkAgentSpecifier(
              @NonNull WifiConfiguration currentWifiConfiguration, @Nullable String currentBssid,
              int specificRequestUid, @NonNull String specificRequestPackageName) {
          currentWifiConfiguration.BSSID = currentBssid;
          WifiNetworkAgentSpecifier wns =
                  new WifiNetworkAgentSpecifier(currentWifiConfiguration, specificRequestUid,
                          specificRequestPackageName);
          return wns;
      }
  
      private NetworkCapabilities getCapabilities(WifiConfiguration currentWifiConfiguration) {
          final NetworkCapabilities result = new NetworkCapabilities(mNetworkCapabilitiesFilter);
          // MatchAllNetworkSpecifier set in the mNetworkCapabilitiesFilter should never be set in the
          // agent's specifier.
          result.setNetworkSpecifier(null);
          if (currentWifiConfiguration == null) {
              return result;
          }
  
          if (!mWifiInfo.isTrusted()) {
              result.removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
          } else {
              result.addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
          }
  
          if (!WifiConfiguration.isMetered(currentWifiConfiguration, mWifiInfo)) {
              result.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
          } else {
              result.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
          }
  
          if (mWifiInfo.getRssi() != WifiInfo.INVALID_RSSI) {
              result.setSignalStrength(mWifiInfo.getRssi());
          } else {
              result.setSignalStrength(NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED);
          }
  
          if (currentWifiConfiguration.osu) {
              result.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
          }
  
          if (!mWifiInfo.getSSID().equals(WifiSsid.NONE)) {
              result.setSSID(mWifiInfo.getSSID());
          } else {
              result.setSSID(null);
          }
          Pair<Integer, String> specificRequestUidAndPackageName =
                  mNetworkFactory.getSpecificNetworkRequestUidAndPackageName(
                          currentWifiConfiguration);
          // There is an active specific request.
          if (specificRequestUidAndPackageName.first != Process.INVALID_UID) {
              // Remove internet capability.
              result.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
          }
          // Fill up the network agent specifier for this connection.
          result.setNetworkSpecifier(
                  createNetworkAgentSpecifier(
                          currentWifiConfiguration, getCurrentBSSID(),
                          specificRequestUidAndPackageName.first,
                          specificRequestUidAndPackageName.second));
          return result;
      }
  
      /**
       * Method to update network capabilities from the current WifiConfiguration.
       */
      public void updateCapabilities() {
          updateCapabilities(getCurrentWifiConfiguration());
      }
  
      private void updateCapabilities(WifiConfiguration currentWifiConfiguration) {
          if (mNetworkAgent == null) {
              return;
          }
          mNetworkAgent.sendNetworkCapabilities(getCapabilities(currentWifiConfiguration));
      }
  
      /**
       * Checks if the given network |networkdId| is provided by the given Passpoint provider with
       * |providerFqdn|.
       *
       * @param networkId The ID of the network to check
       * @param providerFqdn The FQDN of the Passpoint provider
       * @return true if the given network is provided by the given Passpoint provider
       */
      private boolean isProviderOwnedNetwork(int networkId, String providerFqdn) {
          if (networkId == WifiConfiguration.INVALID_NETWORK_ID) {
              return false;
          }
          WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(networkId);
          if (config == null) {
              return false;
          }
          return TextUtils.equals(config.FQDN, providerFqdn);
      }
  
      private void handleEapAuthFailure(int networkId, int errorCode) {
          WifiConfiguration targetedNetwork =
                  mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
          if (targetedNetwork != null) {
              switch (targetedNetwork.enterpriseConfig.getEapMethod()) {
                  case WifiEnterpriseConfig.Eap.SIM:
                  case WifiEnterpriseConfig.Eap.AKA:
                  case WifiEnterpriseConfig.Eap.AKA_PRIME:
                      if (errorCode == WifiNative.EAP_SIM_VENDOR_SPECIFIC_CERT_EXPIRED) {
                          getTelephonyManager()
                                  .createForSubscriptionId(
                                          SubscriptionManager.getDefaultDataSubscriptionId())
                                  .resetCarrierKeysForImsiEncryption();
                      }
                      break;
  
                  default:
                      // Do Nothing
              }
          }
      }
  
      private class WifiNetworkAgent extends NetworkAgent {
          WifiNetworkAgent(Looper l, Context c, String tag, NetworkInfo ni,
                  NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
              super(l, c, tag, ni, nc, lp, score, misc);
          }
          private int mLastNetworkStatus = -1; // To detect when the status really changes
  
          @Override
          protected void unwanted() {
              // Ignore if we're not the current networkAgent.
              if (this != mNetworkAgent) return;
              if (mVerboseLoggingEnabled) {
                  log("WifiNetworkAgent -> Wifi unwanted score " + Integer.toString(mWifiInfo.score));
              }
              unwantedNetwork(NETWORK_STATUS_UNWANTED_DISCONNECT);
          }
  
          @Override
          protected void networkStatus(int status, String redirectUrl) {
              if (this != mNetworkAgent) return;
              if (status == mLastNetworkStatus) return;
              mLastNetworkStatus = status;
              if (status == NetworkAgent.INVALID_NETWORK) {
                  if (mVerboseLoggingEnabled) {
                      log("WifiNetworkAgent -> Wifi networkStatus invalid, score="
                              + Integer.toString(mWifiInfo.score));
                  }
                  unwantedNetwork(NETWORK_STATUS_UNWANTED_VALIDATION_FAILED);
              } else if (status == NetworkAgent.VALID_NETWORK) {
                  if (mVerboseLoggingEnabled) {
                      log("WifiNetworkAgent -> Wifi networkStatus valid, score= "
                              + Integer.toString(mWifiInfo.score));
                  }
                  mWifiMetrics.logStaEvent(StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK);
                  doNetworkStatus(status);
              }
          }
  
          @Override
          protected void saveAcceptUnvalidated(boolean accept) {
              if (this != mNetworkAgent) return;
              ClientModeImpl.this.sendMessage(CMD_ACCEPT_UNVALIDATED, accept ? 1 : 0);
          }
  
          @Override
          protected void startSocketKeepalive(Message msg) {
              ClientModeImpl.this.sendMessage(
                      CMD_START_IP_PACKET_OFFLOAD, msg.arg1, msg.arg2, msg.obj);
          }
  
          @Override
          protected void stopSocketKeepalive(Message msg) {
              ClientModeImpl.this.sendMessage(
                      CMD_STOP_IP_PACKET_OFFLOAD, msg.arg1, msg.arg2, msg.obj);
          }
  
          @Override
          protected void addKeepalivePacketFilter(Message msg) {
              ClientModeImpl.this.sendMessage(
                      CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, msg.arg1, msg.arg2, msg.obj);
          }
  
          @Override
          protected void removeKeepalivePacketFilter(Message msg) {
              ClientModeImpl.this.sendMessage(
                      CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF, msg.arg1, msg.arg2, msg.obj);
          }
  
          @Override
          protected void setSignalStrengthThresholds(int[] thresholds) {
              // 0. If there are no thresholds, or if the thresholds are invalid,
              //    stop RSSI monitoring.
              // 1. Tell the hardware to start RSSI monitoring here, possibly adding MIN_VALUE and
              //    MAX_VALUE at the start/end of the thresholds array if necessary.
              // 2. Ensure that when the hardware event fires, we fetch the RSSI from the hardware
              //    event, call mWifiInfo.setRssi() with it, and call updateCapabilities(), and then
              //    re-arm the hardware event. This needs to be done on the state machine thread to
              //    avoid race conditions. The RSSI used to re-arm the event (and perhaps also the one
              //    sent in the NetworkCapabilities) must be the one received from the hardware event
              //    received, or we might skip callbacks.
              // 3. Ensure that when we disconnect, RSSI monitoring is stopped.
              log("Received signal strength thresholds: " + Arrays.toString(thresholds));
              if (thresholds.length == 0) {
                  ClientModeImpl.this.sendMessage(CMD_STOP_RSSI_MONITORING_OFFLOAD,
                          mWifiInfo.getRssi());
                  return;
              }
              int [] rssiVals = Arrays.copyOf(thresholds, thresholds.length + 2);
              rssiVals[rssiVals.length - 2] = Byte.MIN_VALUE;
              rssiVals[rssiVals.length - 1] = Byte.MAX_VALUE;
              Arrays.sort(rssiVals);
              byte[] rssiRange = new byte[rssiVals.length];
              for (int i = 0; i < rssiVals.length; i++) {
                  int val = rssiVals[i];
                  if (val <= Byte.MAX_VALUE && val >= Byte.MIN_VALUE) {
                      rssiRange[i] = (byte) val;
                  } else {
                      Log.e(TAG, "Illegal value " + val + " for RSSI thresholds: "
                              + Arrays.toString(rssiVals));
                      ClientModeImpl.this.sendMessage(CMD_STOP_RSSI_MONITORING_OFFLOAD,
                              mWifiInfo.getRssi());
                      return;
                  }
              }
              // TODO: Do we quash rssi values in this sorted array which are very close?
              mRssiRanges = rssiRange;
              ClientModeImpl.this.sendMessage(CMD_START_RSSI_MONITORING_OFFLOAD,
                      mWifiInfo.getRssi());
          }
  
          @Override
          protected void preventAutomaticReconnect() {
              if (this != mNetworkAgent) return;
              unwantedNetwork(NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN);
          }
      }
  
      void unwantedNetwork(int reason) {
          sendMessage(CMD_UNWANTED_NETWORK, reason);
      }
  
      void doNetworkStatus(int status) {
          sendMessage(CMD_NETWORK_STATUS, status);
      }
  
      // rfc4186 & rfc4187:
      // create Permanent Identity base on IMSI,
      // identity = usernam@realm
      // with username = prefix | IMSI
      // and realm is derived MMC/MNC tuple according 3GGP spec(TS23.003)
      private String buildIdentity(int eapMethod, String imsi, String mccMnc) {
          String mcc;
          String mnc;
          String prefix;
  
          if (imsi == null || imsi.isEmpty()) {
              return "";
          }
  
          if (eapMethod == WifiEnterpriseConfig.Eap.SIM) {
              prefix = "1";
          } else if (eapMethod == WifiEnterpriseConfig.Eap.AKA) {
              prefix = "0";
          } else if (eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME) {
              prefix = "6";
          } else {
              // not a valid EapMethod
              return "";
          }
  
          /* extract mcc & mnc from mccMnc */
          if (mccMnc != null && !mccMnc.isEmpty()) {
              mcc = mccMnc.substring(0, 3);
              mnc = mccMnc.substring(3);
              if (mnc.length() == 2) {
                  mnc = "0" + mnc;
              }
          } else {
              // extract mcc & mnc from IMSI, assume mnc size is 3
              mcc = imsi.substring(0, 3);
              mnc = imsi.substring(3, 6);
          }
  
          return prefix + imsi + "@wlan.mnc" + mnc + ".mcc" + mcc + ".3gppnetwork.org";
      }
  
      class L2ConnectedState extends State {
          class RssiEventHandler implements WifiNative.WifiRssiEventHandler {
              @Override
              public void onRssiThresholdBreached(byte curRssi) {
                  if (mVerboseLoggingEnabled) {
                      Log.e(TAG, "onRssiThresholdBreach event. Cur Rssi = " + curRssi);
                  }
                  sendMessage(CMD_RSSI_THRESHOLD_BREACHED, curRssi);
              }
          }
  
          RssiEventHandler mRssiEventHandler = new RssiEventHandler();
  
          @Override
          public void enter() {
              mRssiPollToken++;
              if (mEnableRssiPolling) {
                  mLinkProbeManager.resetOnNewConnection();
                  sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0);
              }
              if (mNetworkAgent != null) {
                  loge("Have NetworkAgent when entering L2Connected");
                  setNetworkDetailedState(DetailedState.DISCONNECTED);
              }
              setNetworkDetailedState(DetailedState.CONNECTING);
  
              final NetworkCapabilities nc = getCapabilities(getCurrentWifiConfiguration());
              synchronized (mNetworkAgentLock) {
                  mNetworkAgent = new WifiNetworkAgent(getHandler().getLooper(), mContext,
                      "WifiNetworkAgent", mNetworkInfo, nc, mLinkProperties, 60, mNetworkMisc);
              }
  
              // We must clear the config BSSID, as the wifi chipset may decide to roam
              // from this point on and having the BSSID specified in the network block would
              // cause the roam to faile and the device to disconnect
              clearTargetBssid("L2ConnectedState");
              mCountryCode.setReadyForChange(false);
              mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
              mWifiScoreCard.noteNetworkAgentCreated(mWifiInfo, mNetworkAgent.netId);
          }
  
          @Override
          public void exit() {
              if (mIpClient != null) {
                  mIpClient.stop();
              }
  
              // This is handled by receiving a NETWORK_DISCONNECTION_EVENT in ConnectModeState
              // Bug: 15347363
              // For paranoia's sake, call handleNetworkDisconnect
              // only if BSSID is null or last networkId
              // is not invalid.
              if (mVerboseLoggingEnabled) {
                  StringBuilder sb = new StringBuilder();
                  sb.append("leaving L2ConnectedState state nid=" + Integer.toString(mLastNetworkId));
                  if (mLastBssid != null) {
                      sb.append(" ").append(mLastBssid);
                  }
              }
              if (mLastBssid != null || mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
                  handleNetworkDisconnect();
              }
              mCountryCode.setReadyForChange(true);
              mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_DISCONNECTED);
              mWifiStateTracker.updateState(WifiStateTracker.DISCONNECTED);
              //Inform WifiLockManager
              WifiLockManager wifiLockManager = mWifiInjector.getWifiLockManager();
              wifiLockManager.updateWifiClientConnected(false);
          }
  
          @Override
          public boolean processMessage(Message message) {
              boolean handleStatus = HANDLED;
  
              switch (message.what) {
                  case CMD_PRE_DHCP_ACTION:
                      handlePreDhcpSetup();
                      break;
                  case CMD_PRE_DHCP_ACTION_COMPLETE:
                      if (mIpClient != null) {
                          mIpClient.completedPreDhcpAction();
                      }
                      break;
                  case CMD_POST_DHCP_ACTION:
                      handlePostDhcpSetup();
                      // We advance to mConnectedState because IpClient will also send a
                      // CMD_IPV4_PROVISIONING_SUCCESS message, which calls handleIPv4Success(),
                      // which calls updateLinkProperties, which then sends
                      // CMD_IP_CONFIGURATION_SUCCESSFUL.
                      //
                      // In the event of failure, we transition to mDisconnectingState
                      // similarly--via messages sent back from IpClient.
                      break;
                  case CMD_IPV4_PROVISIONING_SUCCESS: {
                      handleIPv4Success((DhcpResults) message.obj);
                      sendNetworkStateChangeBroadcast(mLastBssid);
                      break;
                  }
                  case CMD_IPV4_PROVISIONING_FAILURE: {
                      handleIPv4Failure();
                      mWifiInjector.getWifiLastResortWatchdog()
                              .noteConnectionFailureAndTriggerIfNeeded(
                                      getTargetSsid(), mTargetRoamBSSID,
                                      WifiLastResortWatchdog.FAILURE_CODE_DHCP);
                      break;
                  }
                  case CMD_IP_CONFIGURATION_SUCCESSFUL:
                      if (getCurrentWifiConfiguration() == null) {
                          // The current config may have been removed while we were connecting,
                          // trigger a disconnect to clear up state.
                          reportConnectionAttemptEnd(
                                  WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION,
                                  WifiMetricsProto.ConnectionEvent.HLF_NONE,
                                  WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
                          mWifiNative.disconnect(mInterfaceName);
                          transitionTo(mDisconnectingState);
                      } else {
                          handleSuccessfulIpConfiguration();
                          sendConnectedState();
                          transitionTo(mConnectedState);
                      }
                      break;
                  case CMD_IP_CONFIGURATION_LOST:
                      // Get Link layer stats so that we get fresh tx packet counters.
                      getWifiLinkLayerStats();
                      handleIpConfigurationLost();
                      reportConnectionAttemptEnd(
                              WifiMetrics.ConnectionEvent.FAILURE_DHCP,
                              WifiMetricsProto.ConnectionEvent.HLF_NONE,
                              WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
                      mWifiInjector.getWifiLastResortWatchdog()
                              .noteConnectionFailureAndTriggerIfNeeded(
                                      getTargetSsid(), mTargetRoamBSSID,
                                      WifiLastResortWatchdog.FAILURE_CODE_DHCP);
                      transitionTo(mDisconnectingState);
                      break;
                  case CMD_IP_REACHABILITY_LOST:
                      if (mVerboseLoggingEnabled && message.obj != null) log((String) message.obj);
                      mWifiDiagnostics.captureBugReportData(
                              WifiDiagnostics.REPORT_REASON_REACHABILITY_LOST);
                      mWifiMetrics.logWifiIsUnusableEvent(
                              WifiIsUnusableEvent.TYPE_IP_REACHABILITY_LOST);
                      mWifiMetrics.addToWifiUsabilityStatsList(WifiUsabilityStats.LABEL_BAD,
                              WifiUsabilityStats.TYPE_IP_REACHABILITY_LOST, -1);
                      if (mIpReachabilityDisconnectEnabled) {
                          handleIpReachabilityLost();
                          transitionTo(mDisconnectingState);
                      } else {
                          logd("CMD_IP_REACHABILITY_LOST but disconnect disabled -- ignore");
                      }
                      break;
                  case CMD_DISCONNECT:
                      mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                              StaEvent.DISCONNECT_GENERIC);
                      mWifiNative.disconnect(mInterfaceName);
                      transitionTo(mDisconnectingState);
                      break;
                  case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
                      if (message.arg1 == 1) {
                          mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                                  StaEvent.DISCONNECT_P2P_DISCONNECT_WIFI_REQUEST);
                          mWifiNative.disconnect(mInterfaceName);
                          mTemporarilyDisconnectWifi = true;
                          transitionTo(mDisconnectingState);
                      }
                      break;
                      /* Ignore connection to same network */
                  case WifiManager.CONNECT_NETWORK:
                      int netId = message.arg1;
                      if (mWifiInfo.getNetworkId() == netId) {
                          replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
                          break;
                      }
                      handleStatus = NOT_HANDLED;
                      break;
                  case WifiMonitor.NETWORK_CONNECTION_EVENT:
                      mWifiInfo.setBSSID((String) message.obj);
                      mLastNetworkId = message.arg1;
                      mWifiInfo.setNetworkId(mLastNetworkId);
                      mWifiInfo.setMacAddress(mWifiNative.getMacAddress(mInterfaceName));
                      if (!mLastBssid.equals(message.obj)) {
                          mLastBssid = (String) message.obj;
                          sendNetworkStateChangeBroadcast(mLastBssid);
                      }
                      break;
                  case CMD_ONESHOT_RSSI_POLL:
                      if (!mEnableRssiPolling) {
                          updateLinkLayerStatsRssiAndScoreReportInternal();
                      }
                      break;
                  case CMD_RSSI_POLL:
                      if (message.arg1 == mRssiPollToken) {
                          WifiLinkLayerStats stats = updateLinkLayerStatsRssiAndScoreReportInternal();
                          mWifiMetrics.updateWifiUsabilityStatsEntries(mWifiInfo, stats);
                          if (mWifiScoreReport.shouldCheckIpLayer()) {
                              if (mIpClient != null) {
                                  mIpClient.confirmConfiguration();
                              }
                              mWifiScoreReport.noteIpCheck();
                          }
                          int statusDataStall = mWifiDataStall.checkForDataStall(
                                  mLastLinkLayerStats, stats, mWifiInfo);
                          if (mDataStallTriggerTimeMs == -1
                                  && statusDataStall != WifiIsUnusableEvent.TYPE_UNKNOWN) {
                              mDataStallTriggerTimeMs = mClock.getElapsedSinceBootMillis();
                              mLastStatusDataStall = statusDataStall;
                          }
                          if (mDataStallTriggerTimeMs != -1) {
                              long elapsedTime =  mClock.getElapsedSinceBootMillis()
                                      - mDataStallTriggerTimeMs;
                              if (elapsedTime >= DURATION_TO_WAIT_ADD_STATS_AFTER_DATA_STALL_MS) {
                                  mDataStallTriggerTimeMs = -1;
                                  mWifiMetrics.addToWifiUsabilityStatsList(
                                          WifiUsabilityStats.LABEL_BAD,
                                          convertToUsabilityStatsTriggerType(mLastStatusDataStall),
                                          -1);
                                  mLastStatusDataStall = WifiIsUnusableEvent.TYPE_UNKNOWN;
                              }
                          }
                          mWifiMetrics.incrementWifiLinkLayerUsageStats(stats);
                          mLastLinkLayerStats = stats;
                          mWifiScoreCard.noteSignalPoll(mWifiInfo);
                          mLinkProbeManager.updateConnectionStats(
                                  mWifiInfo, mInterfaceName);
                          sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
                                  mPollRssiIntervalMsecs);
                          if (mVerboseLoggingEnabled) sendRssiChangeBroadcast(mWifiInfo.getRssi());
                          mWifiTrafficPoller.notifyOnDataActivity(mWifiInfo.txSuccess,
                                  mWifiInfo.rxSuccess);
                      } else {
                          // Polling has completed
                      }
                      break;
                  case CMD_ENABLE_RSSI_POLL:
                      cleanWifiScore();
                      mEnableRssiPolling = (message.arg1 == 1);
                      mRssiPollToken++;
                      if (mEnableRssiPolling) {
                          // First poll
                          mLastSignalLevel = -1;
                          mLinkProbeManager.resetOnScreenTurnedOn();
                          fetchRssiLinkSpeedAndFrequencyNative();
                          sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
                                  mPollRssiIntervalMsecs);
                      }
                      break;
                  case WifiManager.RSSI_PKTCNT_FETCH:
                      RssiPacketCountInfo info = new RssiPacketCountInfo();
                      fetchRssiLinkSpeedAndFrequencyNative();
                      info.rssi = mWifiInfo.getRssi();
                      WifiNative.TxPacketCounters counters =
                              mWifiNative.getTxPacketCounters(mInterfaceName);
                      if (counters != null) {
                          info.txgood = counters.txSucceeded;
                          info.txbad = counters.txFailed;
                          replyToMessage(message, WifiManager.RSSI_PKTCNT_FETCH_SUCCEEDED, info);
                      } else {
                          replyToMessage(message,
                                  WifiManager.RSSI_PKTCNT_FETCH_FAILED, WifiManager.ERROR);
                      }
                      break;
                  case CMD_ASSOCIATED_BSSID:
                      if ((String) message.obj == null) {
                          logw("Associated command w/o BSSID");
                          break;
                      }
                      mLastBssid = (String) message.obj;
                      if (mLastBssid != null && (mWifiInfo.getBSSID() == null
                              || !mLastBssid.equals(mWifiInfo.getBSSID()))) {
                          mWifiInfo.setBSSID(mLastBssid);
                          WifiConfiguration config = getCurrentWifiConfiguration();
                          if (config != null) {
                              ScanDetailCache scanDetailCache = mWifiConfigManager
                                      .getScanDetailCacheForNetwork(config.networkId);
                              if (scanDetailCache != null) {
                                  ScanResult scanResult = scanDetailCache.getScanResult(mLastBssid);
                                  if (scanResult != null) {
                                      mWifiInfo.setFrequency(scanResult.frequency);
                                  }
                              }
                          }
                          sendNetworkStateChangeBroadcast(mLastBssid);
                      }
                      break;
                  case CMD_START_RSSI_MONITORING_OFFLOAD:
                  case CMD_RSSI_THRESHOLD_BREACHED:
                      byte currRssi = (byte) message.arg1;
                      processRssiThreshold(currRssi, message.what, mRssiEventHandler);
                      break;
                  case CMD_STOP_RSSI_MONITORING_OFFLOAD:
                      stopRssiMonitoringOffload();
                      break;
                  case CMD_RECONNECT:
                      log(" Ignore CMD_RECONNECT request because wifi is already connected");
                      break;
                  case CMD_RESET_SIM_NETWORKS:
                      if (message.arg1 == 0 // sim was removed
                              && mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
                          WifiConfiguration config =
                                  mWifiConfigManager.getConfiguredNetwork(mLastNetworkId);
                          if (TelephonyUtil.isSimConfig(config)) {
                              mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                                      StaEvent.DISCONNECT_RESET_SIM_NETWORKS);
                              // TODO(b/132385576): STA may immediately connect back to the network
                              //  that we just disconnected from
                              mWifiNative.disconnect(mInterfaceName);
                              transitionTo(mDisconnectingState);
                          }
                      }
                      /* allow parent state to reset data for other networks */
                      handleStatus = NOT_HANDLED;
                      break;
                  case CMD_START_IP_PACKET_OFFLOAD: {
                      int slot = message.arg1;
                      int intervalSeconds = message.arg2;
                      KeepalivePacketData pkt = (KeepalivePacketData) message.obj;
                      int result = startWifiIPPacketOffload(slot, pkt, intervalSeconds);
                      if (mNetworkAgent != null) {
                          mNetworkAgent.onSocketKeepaliveEvent(slot, result);
                      }
                      break;
                  }
                  case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF: {
                      if (mIpClient != null) {
                          final int slot = message.arg1;
                          if (message.obj instanceof NattKeepalivePacketData) {
                              final NattKeepalivePacketData pkt =
                                      (NattKeepalivePacketData) message.obj;
                              mIpClient.addKeepalivePacketFilter(slot, pkt);
                          } else if (message.obj instanceof TcpKeepalivePacketData) {
                              final TcpKeepalivePacketData pkt =
                                      (TcpKeepalivePacketData) message.obj;
                              mIpClient.addKeepalivePacketFilter(slot, pkt);
                          }
                      }
                      break;
                  }
                  case CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF: {
                      if (mIpClient != null) {
                          mIpClient.removeKeepalivePacketFilter(message.arg1);
                      }
                      break;
                  }
                  default:
                      handleStatus = NOT_HANDLED;
                      break;
              }
  
              if (handleStatus == HANDLED) {
                  logStateAndMessage(message, this);
              }
  
              return handleStatus;
          }
  
          /**
           * Fetches link stats and updates Wifi Score Report.
           */
          private WifiLinkLayerStats updateLinkLayerStatsRssiAndScoreReportInternal() {
              WifiLinkLayerStats stats = getWifiLinkLayerStats();
              // Get Info and continue polling
              fetchRssiLinkSpeedAndFrequencyNative();
              // Send the update score to network agent.
              mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mWifiMetrics);
              return stats;
          }
      }
  
      /**
       * Fetches link stats and updates Wifi Score Report.
       */
      public void updateLinkLayerStatsRssiAndScoreReport() {
          sendMessage(CMD_ONESHOT_RSSI_POLL);
      }
  
      private static int convertToUsabilityStatsTriggerType(int unusableEventTriggerType) {
          int triggerType;
          switch (unusableEventTriggerType) {
              case WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX:
                  triggerType = WifiUsabilityStats.TYPE_DATA_STALL_BAD_TX;
                  break;
              case WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX:
                  triggerType = WifiUsabilityStats.TYPE_DATA_STALL_TX_WITHOUT_RX;
                  break;
              case WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH:
                  triggerType = WifiUsabilityStats.TYPE_DATA_STALL_BOTH;
                  break;
              case WifiIsUnusableEvent.TYPE_FIRMWARE_ALERT:
                  triggerType = WifiUsabilityStats.TYPE_FIRMWARE_ALERT;
                  break;
              case WifiIsUnusableEvent.TYPE_IP_REACHABILITY_LOST:
                  triggerType = WifiUsabilityStats.TYPE_IP_REACHABILITY_LOST;
                  break;
              default:
                  triggerType = WifiUsabilityStats.TYPE_UNKNOWN;
                  Log.e(TAG, "Unknown WifiIsUnusableEvent: " + unusableEventTriggerType);
          }
          return triggerType;
      }
  
      class ObtainingIpState extends State {
          @Override
          public void enter() {
              final WifiConfiguration currentConfig = getCurrentWifiConfiguration();
              final boolean isUsingStaticIp =
                      (currentConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC);
              if (mVerboseLoggingEnabled) {
                  final String key = currentConfig.configKey();
                  log("enter ObtainingIpState netId=" + Integer.toString(mLastNetworkId)
                          + " " + key + " "
                          + " roam=" + mIsAutoRoaming
                          + " static=" + isUsingStaticIp);
              }
  
              // Send event to CM & network change broadcast
              setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);
  
              // We must clear the config BSSID, as the wifi chipset may decide to roam
              // from this point on and having the BSSID specified in the network block would
              // cause the roam to fail and the device to disconnect.
              clearTargetBssid("ObtainingIpAddress");
  
              // Stop IpClient in case we're switching from DHCP to static
              // configuration or vice versa.
              //
              // TODO: Only ever enter this state the first time we connect to a
              // network, never on switching between static configuration and
              // DHCP. When we transition from static configuration to DHCP in
              // particular, we must tell ConnectivityService that we're
              // disconnected, because DHCP might take a long time during which
              // connectivity APIs such as getActiveNetworkInfo should not return
              // CONNECTED.
              stopIpClient();
  
              if (mIpClient != null) {
                  mIpClient.setHttpProxy(currentConfig.getHttpProxy());
                  if (!TextUtils.isEmpty(mTcpBufferSizes)) {
                      mIpClient.setTcpBufferSizes(mTcpBufferSizes);
                  }
              }
              final ProvisioningConfiguration prov;
              if (!isUsingStaticIp) {
                  prov = new ProvisioningConfiguration.Builder()
                              .withPreDhcpAction()
                              .withApfCapabilities(mWifiNative.getApfCapabilities(mInterfaceName))
                              .withNetwork(getCurrentNetwork())
                              .withDisplayName(currentConfig.SSID)
                              .build();
              } else {
                  StaticIpConfiguration staticIpConfig = currentConfig.getStaticIpConfiguration();
                  prov = new ProvisioningConfiguration.Builder()
                              .withStaticConfiguration(staticIpConfig)
                              .withApfCapabilities(mWifiNative.getApfCapabilities(mInterfaceName))
                              .withNetwork(getCurrentNetwork())
                              .withDisplayName(currentConfig.SSID)
                              .build();
              }
              if (mIpClient != null) {
                  mIpClient.startProvisioning(prov);
              }
              // Get Link layer stats so as we get fresh tx packet counters
              getWifiLinkLayerStats();
          }
  
          @Override
          public boolean processMessage(Message message) {
              boolean handleStatus = HANDLED;
  
              switch(message.what) {
                  case CMD_START_CONNECT:
                  case CMD_START_ROAM:
                      mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                      break;
                  case WifiManager.SAVE_NETWORK:
                      mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
                      deferMessage(message);
                      break;
                  case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                      reportConnectionAttemptEnd(
                              WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION,
                              WifiMetricsProto.ConnectionEvent.HLF_NONE,
                              WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
                      handleStatus = NOT_HANDLED;
                      break;
                  case CMD_SET_HIGH_PERF_MODE:
                      mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED;
                      deferMessage(message);
                      break;
                  default:
                      handleStatus = NOT_HANDLED;
                      break;
              }
  
              if (handleStatus == HANDLED) {
                  logStateAndMessage(message, this);
              }
              return handleStatus;
          }
      }
  
      /**
       * Helper function to check if we need to invoke
       * {@link NetworkAgent#explicitlySelected(boolean, boolean)} to indicate that we connected to a
       * network which the user just chose
       * (i.e less than {@link #LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS) before).
       */
      @VisibleForTesting
      public boolean shouldEvaluateWhetherToSendExplicitlySelected(WifiConfiguration currentConfig) {
          if (currentConfig == null) {
              Log.wtf(TAG, "Current WifiConfiguration is null, but IP provisioning just succeeded");
              return false;
          }
          long currentTimeMillis = mClock.getElapsedSinceBootMillis();
          return (mWifiConfigManager.getLastSelectedNetwork() == currentConfig.networkId
                  && currentTimeMillis - mWifiConfigManager.getLastSelectedTimeStamp()
                  < LAST_SELECTED_NETWORK_EXPIRATION_AGE_MILLIS);
      }
  
      private void sendConnectedState() {
          // If this network was explicitly selected by the user, evaluate whether to inform
          // ConnectivityService of that fact so the system can treat it appropriately.
          WifiConfiguration config = getCurrentWifiConfiguration();
  
          boolean explicitlySelected = false;
          if (shouldEvaluateWhetherToSendExplicitlySelected(config)) {
              // If explicitlySelected is true, the network was selected by the user via Settings or
              // QuickSettings. If this network has Internet access, switch to it. Otherwise, switch
              // to it only if the user confirms that they really want to switch, or has already
              // confirmed and selected "Don't ask again".
              explicitlySelected =
                      mWifiPermissionsUtil.checkNetworkSettingsPermission(config.lastConnectUid);
              if (mVerboseLoggingEnabled) {
                  log("Network selected by UID " + config.lastConnectUid + " explicitlySelected="
                          + explicitlySelected);
              }
          }
  
          if (mVerboseLoggingEnabled) {
              log("explictlySelected=" + explicitlySelected + " acceptUnvalidated="
                      + config.noInternetAccessExpected);
          }
  
          if (mNetworkAgent != null) {
              mNetworkAgent.explicitlySelected(explicitlySelected, config.noInternetAccessExpected);
          }
  
          setNetworkDetailedState(DetailedState.CONNECTED);
          sendNetworkStateChangeBroadcast(mLastBssid);
      }
  
      class RoamingState extends State {
          boolean mAssociated;
          @Override
          public void enter() {
              if (mVerboseLoggingEnabled) {
                  log("RoamingState Enter mScreenOn=" + mScreenOn);
              }
  
              // Make sure we disconnect if roaming fails
              mRoamWatchdogCount++;
              logd("Start Roam Watchdog " + mRoamWatchdogCount);
              sendMessageDelayed(obtainMessage(CMD_ROAM_WATCHDOG_TIMER,
                      mRoamWatchdogCount, 0), ROAM_GUARD_TIMER_MSEC);
              mAssociated = false;
          }
          @Override
          public boolean processMessage(Message message) {
              WifiConfiguration config;
              boolean handleStatus = HANDLED;
  
              switch (message.what) {
                  case CMD_IP_CONFIGURATION_LOST:
                      config = getCurrentWifiConfiguration();
                      if (config != null) {
                          mWifiDiagnostics.captureBugReportData(
                                  WifiDiagnostics.REPORT_REASON_AUTOROAM_FAILURE);
                      }
                      handleStatus = NOT_HANDLED;
                      break;
                  case CMD_UNWANTED_NETWORK:
                      if (mVerboseLoggingEnabled) {
                          log("Roaming and CS doesn't want the network -> ignore");
                      }
                      break;
                  case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                      /**
                       * If we get a SUPPLICANT_STATE_CHANGE_EVENT indicating a DISCONNECT
                       * before NETWORK_DISCONNECTION_EVENT
                       * And there is an associated BSSID corresponding to our target BSSID, then
                       * we have missed the network disconnection, transition to mDisconnectedState
                       * and handle the rest of the events there.
                       */
                      StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
                      if (stateChangeResult.state == SupplicantState.DISCONNECTED
                              || stateChangeResult.state == SupplicantState.INACTIVE
                              || stateChangeResult.state == SupplicantState.INTERFACE_DISABLED) {
                          if (mVerboseLoggingEnabled) {
                              log("STATE_CHANGE_EVENT in roaming state "
                                      + stateChangeResult.toString());
                          }
                          if (stateChangeResult.BSSID != null
                                  && stateChangeResult.BSSID.equals(mTargetRoamBSSID)) {
                              handleNetworkDisconnect();
                              transitionTo(mDisconnectedState);
                          }
                      }
                      if (stateChangeResult.state == SupplicantState.ASSOCIATED) {
                          // We completed the layer2 roaming part
                          mAssociated = true;
                          if (stateChangeResult.BSSID != null) {
                              mTargetRoamBSSID = stateChangeResult.BSSID;
                          }
                      }
                      break;
                  case CMD_ROAM_WATCHDOG_TIMER:
                      if (mRoamWatchdogCount == message.arg1) {
                          if (mVerboseLoggingEnabled) log("roaming watchdog! -> disconnect");
                          mWifiMetrics.endConnectionEvent(
                                  WifiMetrics.ConnectionEvent.FAILURE_ROAM_TIMEOUT,
                                  WifiMetricsProto.ConnectionEvent.HLF_NONE,
                                  WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
                          mRoamFailCount++;
                          handleNetworkDisconnect();
                          mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                                  StaEvent.DISCONNECT_ROAM_WATCHDOG_TIMER);
                          mWifiNative.disconnect(mInterfaceName);
                          transitionTo(mDisconnectedState);
                      }
                      break;
                  case WifiMonitor.NETWORK_CONNECTION_EVENT:
                      if (mAssociated) {
                          if (mVerboseLoggingEnabled) {
                              log("roaming and Network connection established");
                          }
                          mLastNetworkId = message.arg1;
                          mLastBssid = (String) message.obj;
                          mWifiInfo.setBSSID(mLastBssid);
                          mWifiInfo.setNetworkId(mLastNetworkId);
                          int reasonCode = message.arg2;
                          mWifiConnectivityManager.trackBssid(mLastBssid, true, reasonCode);
                          sendNetworkStateChangeBroadcast(mLastBssid);
  
                          // Successful framework roam! (probably)
                          reportConnectionAttemptEnd(
                                  WifiMetrics.ConnectionEvent.FAILURE_NONE,
                                  WifiMetricsProto.ConnectionEvent.HLF_NONE,
                                  WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
  
                          // We must clear the config BSSID, as the wifi chipset may decide to roam
                          // from this point on and having the BSSID specified by QNS would cause
                          // the roam to fail and the device to disconnect.
                          // When transition from RoamingState to DisconnectingState or
                          // DisconnectedState, the config BSSID is cleared by
                          // handleNetworkDisconnect().
                          clearTargetBssid("RoamingCompleted");
  
                          // We used to transition to ObtainingIpState in an
                          // attempt to do DHCPv4 RENEWs on framework roams.
                          // DHCP can take too long to time out, and we now rely
                          // upon IpClient's use of IpReachabilityMonitor to
                          // confirm our current network configuration.
                          //
                          // mIpClient.confirmConfiguration() is called within
                          // the handling of SupplicantState.COMPLETED.
                          transitionTo(mConnectedState);
                      } else {
                          mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                      }
                      break;
                  case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                      // Throw away but only if it corresponds to the network we're roaming to
                      String bssid = (String) message.obj;
                      if (true) {
                          String target = "";
                          if (mTargetRoamBSSID != null) target = mTargetRoamBSSID;
                          log("NETWORK_DISCONNECTION_EVENT in roaming state"
                                  + " BSSID=" + bssid
                                  + " target=" + target);
                      }
                      if (bssid != null && bssid.equals(mTargetRoamBSSID)) {
                          handleNetworkDisconnect();
                          transitionTo(mDisconnectedState);
                      }
                      break;
                  default:
                      handleStatus = NOT_HANDLED;
                      break;
              }
  
              if (handleStatus == HANDLED) {
                  logStateAndMessage(message, this);
              }
              return handleStatus;
          }
  
          @Override
          public void exit() {
              logd("ClientModeImpl: Leaving Roaming state");
          }
      }
  
      class ConnectedState extends State {
          @Override
          public void enter() {
              if (mVerboseLoggingEnabled) {
                  log("Enter ConnectedState  mScreenOn=" + mScreenOn);
              }
  
              reportConnectionAttemptEnd(
                      WifiMetrics.ConnectionEvent.FAILURE_NONE,
                      WifiMetricsProto.ConnectionEvent.HLF_NONE,
                      WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
              mWifiConnectivityManager.handleConnectionStateChanged(
                      WifiConnectivityManager.WIFI_STATE_CONNECTED);
              registerConnected();
              mLastConnectAttemptTimestamp = 0;
              mTargetWifiConfiguration = null;
              mWifiScoreReport.reset();
              mLastSignalLevel = -1;
  
              // Not roaming anymore
              mIsAutoRoaming = false;
  
              mLastDriverRoamAttempt = 0;
              mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
              mWifiInjector.getWifiLastResortWatchdog().connectedStateTransition(true);
              mWifiStateTracker.updateState(WifiStateTracker.CONNECTED);
              //Inform WifiLockManager
              WifiLockManager wifiLockManager = mWifiInjector.getWifiLockManager();
              wifiLockManager.updateWifiClientConnected(true);
          }
          @Override
          public boolean processMessage(Message message) {
              WifiConfiguration config = null;
              boolean handleStatus = HANDLED;
  
              switch (message.what) {
                  case CMD_UNWANTED_NETWORK:
                      if (message.arg1 == NETWORK_STATUS_UNWANTED_DISCONNECT) {
                          mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                                  StaEvent.DISCONNECT_UNWANTED);
                          mWifiNative.disconnect(mInterfaceName);
                          transitionTo(mDisconnectingState);
                      } else if (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN
                              || message.arg1 == NETWORK_STATUS_UNWANTED_VALIDATION_FAILED) {
                          Log.d(TAG, (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN
                                  ? "NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN"
                                  : "NETWORK_STATUS_UNWANTED_VALIDATION_FAILED"));
                          config = getCurrentWifiConfiguration();
                          if (config != null) {
                              // Disable autojoin
                              if (message.arg1 == NETWORK_STATUS_UNWANTED_DISABLE_AUTOJOIN) {
                                  mWifiConfigManager.setNetworkValidatedInternetAccess(
                                          config.networkId, false);
                                  mWifiConfigManager.updateNetworkSelectionStatus(config.networkId,
                                          WifiConfiguration.NetworkSelectionStatus
                                          .DISABLED_NO_INTERNET_PERMANENT);
                              } else {
                                  // stop collect last-mile stats since validation fail
                                  removeMessages(CMD_DIAGS_CONNECT_TIMEOUT);
                                  mWifiDiagnostics.reportConnectionEvent(
                                          WifiDiagnostics.CONNECTION_EVENT_FAILED);
                                  mWifiConfigManager.incrementNetworkNoInternetAccessReports(
                                          config.networkId);
                                  // If this was not the last selected network, update network
                                  // selection status to temporarily disable the network.
                                  if (mWifiConfigManager.getLastSelectedNetwork() != config.networkId
                                          && !config.noInternetAccessExpected) {
                                      Log.i(TAG, "Temporarily disabling network because of"
                                              + "no-internet access");
                                      mWifiConfigManager.updateNetworkSelectionStatus(
                                              config.networkId,
                                              WifiConfiguration.NetworkSelectionStatus
                                                      .DISABLED_NO_INTERNET_TEMPORARY);
                                  }
                              }
                          }
                      }
                      break;
                  case CMD_NETWORK_STATUS:
                      if (message.arg1 == NetworkAgent.VALID_NETWORK) {
                          // stop collect last-mile stats since validation pass
                          removeMessages(CMD_DIAGS_CONNECT_TIMEOUT);
                          mWifiDiagnostics.reportConnectionEvent(
                                  WifiDiagnostics.CONNECTION_EVENT_SUCCEEDED);
                          mWifiScoreCard.noteValidationSuccess(mWifiInfo);
                          config = getCurrentWifiConfiguration();
                          if (config != null) {
                              // re-enable autojoin
                              mWifiConfigManager.updateNetworkSelectionStatus(
                                      config.networkId,
                                      WifiConfiguration.NetworkSelectionStatus
                                              .NETWORK_SELECTION_ENABLE);
                              mWifiConfigManager.setNetworkValidatedInternetAccess(
                                      config.networkId, true);
                          }
                      }
                      break;
                  case CMD_ACCEPT_UNVALIDATED:
                      boolean accept = (message.arg1 != 0);
                      mWifiConfigManager.setNetworkNoInternetAccessExpected(mLastNetworkId, accept);
                      break;
                  case CMD_ASSOCIATED_BSSID:
                      // ASSOCIATING to a new BSSID while already connected, indicates
                      // that driver is roaming
                      mLastDriverRoamAttempt = mClock.getWallClockMillis();
                      handleStatus = NOT_HANDLED;
                      break;
                  case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                      long lastRoam = 0;
                      reportConnectionAttemptEnd(
                              WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION,
                              WifiMetricsProto.ConnectionEvent.HLF_NONE,
                              WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
                      if (mLastDriverRoamAttempt != 0) {
                          // Calculate time since last driver roam attempt
                          lastRoam = mClock.getWallClockMillis() - mLastDriverRoamAttempt;
                          mLastDriverRoamAttempt = 0;
                      }
                      if (unexpectedDisconnectedReason(message.arg2)) {
                          mWifiDiagnostics.captureBugReportData(
                                  WifiDiagnostics.REPORT_REASON_UNEXPECTED_DISCONNECT);
                      }
                      config = getCurrentWifiConfiguration();
  
                      if (mVerboseLoggingEnabled) {
                          log("NETWORK_DISCONNECTION_EVENT in connected state"
                                  + " BSSID=" + mWifiInfo.getBSSID()
                                  + " RSSI=" + mWifiInfo.getRssi()
                                  + " freq=" + mWifiInfo.getFrequency()
                                  + " reason=" + message.arg2
                                  + " Network Selection Status=" + (config == null ? "Unavailable"
                                      : config.getNetworkSelectionStatus().getNetworkStatusString()));
                      }
                      break;
                  case CMD_START_ROAM:
                      // Clear the driver roam indication since we are attempting a framework roam
                      mLastDriverRoamAttempt = 0;
  
                      /* Connect command coming from auto-join */
                      int netId = message.arg1;
                      ScanResult candidate = (ScanResult) message.obj;
                      String bssid = SUPPLICANT_BSSID_ANY;
                      if (candidate != null) {
                          bssid = candidate.BSSID;
                      }
                      config = mWifiConfigManager.getConfiguredNetworkWithoutMasking(netId);
                      if (config == null) {
                          loge("CMD_START_ROAM and no config, bail out...");
                          break;
                      }
                      mWifiScoreCard.noteConnectionAttempt(mWifiInfo);
                      setTargetBssid(config, bssid);
                      mTargetNetworkId = netId;
  
                      logd("CMD_START_ROAM sup state "
                              + mSupplicantStateTracker.getSupplicantStateName()
                              + " my state " + getCurrentState().getName()
                              + " nid=" + Integer.toString(netId)
                              + " config " + config.configKey()
                              + " targetRoamBSSID " + mTargetRoamBSSID);
  
                      reportConnectionAttemptStart(config, mTargetRoamBSSID,
                              WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
                      if (mWifiNative.roamToNetwork(mInterfaceName, config)) {
                          mLastConnectAttemptTimestamp = mClock.getWallClockMillis();
                          mTargetWifiConfiguration = config;
                          mIsAutoRoaming = true;
                          mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_ROAM, config);
                          transitionTo(mRoamingState);
                      } else {
                          loge("CMD_START_ROAM Failed to start roaming to network " + config);
                          reportConnectionAttemptEnd(
                                  WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
                                  WifiMetricsProto.ConnectionEvent.HLF_NONE,
                                  WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
                          replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
                                  WifiManager.ERROR);
                          mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
                          break;
                      }
                      break;
                  default:
                      handleStatus = NOT_HANDLED;
                      break;
              }
  
              if (handleStatus == HANDLED) {
                  logStateAndMessage(message, this);
              }
  
              return handleStatus;
          }
  
          @Override
          public void exit() {
              logd("ClientModeImpl: Leaving Connected state");
              mWifiConnectivityManager.handleConnectionStateChanged(
                       WifiConnectivityManager.WIFI_STATE_TRANSITIONING);
  
              mLastDriverRoamAttempt = 0;
              mWifiInjector.getWifiLastResortWatchdog().connectedStateTransition(false);
          }
      }
  
      class DisconnectingState extends State {
  
          @Override
          public void enter() {
  
              if (mVerboseLoggingEnabled) {
                  logd(" Enter DisconnectingState State screenOn=" + mScreenOn);
              }
  
              // Make sure we disconnect: we enter this state prior to connecting to a new
              // network, waiting for either a DISCONNECT event or a SUPPLICANT_STATE_CHANGE
              // event which in this case will be indicating that supplicant started to associate.
              // In some cases supplicant doesn't ignore the connect requests (it might not
              // find the target SSID in its cache),
              // Therefore we end up stuck that state, hence the need for the watchdog.
              mDisconnectingWatchdogCount++;
              logd("Start Disconnecting Watchdog " + mDisconnectingWatchdogCount);
              sendMessageDelayed(obtainMessage(CMD_DISCONNECTING_WATCHDOG_TIMER,
                      mDisconnectingWatchdogCount, 0), DISCONNECTING_GUARD_TIMER_MSEC);
          }
  
          @Override
          public boolean processMessage(Message message) {
              boolean handleStatus = HANDLED;
  
              switch (message.what) {
                  case CMD_DISCONNECT:
                      if (mVerboseLoggingEnabled) {
                          log("Ignore CMD_DISCONNECT when already disconnecting.");
                      }
                      break;
                  case CMD_DISCONNECTING_WATCHDOG_TIMER:
                      if (mDisconnectingWatchdogCount == message.arg1) {
                          if (mVerboseLoggingEnabled) log("disconnecting watchdog! -> disconnect");
                          handleNetworkDisconnect();
                          transitionTo(mDisconnectedState);
                      }
                      break;
                  case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                      /**
                       * If we get a SUPPLICANT_STATE_CHANGE_EVENT before NETWORK_DISCONNECTION_EVENT
                       * we have missed the network disconnection, transition to mDisconnectedState
                       * and handle the rest of the events there
                       */
                      deferMessage(message);
                      handleNetworkDisconnect();
                      transitionTo(mDisconnectedState);
                      break;
                  default:
                      handleStatus = NOT_HANDLED;
                      break;
              }
  
              if (handleStatus == HANDLED) {
                  logStateAndMessage(message, this);
              }
              return handleStatus;
          }
      }
  
      class DisconnectedState extends State {
          @Override
          public void enter() {
              Log.i(TAG, "disconnectedstate enter");
              // We don't scan frequently if this is a temporary disconnect
              // due to p2p
              if (mTemporarilyDisconnectWifi) {
                  p2pSendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_RESPONSE);
                  return;
              }
  
              if (mVerboseLoggingEnabled) {
                  logd(" Enter DisconnectedState screenOn=" + mScreenOn);
              }
  
              /** clear the roaming state, if we were roaming, we failed */
              mIsAutoRoaming = false;
  
              mWifiConnectivityManager.handleConnectionStateChanged(
                      WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
          }
  
          @Override
          public boolean processMessage(Message message) {
              boolean handleStatus = HANDLED;
  
              switch (message.what) {
                  case CMD_DISCONNECT:
                      mWifiMetrics.logStaEvent(StaEvent.TYPE_FRAMEWORK_DISCONNECT,
                              StaEvent.DISCONNECT_GENERIC);
                      mWifiNative.disconnect(mInterfaceName);
                      break;
                  case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                      if (message.arg2 == 15 /* FOURWAY_HANDSHAKE_TIMEOUT */) {
                          String bssid = (message.obj == null)
                                  ? mTargetRoamBSSID : (String) message.obj;
                          mWifiInjector.getWifiLastResortWatchdog()
                                  .noteConnectionFailureAndTriggerIfNeeded(
                                          getTargetSsid(), bssid,
                                          WifiLastResortWatchdog.FAILURE_CODE_AUTHENTICATION);
                      }
                      break;
                  case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
                      StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
                      if (mVerboseLoggingEnabled) {
                          logd("SUPPLICANT_STATE_CHANGE_EVENT state=" + stateChangeResult.state
                                  + " -> state= "
                                  + WifiInfo.getDetailedStateOf(stateChangeResult.state));
                      }
                      if (SupplicantState.isConnecting(stateChangeResult.state)) {
                          WifiConfiguration config = mWifiConfigManager.getConfiguredNetwork(
                                  stateChangeResult.networkId);
  
                          // Update Passpoint information before setNetworkDetailedState as
                          // WifiTracker monitors NETWORK_STATE_CHANGED_ACTION to update UI.
                          mWifiInfo.setFQDN(null);
                          mWifiInfo.setOsuAp(false);
                          mWifiInfo.setProviderFriendlyName(null);
                          if (config != null && (config.isPasspoint() || config.osu)) {
                              if (config.isPasspoint()) {
                                  mWifiInfo.setFQDN(config.FQDN);
                              } else {
                                  mWifiInfo.setOsuAp(true);
                              }
                              mWifiInfo.setProviderFriendlyName(config.providerFriendlyName);
                          }
                      }
                      setNetworkDetailedState(WifiInfo.getDetailedStateOf(stateChangeResult.state));
                      /* ConnectModeState does the rest of the handling */
                      handleStatus = NOT_HANDLED;
                      break;
                  case WifiP2pServiceImpl.P2P_CONNECTION_CHANGED:
                      NetworkInfo info = (NetworkInfo) message.obj;
                      mP2pConnected.set(info.isConnected());
                      break;
                  case CMD_RECONNECT:
                  case CMD_REASSOCIATE:
                      if (mTemporarilyDisconnectWifi) {
                          // Drop a third party reconnect/reassociate if STA is
                          // temporarily disconnected for p2p
                          break;
                      } else {
                          // ConnectModeState handles it
                          handleStatus = NOT_HANDLED;
                      }
                      break;
                  case CMD_SCREEN_STATE_CHANGED:
                      handleScreenStateChanged(message.arg1 != 0);
                      break;
                  default:
                      handleStatus = NOT_HANDLED;
                      break;
              }
  
              if (handleStatus == HANDLED) {
                  logStateAndMessage(message, this);
              }
              return handleStatus;
          }
  
          @Override
          public void exit() {
              mWifiConnectivityManager.handleConnectionStateChanged(
                       WifiConnectivityManager.WIFI_STATE_TRANSITIONING);
          }
      }
  
      /**
       * State machine initiated requests can have replyTo set to null, indicating
       * there are no recipients, we ignore those reply actions.
       */
      private void replyToMessage(Message msg, int what) {
          if (msg.replyTo == null) return;
          Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
          mReplyChannel.replyToMessage(msg, dstMsg);
      }
  
      private void replyToMessage(Message msg, int what, int arg1) {
          if (msg.replyTo == null) return;
          Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
          dstMsg.arg1 = arg1;
          mReplyChannel.replyToMessage(msg, dstMsg);
      }
  
      private void replyToMessage(Message msg, int what, Object obj) {
          if (msg.replyTo == null) return;
          Message dstMsg = obtainMessageWithWhatAndArg2(msg, what);
          dstMsg.obj = obj;
          mReplyChannel.replyToMessage(msg, dstMsg);
      }
  
      /**
       * arg2 on the source message has a unique id that needs to be retained in replies
       * to match the request
       * <p>see WifiManager for details
       */
      private Message obtainMessageWithWhatAndArg2(Message srcMsg, int what) {
          Message msg = Message.obtain();
          msg.what = what;
          msg.arg2 = srcMsg.arg2;
          return msg;
      }
  
      /**
       * Notify interested parties if a wifi config has been changed.
       *
       * @param wifiCredentialEventType WIFI_CREDENTIAL_SAVED or WIFI_CREDENTIAL_FORGOT
       * @param config Must have a WifiConfiguration object to succeed
       * TODO: b/35258354 investigate if this can be removed.  Is the broadcast sent by
       * WifiConfigManager sufficient?
       */
      private void broadcastWifiCredentialChanged(int wifiCredentialEventType,
              WifiConfiguration config) {
          if (config != null && config.preSharedKey != null) {
              Intent intent = new Intent(WifiManager.WIFI_CREDENTIAL_CHANGED_ACTION);
              intent.putExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_SSID, config.SSID);
              intent.putExtra(WifiManager.EXTRA_WIFI_CREDENTIAL_EVENT_TYPE,
                      wifiCredentialEventType);
              mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT,
                      android.Manifest.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE);
          }
      }
  
      void handleGsmAuthRequest(SimAuthRequestData requestData) {
          if (mTargetWifiConfiguration == null
                  || mTargetWifiConfiguration.networkId
                  == requestData.networkId) {
              logd("id matches targetWifiConfiguration");
          } else {
              logd("id does not match targetWifiConfiguration");
              return;
          }
  
          /*
           * Try authentication in the following order.
           *
           *    Standard       Cellular_auth     Type Command
           *
           * 1. 3GPP TS 31.102 3G_authentication [Length][RAND][Length][AUTN]
           *                            [Length][RES][Length][CK][Length][IK] and more
           * 2. 3GPP TS 31.102 2G_authentication [Length][RAND]
           *                            [Length][SRES][Length][Cipher Key Kc]
           * 3. 3GPP TS 11.11  2G_authentication [RAND]
           *                            [SRES][Cipher Key Kc]
           */
          String response =
                  TelephonyUtil.getGsmSimAuthResponse(requestData.data, getTelephonyManager());
          if (response == null) {
              // In case of failure, issue may be due to sim type, retry as No.2 case
              response = TelephonyUtil.getGsmSimpleSimAuthResponse(requestData.data,
                      getTelephonyManager());
              if (response == null) {
                  // In case of failure, issue may be due to sim type, retry as No.3 case
                  response = TelephonyUtil.getGsmSimpleSimNoLengthAuthResponse(requestData.data,
                          getTelephonyManager());
              }
          }
          if (response == null || response.length() == 0) {
              mWifiNative.simAuthFailedResponse(mInterfaceName, requestData.networkId);
          } else {
              logv("Supplicant Response -" + response);
              mWifiNative.simAuthResponse(
                      mInterfaceName, requestData.networkId,
                      WifiNative.SIM_AUTH_RESP_TYPE_GSM_AUTH, response);
          }
      }
  
      void handle3GAuthRequest(SimAuthRequestData requestData) {
          if (mTargetWifiConfiguration == null
                  || mTargetWifiConfiguration.networkId
                  == requestData.networkId) {
              logd("id matches targetWifiConfiguration");
          } else {
              logd("id does not match targetWifiConfiguration");
              return;
          }
  
          SimAuthResponseData response =
                  TelephonyUtil.get3GAuthResponse(requestData, getTelephonyManager());
          if (response != null) {
              mWifiNative.simAuthResponse(
                      mInterfaceName, requestData.networkId, response.type, response.response);
          } else {
              mWifiNative.umtsAuthFailedResponse(mInterfaceName, requestData.networkId);
          }
      }
  
      /**
       * Automatically connect to the network specified
       *
       * @param networkId ID of the network to connect to
       * @param uid UID of the app triggering the connection.
       * @param bssid BSSID of the network
       */
      public void startConnectToNetwork(int networkId, int uid, String bssid) {
          sendMessage(CMD_START_CONNECT, networkId, uid, bssid);
      }
  
      /**
       * Automatically roam to the network specified
       *
       * @param networkId ID of the network to roam to
       * @param scanResult scan result which identifies the network to roam to
       */
      public void startRoamToNetwork(int networkId, ScanResult scanResult) {
          sendMessage(CMD_START_ROAM, networkId, 0, scanResult);
      }
  
      /**
       * Dynamically turn on/off WifiConnectivityManager
       *
       * @param enabled true-enable; false-disable
       */
      public void enableWifiConnectivityManager(boolean enabled) {
          sendMessage(CMD_ENABLE_WIFI_CONNECTIVITY_MANAGER, enabled ? 1 : 0);
      }
  
      /**
       * @param reason reason code from supplicant on network disconnected event
       * @return true if this is a suspicious disconnect
       */
      static boolean unexpectedDisconnectedReason(int reason) {
          return reason == 2              // PREV_AUTH_NOT_VALID
                  || reason == 6          // CLASS2_FRAME_FROM_NONAUTH_STA
                  || reason == 7          // FRAME_FROM_NONASSOC_STA
                  || reason == 8          // STA_HAS_LEFT
                  || reason == 9          // STA_REQ_ASSOC_WITHOUT_AUTH
                  || reason == 14         // MICHAEL_MIC_FAILURE
                  || reason == 15         // 4WAY_HANDSHAKE_TIMEOUT
                  || reason == 16         // GROUP_KEY_UPDATE_TIMEOUT
                  || reason == 18         // GROUP_CIPHER_NOT_VALID
                  || reason == 19         // PAIRWISE_CIPHER_NOT_VALID
                  || reason == 23         // IEEE_802_1X_AUTH_FAILED
                  || reason == 34;        // DISASSOC_LOW_ACK
      }
  
      /**
       * Update WifiMetrics before dumping
       */
      public void updateWifiMetrics() {
          mWifiMetrics.updateSavedNetworks(mWifiConfigManager.getSavedNetworks(Process.WIFI_UID));
          mPasspointManager.updateMetrics();
      }
  
      /**
       * Private method to handle calling WifiConfigManager to forget/remove network configs and reply
       * to the message from the sender of the outcome.
       *
       * The current implementation requires that forget and remove be handled in different ways
       * (responses are handled differently).  In the interests of organization, the handling is all
       * now in this helper method.  TODO: b/35257965 is filed to track the possibility of merging
       * the two call paths.
       */
      private boolean deleteNetworkConfigAndSendReply(Message message, boolean calledFromForget) {
          boolean success = mWifiConfigManager.removeNetwork(message.arg1, message.sendingUid);
          if (!success) {
              loge("Failed to remove network");
          }
  
          if (calledFromForget) {
              if (success) {
                  replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED);
                  broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_FORGOT,
                                                 (WifiConfiguration) message.obj);
                  return true;
              }
              replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED, WifiManager.ERROR);
              return false;
          } else {
              // Remaining calls are from the removeNetwork path
              if (success) {
                  replyToMessage(message, message.what, SUCCESS);
                  return true;
              }
              mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
              replyToMessage(message, message.what, FAILURE);
              return false;
          }
      }
  
      /**
       * Private method to handle calling WifiConfigManager to add & enable network configs and reply
       * to the message from the sender of the outcome.
       *
       * @return NetworkUpdateResult with networkId of the added/updated configuration. Will return
       * {@link WifiConfiguration#INVALID_NETWORK_ID} in case of error.
       */
      private NetworkUpdateResult saveNetworkConfigAndSendReply(Message message) {
          WifiConfiguration config = (WifiConfiguration) message.obj;
          if (config == null) {
              loge("SAVE_NETWORK with null configuration "
                      + mSupplicantStateTracker.getSupplicantStateName()
                      + " my state " + getCurrentState().getName());
              mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
              replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
              return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
          }
          NetworkUpdateResult result =
                  mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
          if (!result.isSuccess()) {
              loge("SAVE_NETWORK adding/updating config=" + config + " failed");
              mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
              replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
              return result;
          }
          if (!mWifiConfigManager.enableNetwork(
                  result.getNetworkId(), false, message.sendingUid)) {
              loge("SAVE_NETWORK enabling config=" + config + " failed");
              mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
              replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
              return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
          }
          broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
          replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
          return result;
      }
  
      private static String getLinkPropertiesSummary(LinkProperties lp) {
          List<String> attributes = new ArrayList<>(6);
          if (lp.hasIPv4Address()) {
              attributes.add("v4");
          }
          if (lp.hasIPv4DefaultRoute()) {
              attributes.add("v4r");
          }
          if (lp.hasIPv4DnsServer()) {
              attributes.add("v4dns");
          }
          if (lp.hasGlobalIPv6Address()) {
              attributes.add("v6");
          }
          if (lp.hasIPv6DefaultRoute()) {
              attributes.add("v6r");
          }
          if (lp.hasIPv6DnsServer()) {
              attributes.add("v6dns");
          }
  
          return TextUtils.join(" ", attributes);
      }
  
      /**
       * Gets the SSID from the WifiConfiguration pointed at by 'mTargetNetworkId'
       * This should match the network config framework is attempting to connect to.
       */
      private String getTargetSsid() {
          WifiConfiguration currentConfig = mWifiConfigManager.getConfiguredNetwork(mTargetNetworkId);
          if (currentConfig != null) {
              return currentConfig.SSID;
          }
          return null;
      }
  
      /**
       * Send message to WifiP2pServiceImpl.
       * @return true if message is sent.
       *         false if there is no channel configured for WifiP2pServiceImpl.
       */
      private boolean p2pSendMessage(int what) {
          if (mWifiP2pChannel != null) {
              mWifiP2pChannel.sendMessage(what);
              return true;
          }
          return false;
      }
  
      /**
       * Send message to WifiP2pServiceImpl with an additional param |arg1|.
       * @return true if message is sent.
       *         false if there is no channel configured for WifiP2pServiceImpl.
       */
      private boolean p2pSendMessage(int what, int arg1) {
          if (mWifiP2pChannel != null) {
              mWifiP2pChannel.sendMessage(what, arg1);
              return true;
          }
          return false;
      }
  
      /**
       * Check if there is any connection request for WiFi network.
       */
      private boolean hasConnectionRequests() {
          return mNetworkFactory.hasConnectionRequests()
                  || mUntrustedNetworkFactory.hasConnectionRequests();
      }
  
      /**
       * Returns whether CMD_IP_REACHABILITY_LOST events should trigger disconnects.
       */
      public boolean getIpReachabilityDisconnectEnabled() {
          return mIpReachabilityDisconnectEnabled;
      }
  
      /**
       * Sets whether CMD_IP_REACHABILITY_LOST events should trigger disconnects.
       */
      public void setIpReachabilityDisconnectEnabled(boolean enabled) {
          mIpReachabilityDisconnectEnabled = enabled;
      }
  
      /**
       * Sends a message to initialize the ClientModeImpl.
       *
       * @return true if succeeded, false otherwise.
       */
      public boolean syncInitialize(AsyncChannel channel) {
          Message resultMsg = channel.sendMessageSynchronously(CMD_INITIALIZE);
          boolean result = (resultMsg.arg1 != FAILURE);
          resultMsg.recycle();
          return result;
      }
  
      /**
       * Add a network request match callback to {@link WifiNetworkFactory}.
       */
      public void addNetworkRequestMatchCallback(IBinder binder,
                                                 INetworkRequestMatchCallback callback,
                                                 int callbackIdentifier) {
          mNetworkFactory.addCallback(binder, callback, callbackIdentifier);
      }
  
      /**
       * Remove a network request match callback from {@link WifiNetworkFactory}.
       */
      public void removeNetworkRequestMatchCallback(int callbackIdentifier) {
          mNetworkFactory.removeCallback(callbackIdentifier);
      }
  
      /**
       * Remove all approved access points from {@link WifiNetworkFactory} for the provided package.
       */
      public void removeNetworkRequestUserApprovedAccessPointsForApp(@NonNull String packageName) {
          mNetworkFactory.removeUserApprovedAccessPointsForApp(packageName);
      }
  
      /**
       * Clear all approved access points from {@link WifiNetworkFactory}.
       */
      public void clearNetworkRequestUserApprovedAccessPoints() {
          mNetworkFactory.clear();
      }
  
      /**
       * Gets the factory MAC address of wlan0 (station interface).
       * @return String representation of the factory MAC address.
       */
      public String getFactoryMacAddress() {
          MacAddress macAddress = mWifiNative.getFactoryMacAddress(mInterfaceName);
          if (macAddress != null) {
              return macAddress.toString();
          }
          if (!mConnectedMacRandomzationSupported) {
              return mWifiNative.getMacAddress(mInterfaceName);
          }
          return null;
      }
  
      /**
       * Sets the current device mobility state.
       * @param state the new device mobility state
       */
      public void setDeviceMobilityState(@DeviceMobilityState int state) {
          mWifiConnectivityManager.setDeviceMobilityState(state);
      }
  
      /**
       * Updates the Wi-Fi usability score.
       * @param seqNum Sequence number of the Wi-Fi usability score.
       * @param score The Wi-Fi usability score.
       * @param predictionHorizonSec Prediction horizon of the Wi-Fi usability score.
       */
      public void updateWifiUsabilityScore(int seqNum, int score, int predictionHorizonSec) {
          mWifiMetrics.incrementWifiUsabilityScoreCount(seqNum, score, predictionHorizonSec);
      }
  
      /**
       * Sends a link probe.
       */
      @VisibleForTesting
      public void probeLink(WifiNative.SendMgmtFrameCallback callback, int mcs) {
          mWifiNative.probeLink(mInterfaceName, MacAddress.fromString(mWifiInfo.getBSSID()),
                  callback, mcs);
      }
  }
  