content: /*
   * Copyright (C) 2008 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 android.annotation.IntDef;
  import android.annotation.NonNull;
  import android.net.InterfaceConfiguration;
  import android.net.MacAddress;
  import android.net.TrafficStats;
  import android.net.apf.ApfCapabilities;
  import android.net.wifi.ScanResult;
  import android.net.wifi.WifiConfiguration;
  import android.net.wifi.WifiScanner;
  import android.os.Handler;
  import android.os.INetworkManagementService;
  import android.os.RemoteException;
  import android.os.SystemClock;
  import android.text.TextUtils;
  import android.util.Log;
  import android.util.SparseArray;
  
  import com.android.internal.annotations.Immutable;
  import com.android.internal.util.HexDump;
  import com.android.server.net.BaseNetworkObserver;
  import com.android.server.wifi.util.FrameParser;
  import com.android.server.wifi.util.NativeUtil;
  
  import java.io.PrintWriter;
  import java.io.StringWriter;
  import java.lang.annotation.Retention;
  import java.lang.annotation.RetentionPolicy;
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.nio.CharBuffer;
  import java.nio.charset.CharacterCodingException;
  import java.nio.charset.CharsetDecoder;
  import java.nio.charset.StandardCharsets;
  import java.text.SimpleDateFormat;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Random;
  import java.util.Set;
  import java.util.TimeZone;
  
  /**
   * Native calls for bring up/shut down of the supplicant daemon and for
   * sending requests to the supplicant daemon
   *
   * {@hide}
   */
  public class WifiNative {
      private static final String TAG = "WifiNative";
      private final SupplicantStaIfaceHal mSupplicantStaIfaceHal;
      private final HostapdHal mHostapdHal;
      private final WifiVendorHal mWifiVendorHal;
      private final WificondControl mWificondControl;
      private final WifiMonitor mWifiMonitor;
      private final INetworkManagementService mNwManagementService;
      private final PropertyService mPropertyService;
      private final WifiMetrics mWifiMetrics;
      private final Handler mHandler;
      private final Random mRandom;
      private boolean mVerboseLoggingEnabled = false;
  
      public WifiNative(WifiVendorHal vendorHal,
                        SupplicantStaIfaceHal staIfaceHal, HostapdHal hostapdHal,
                        WificondControl condControl, WifiMonitor wifiMonitor,
                        INetworkManagementService nwService,
                        PropertyService propertyService, WifiMetrics wifiMetrics,
                        Handler handler, Random random) {
          mWifiVendorHal = vendorHal;
          mSupplicantStaIfaceHal = staIfaceHal;
          mHostapdHal = hostapdHal;
          mWificondControl = condControl;
          mWifiMonitor = wifiMonitor;
          mNwManagementService = nwService;
          mPropertyService = propertyService;
          mWifiMetrics = wifiMetrics;
          mHandler = handler;
          mRandom = random;
      }
  
      /**
       * Enable verbose logging for all sub modules.
       */
      public void enableVerboseLogging(int verbose) {
          mVerboseLoggingEnabled = verbose > 0 ? true : false;
          mWificondControl.enableVerboseLogging(mVerboseLoggingEnabled);
          mSupplicantStaIfaceHal.enableVerboseLogging(mVerboseLoggingEnabled);
          mWifiVendorHal.enableVerboseLogging(mVerboseLoggingEnabled);
      }
  
      /********************************************************
       * Interface management related methods.
       ********************************************************/
      /**
       * Meta-info about every iface that is active.
       */
      private static class Iface {
          /** Type of ifaces possible */
          public static final int IFACE_TYPE_AP = 0;
          public static final int IFACE_TYPE_STA_FOR_CONNECTIVITY = 1;
          public static final int IFACE_TYPE_STA_FOR_SCAN = 2;
  
          @IntDef({IFACE_TYPE_AP, IFACE_TYPE_STA_FOR_CONNECTIVITY, IFACE_TYPE_STA_FOR_SCAN})
          @Retention(RetentionPolicy.SOURCE)
          public @interface IfaceType{}
  
          /** Identifier allocated for the interface */
          public final int id;
          /** Type of the iface: STA (for Connectivity or Scan) or AP */
          public final @IfaceType int type;
          /** Name of the interface */
          public String name;
          /** Is the interface up? This is used to mask up/down notifications to external clients. */
          public boolean isUp;
          /** External iface destroyed listener for the iface */
          public InterfaceCallback externalListener;
          /** Network observer registered for this interface */
          public NetworkObserverInternal networkObserver;
          /** Interface feature set / capabilities */
          public long featureSet;
  
          Iface(int id, @Iface.IfaceType int type) {
              this.id = id;
              this.type = type;
          }
  
          @Override
          public String toString() {
              StringBuffer sb = new StringBuffer();
              String typeString;
              switch(type) {
                  case IFACE_TYPE_STA_FOR_CONNECTIVITY:
                      typeString = "STA_CONNECTIVITY";
                      break;
                  case IFACE_TYPE_STA_FOR_SCAN:
                      typeString = "STA_SCAN";
                      break;
                  case IFACE_TYPE_AP:
                      typeString = "AP";
                      break;
                  default:
                      typeString = "<UNKNOWN>";
                      break;
              }
              sb.append("Iface:")
                  .append("{")
                  .append("Name=").append(name)
                  .append(",")
                  .append("Id=").append(id)
                  .append(",")
                  .append("Type=").append(typeString)
                  .append("}");
              return sb.toString();
          }
      }
  
      /**
       * Iface Management entity. This class maintains list of all the active ifaces.
       */
      private static class IfaceManager {
          /** Integer to allocate for the next iface being created */
          private int mNextId;
          /** Map of the id to the iface structure */
          private HashMap<Integer, Iface> mIfaces = new HashMap<>();
  
          /** Allocate a new iface for the given type */
          private Iface allocateIface(@Iface.IfaceType  int type) {
              Iface iface = new Iface(mNextId, type);
              mIfaces.put(mNextId, iface);
              mNextId++;
              return iface;
          }
  
          /** Remove the iface using the provided id */
          private Iface removeIface(int id) {
              return mIfaces.remove(id);
          }
  
          /** Lookup the iface using the provided id */
          private Iface getIface(int id) {
              return mIfaces.get(id);
          }
  
          /** Lookup the iface using the provided name */
          private Iface getIface(@NonNull String ifaceName) {
              for (Iface iface : mIfaces.values()) {
                  if (TextUtils.equals(iface.name, ifaceName)) {
                      return iface;
                  }
              }
              return null;
          }
  
          /** Iterator to use for deleting all the ifaces while performing teardown on each of them */
          private Iterator<Integer> getIfaceIdIter() {
              return mIfaces.keySet().iterator();
          }
  
          /** Checks if there are any iface active. */
          private boolean hasAnyIface() {
              return !mIfaces.isEmpty();
          }
  
          /** Checks if there are any iface of the given type active. */
          private boolean hasAnyIfaceOfType(@Iface.IfaceType int type) {
              for (Iface iface : mIfaces.values()) {
                  if (iface.type == type) {
                      return true;
                  }
              }
              return false;
          }
  
          /** Checks if there are any iface of the given type active. */
          private Iface findAnyIfaceOfType(@Iface.IfaceType int type) {
              for (Iface iface : mIfaces.values()) {
                  if (iface.type == type) {
                      return iface;
                  }
              }
              return null;
          }
  
          /** Checks if there are any STA (for connectivity) iface active. */
          private boolean hasAnyStaIfaceForConnectivity() {
              return hasAnyIfaceOfType(Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY);
          }
  
          /** Checks if there are any STA (for scan) iface active. */
          private boolean hasAnyStaIfaceForScan() {
              return hasAnyIfaceOfType(Iface.IFACE_TYPE_STA_FOR_SCAN);
          }
  
          /** Checks if there are any AP iface active. */
          private boolean hasAnyApIface() {
              return hasAnyIfaceOfType(Iface.IFACE_TYPE_AP);
          }
  
          /** Finds the name of any STA iface active. */
          private String findAnyStaIfaceName() {
              Iface iface = findAnyIfaceOfType(Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY);
              if (iface == null) {
                  iface = findAnyIfaceOfType(Iface.IFACE_TYPE_STA_FOR_SCAN);
              }
              if (iface == null) {
                  return null;
              }
              return iface.name;
          }
  
          /** Finds the name of any AP iface active. */
          private String findAnyApIfaceName() {
              Iface iface = findAnyIfaceOfType(Iface.IFACE_TYPE_AP);
              if (iface == null) {
                  return null;
              }
              return iface.name;
          }
  
          /** Removes the existing iface that does not match the provided id. */
          public Iface removeExistingIface(int newIfaceId) {
              Iface removedIface = null;
              // The number of ifaces in the database could be 1 existing & 1 new at the max.
              if (mIfaces.size() > 2) {
                  Log.wtf(TAG, "More than 1 existing interface found");
              }
              Iterator<Map.Entry<Integer, Iface>> iter = mIfaces.entrySet().iterator();
              while (iter.hasNext()) {
                  Map.Entry<Integer, Iface> entry = iter.next();
                  if (entry.getKey() != newIfaceId) {
                      removedIface = entry.getValue();
                      iter.remove();
                  }
              }
              return removedIface;
          }
      }
  
      private Object mLock = new Object();
      private final IfaceManager mIfaceMgr = new IfaceManager();
      private HashSet<StatusListener> mStatusListeners = new HashSet<>();
  
      /** Helper method invoked to start supplicant if there were no ifaces */
      private boolean startHal() {
          synchronized (mLock) {
              if (!mIfaceMgr.hasAnyIface()) {
                  if (mWifiVendorHal.isVendorHalSupported()) {
                      if (!mWifiVendorHal.startVendorHal()) {
                          Log.e(TAG, "Failed to start vendor HAL");
                          return false;
                      }
                  } else {
                      Log.i(TAG, "Vendor Hal not supported, ignoring start.");
                  }
              }
              return true;
          }
      }
  
      /** Helper method invoked to stop HAL if there are no more ifaces */
      private void stopHalAndWificondIfNecessary() {
          synchronized (mLock) {
              if (!mIfaceMgr.hasAnyIface()) {
                  if (!mWificondControl.tearDownInterfaces()) {
                      Log.e(TAG, "Failed to teardown ifaces from wificond");
                  }
                  if (mWifiVendorHal.isVendorHalSupported()) {
                      mWifiVendorHal.stopVendorHal();
                  } else {
                      Log.i(TAG, "Vendor Hal not supported, ignoring stop.");
                  }
              }
          }
      }
  
      private static final int CONNECT_TO_SUPPLICANT_RETRY_INTERVAL_MS = 100;
      private static final int CONNECT_TO_SUPPLICANT_RETRY_TIMES = 50;
      /**
       * This method is called to wait for establishing connection to wpa_supplicant.
       *
       * @return true if connection is established, false otherwise.
       */
      private boolean startAndWaitForSupplicantConnection() {
          // Start initialization if not already started.
          if (!mSupplicantStaIfaceHal.isInitializationStarted()
                  && !mSupplicantStaIfaceHal.initialize()) {
              return false;
          }
          if (!mSupplicantStaIfaceHal.startDaemon()) {
              Log.e(TAG, "Failed to startup supplicant");
              return false;
          }
          boolean connected = false;
          int connectTries = 0;
          while (!connected && connectTries++ < CONNECT_TO_SUPPLICANT_RETRY_TIMES) {
              // Check if the initialization is complete.
              connected = mSupplicantStaIfaceHal.isInitializationComplete();
              if (connected) {
                  break;
              }
              try {
                  Thread.sleep(CONNECT_TO_SUPPLICANT_RETRY_INTERVAL_MS);
              } catch (InterruptedException ignore) {
              }
          }
          return connected;
      }
  
      /** Helper method invoked to start supplicant if there were no STA ifaces */
      private boolean startSupplicant() {
          synchronized (mLock) {
              if (!mIfaceMgr.hasAnyStaIfaceForConnectivity()) {
                  if (!startAndWaitForSupplicantConnection()) {
                      Log.e(TAG, "Failed to connect to supplicant");
                      return false;
                  }
                  if (!mSupplicantStaIfaceHal.registerDeathHandler(
                          new SupplicantDeathHandlerInternal())) {
                      Log.e(TAG, "Failed to register supplicant death handler");
                      return false;
                  }
              }
              return true;
          }
      }
  
      /** Helper method invoked to stop supplicant if there are no more STA ifaces */
      private void stopSupplicantIfNecessary() {
          synchronized (mLock) {
              if (!mIfaceMgr.hasAnyStaIfaceForConnectivity()) {
                  if (!mSupplicantStaIfaceHal.deregisterDeathHandler()) {
                      Log.e(TAG, "Failed to deregister supplicant death handler");
                  }
                  mSupplicantStaIfaceHal.terminate();
              }
          }
      }
  
      /** Helper method invoked to start hostapd if there were no AP ifaces */
      private boolean startHostapd() {
          synchronized (mLock) {
              if (!mIfaceMgr.hasAnyApIface()) {
                  if (!startAndWaitForHostapdConnection()) {
                      Log.e(TAG, "Failed to connect to hostapd");
                      return false;
                  }
                  if (!mHostapdHal.registerDeathHandler(
                          new HostapdDeathHandlerInternal())) {
                      Log.e(TAG, "Failed to register hostapd death handler");
                      return false;
                  }
              }
              return true;
          }
      }
  
      /** Helper method invoked to stop hostapd if there are no more AP ifaces */
      private void stopHostapdIfNecessary() {
          synchronized (mLock) {
              if (!mIfaceMgr.hasAnyApIface()) {
                  if (!mHostapdHal.deregisterDeathHandler()) {
                      Log.e(TAG, "Failed to deregister hostapd death handler");
                  }
                  mHostapdHal.terminate();
              }
          }
      }
  
      /** Helper method to register a network observer and return it */
      private boolean registerNetworkObserver(NetworkObserverInternal observer) {
          if (observer == null) return false;
          try {
              mNwManagementService.registerObserver(observer);
          } catch (RemoteException | IllegalStateException e) {
              Log.e(TAG, "Unable to register observer", e);
              return false;
          }
          return true;
      }
  
      /** Helper method to unregister a network observer */
      private boolean unregisterNetworkObserver(NetworkObserverInternal observer) {
          if (observer == null) return false;
          try {
              mNwManagementService.unregisterObserver(observer);
          } catch (RemoteException | IllegalStateException e) {
              Log.e(TAG, "Unable to unregister observer", e);
              return false;
          }
          return true;
      }
  
      /**
       * Helper method invoked to teardown client iface (for connectivity) and perform
       * necessary cleanup
       */
      private void onClientInterfaceForConnectivityDestroyed(@NonNull Iface iface) {
          synchronized (mLock) {
              mWifiMonitor.stopMonitoring(iface.name);
              if (!unregisterNetworkObserver(iface.networkObserver)) {
                  Log.e(TAG, "Failed to unregister network observer on " + iface);
              }
              if (!mSupplicantStaIfaceHal.teardownIface(iface.name)) {
                  Log.e(TAG, "Failed to teardown iface in supplicant on " + iface);
              }
              if (!mWificondControl.tearDownClientInterface(iface.name)) {
                  Log.e(TAG, "Failed to teardown iface in wificond on " + iface);
              }
              stopSupplicantIfNecessary();
              stopHalAndWificondIfNecessary();
          }
      }
  
      /** Helper method invoked to teardown client iface (for scan) and perform necessary cleanup */
      private void onClientInterfaceForScanDestroyed(@NonNull Iface iface) {
          synchronized (mLock) {
              mWifiMonitor.stopMonitoring(iface.name);
              if (!unregisterNetworkObserver(iface.networkObserver)) {
                  Log.e(TAG, "Failed to unregister network observer on " + iface);
              }
              if (!mWificondControl.tearDownClientInterface(iface.name)) {
                  Log.e(TAG, "Failed to teardown iface in wificond on " + iface);
              }
              stopHalAndWificondIfNecessary();
          }
      }
  
      /** Helper method invoked to teardown softAp iface and perform necessary cleanup */
      private void onSoftApInterfaceDestroyed(@NonNull Iface iface) {
          synchronized (mLock) {
              if (!unregisterNetworkObserver(iface.networkObserver)) {
                  Log.e(TAG, "Failed to unregister network observer on " + iface);
              }
              if (!mHostapdHal.removeAccessPoint(iface.name)) {
                  Log.e(TAG, "Failed to remove access point on " + iface);
              }
              if (!mWificondControl.tearDownSoftApInterface(iface.name)) {
                  Log.e(TAG, "Failed to teardown iface in wificond on " + iface);
              }
              stopHostapdIfNecessary();
              stopHalAndWificondIfNecessary();
          }
      }
  
      /** Helper method invoked to teardown iface and perform necessary cleanup */
      private void onInterfaceDestroyed(@NonNull Iface iface) {
          synchronized (mLock) {
              if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY) {
                  onClientInterfaceForConnectivityDestroyed(iface);
              } else if (iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) {
                  onClientInterfaceForScanDestroyed(iface);
              } else if (iface.type == Iface.IFACE_TYPE_AP) {
                  onSoftApInterfaceDestroyed(iface);
              }
              // Invoke the external callback.
              iface.externalListener.onDestroyed(iface.name);
          }
      }
  
      /**
       * Callback to be invoked by HalDeviceManager when an interface is destroyed.
       */
      private class InterfaceDestoyedListenerInternal
              implements HalDeviceManager.InterfaceDestroyedListener {
          /** Identifier allocated for the interface */
          private final int mInterfaceId;
  
          InterfaceDestoyedListenerInternal(int ifaceId) {
              mInterfaceId = ifaceId;
          }
  
          @Override
          public void onDestroyed(@NonNull String ifaceName) {
              synchronized (mLock) {
                  final Iface iface = mIfaceMgr.removeIface(mInterfaceId);
                  if (iface == null) {
                      if (mVerboseLoggingEnabled) {
                          Log.v(TAG, "Received iface destroyed notification on an invalid iface="
                                  + ifaceName);
                      }
                      return;
                  }
                  onInterfaceDestroyed(iface);
                  Log.i(TAG, "Successfully torn down " + iface);
              }
          }
      }
  
      /**
       * Helper method invoked to trigger the status changed callback after one of the native
       * daemon's death.
       */
      private void onNativeDaemonDeath() {
          synchronized (mLock) {
              for (StatusListener listener : mStatusListeners) {
                  listener.onStatusChanged(false);
              }
              for (StatusListener listener : mStatusListeners) {
                  listener.onStatusChanged(true);
              }
          }
      }
  
      /**
       * Death handler for the Vendor HAL daemon.
       */
      private class VendorHalDeathHandlerInternal implements VendorHalDeathEventHandler {
          @Override
          public void onDeath() {
              synchronized (mLock) {
                  Log.i(TAG, "Vendor HAL died. Cleaning up internal state.");
                  onNativeDaemonDeath();
                  mWifiMetrics.incrementNumHalCrashes();
              }
          }
      }
  
      /**
       * Death handler for the wificond daemon.
       */
      private class WificondDeathHandlerInternal implements WificondDeathEventHandler {
          @Override
          public void onDeath() {
              synchronized (mLock) {
                  Log.i(TAG, "wificond died. Cleaning up internal state.");
                  onNativeDaemonDeath();
                  mWifiMetrics.incrementNumWificondCrashes();
              }
          }
      }
  
      /**
       * Death handler for the supplicant daemon.
       */
      private class SupplicantDeathHandlerInternal implements SupplicantDeathEventHandler {
          @Override
          public void onDeath() {
              synchronized (mLock) {
                  Log.i(TAG, "wpa_supplicant died. Cleaning up internal state.");
                  onNativeDaemonDeath();
                  mWifiMetrics.incrementNumSupplicantCrashes();
              }
          }
      }
  
      /**
       * Death handler for the hostapd daemon.
       */
      private class HostapdDeathHandlerInternal implements HostapdDeathEventHandler {
          @Override
          public void onDeath() {
              synchronized (mLock) {
                  Log.i(TAG, "hostapd died. Cleaning up internal state.");
                  onNativeDaemonDeath();
                  mWifiMetrics.incrementNumHostapdCrashes();
              }
          }
      }
  
      /** Helper method invoked to handle interface change. */
      private void onInterfaceStateChanged(Iface iface, boolean isUp) {
          synchronized (mLock) {
              // Mask multiple notifications with the same state.
              if (isUp == iface.isUp) {
                  if (mVerboseLoggingEnabled) {
                      Log.v(TAG, "Interface status unchanged on " + iface + " from " + isUp
                              + ", Ignoring...");
                  }
                  return;
              }
              Log.i(TAG, "Interface state changed on " + iface + ", isUp=" + isUp);
              if (isUp) {
                  iface.externalListener.onUp(iface.name);
              } else {
                  iface.externalListener.onDown(iface.name);
                  if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY
                          || iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) {
                      mWifiMetrics.incrementNumClientInterfaceDown();
                  } else if (iface.type == Iface.IFACE_TYPE_AP) {
                      mWifiMetrics.incrementNumSoftApInterfaceDown();
                  }
              }
              iface.isUp = isUp;
          }
      }
  
      /**
       * Network observer to use for all interface up/down notifications.
       */
      private class NetworkObserverInternal extends BaseNetworkObserver {
          /** Identifier allocated for the interface */
          private final int mInterfaceId;
  
          NetworkObserverInternal(int id) {
              mInterfaceId = id;
          }
  
          /**
           * Note: We should ideally listen to
           * {@link BaseNetworkObserver#interfaceStatusChanged(String, boolean)} here. But, that
           * callback is not working currently (broken in netd). So, instead listen to link state
           * change callbacks as triggers to query the real interface state. We should get rid of
           * this workaround if we get the |interfaceStatusChanged| callback to work in netd.
           * Also, this workaround will not detect an interface up event, if the link state is
           * still down.
           */
          @Override
          public void interfaceLinkStateChanged(String ifaceName, boolean unusedIsLinkUp) {
              // This is invoked from the main system_server thread. Post to our handler.
              mHandler.post(() -> {
                  synchronized (mLock) {
                      final Iface ifaceWithId = mIfaceMgr.getIface(mInterfaceId);
                      if (ifaceWithId == null) {
                          if (mVerboseLoggingEnabled) {
                              Log.v(TAG, "Received iface link up/down notification on an invalid"
                                      + " iface=" + mInterfaceId);
                          }
                          return;
                      }
                      final Iface ifaceWithName = mIfaceMgr.getIface(ifaceName);
                      if (ifaceWithName == null || ifaceWithName != ifaceWithId) {
                          if (mVerboseLoggingEnabled) {
                              Log.v(TAG, "Received iface link up/down notification on an invalid"
                                      + " iface=" + ifaceName);
                          }
                          return;
                      }
                      onInterfaceStateChanged(ifaceWithName, isInterfaceUp(ifaceName));
                  }
              });
          }
      }
  
      /**
       * Radio mode change handler for the Vendor HAL daemon.
       */
      private class VendorHalRadioModeChangeHandlerInternal
              implements VendorHalRadioModeChangeEventHandler {
          @Override
          public void onMcc(int band) {
              synchronized (mLock) {
                  Log.i(TAG, "Device is in MCC mode now");
                  mWifiMetrics.incrementNumRadioModeChangeToMcc();
              }
          }
          @Override
          public void onScc(int band) {
              synchronized (mLock) {
                  Log.i(TAG, "Device is in SCC mode now");
                  mWifiMetrics.incrementNumRadioModeChangeToScc();
              }
          }
          @Override
          public void onSbs(int band) {
              synchronized (mLock) {
                  Log.i(TAG, "Device is in SBS mode now");
                  mWifiMetrics.incrementNumRadioModeChangeToSbs();
              }
          }
          @Override
          public void onDbs() {
              synchronized (mLock) {
                  Log.i(TAG, "Device is in DBS mode now");
                  mWifiMetrics.incrementNumRadioModeChangeToDbs();
              }
          }
      }
  
      // For devices that don't support the vendor HAL, we will not support any concurrency.
      // So simulate the HalDeviceManager behavior by triggering the destroy listener for
      // any active interface.
      private String handleIfaceCreationWhenVendorHalNotSupported(@NonNull Iface newIface) {
          synchronized (mLock) {
              Iface existingIface = mIfaceMgr.removeExistingIface(newIface.id);
              if (existingIface != null) {
                  onInterfaceDestroyed(existingIface);
                  Log.i(TAG, "Successfully torn down " + existingIface);
              }
              // Return the interface name directly from the system property.
              return mPropertyService.getString("wifi.interface", "wlan0");
          }
      }
  
      /**
       * Helper function to handle creation of STA iface.
       * For devices which do not the support the HAL, this will bypass HalDeviceManager &
       * teardown any existing iface.
       */
      private String createStaIface(@NonNull Iface iface, boolean lowPrioritySta) {
          synchronized (mLock) {
              if (mWifiVendorHal.isVendorHalSupported()) {
                  return mWifiVendorHal.createStaIface(lowPrioritySta,
                          new InterfaceDestoyedListenerInternal(iface.id));
              } else {
                  Log.i(TAG, "Vendor Hal not supported, ignoring createStaIface.");
                  return handleIfaceCreationWhenVendorHalNotSupported(iface);
              }
          }
      }
  
      /**
       * Helper function to handle creation of AP iface.
       * For devices which do not the support the HAL, this will bypass HalDeviceManager &
       * teardown any existing iface.
       */
      private String createApIface(@NonNull Iface iface) {
          synchronized (mLock) {
              if (mWifiVendorHal.isVendorHalSupported()) {
                  return mWifiVendorHal.createApIface(
                          new InterfaceDestoyedListenerInternal(iface.id));
              } else {
                  Log.i(TAG, "Vendor Hal not supported, ignoring createApIface.");
                  return handleIfaceCreationWhenVendorHalNotSupported(iface);
              }
          }
      }
  
      // For devices that don't support the vendor HAL, we will not support any concurrency.
      // So simulate the HalDeviceManager behavior by triggering the destroy listener for
      // the interface.
      private boolean handleIfaceRemovalWhenVendorHalNotSupported(@NonNull Iface iface) {
          synchronized (mLock) {
              mIfaceMgr.removeIface(iface.id);
              onInterfaceDestroyed(iface);
              Log.i(TAG, "Successfully torn down " + iface);
              return true;
          }
      }
  
      /**
       * Helper function to handle removal of STA iface.
       * For devices which do not the support the HAL, this will bypass HalDeviceManager &
       * teardown any existing iface.
       */
      private boolean removeStaIface(@NonNull Iface iface) {
          synchronized (mLock) {
              if (mWifiVendorHal.isVendorHalSupported()) {
                  return mWifiVendorHal.removeStaIface(iface.name);
              } else {
                  Log.i(TAG, "Vendor Hal not supported, ignoring removeStaIface.");
                  return handleIfaceRemovalWhenVendorHalNotSupported(iface);
              }
          }
      }
  
      /**
       * Helper function to handle removal of STA iface.
       */
      private boolean removeApIface(@NonNull Iface iface) {
          synchronized (mLock) {
              if (mWifiVendorHal.isVendorHalSupported()) {
                  return mWifiVendorHal.removeApIface(iface.name);
              } else {
                  Log.i(TAG, "Vendor Hal not supported, ignoring removeApIface.");
                  return handleIfaceRemovalWhenVendorHalNotSupported(iface);
              }
          }
      }
  
      /**
       * Initialize the native modules.
       *
       * @return true on success, false otherwise.
       */
      public boolean initialize() {
          synchronized (mLock) {
              if (!mWifiVendorHal.initialize(new VendorHalDeathHandlerInternal())) {
                  Log.e(TAG, "Failed to initialize vendor HAL");
                  return false;
              }
              if (!mWificondControl.initialize(new WificondDeathHandlerInternal())) {
                  Log.e(TAG, "Failed to initialize wificond");
                  return false;
              }
              mWifiVendorHal.registerRadioModeChangeHandler(
                      new VendorHalRadioModeChangeHandlerInternal());
              return true;
          }
      }
  
      /**
       * Callback to notify when the status of one of the native daemons
       * (wificond, wpa_supplicant & vendor HAL) changes.
       */
      public interface StatusListener {
          /**
           * @param allReady Indicates if all the native daemons are ready for operation or not.
           */
          void onStatusChanged(boolean allReady);
      }
  
      /**
       * Register a StatusListener to get notified about any status changes from the native daemons.
       *
       * It is safe to re-register the same callback object - duplicates are detected and only a
       * single copy kept.
       *
       * @param listener StatusListener listener object.
       */
      public void registerStatusListener(@NonNull StatusListener listener) {
          mStatusListeners.add(listener);
      }
  
      /**
       * Callback to notify when the associated interface is destroyed, up or down.
       */
      public interface InterfaceCallback {
          /**
           * Interface destroyed by HalDeviceManager.
           *
           * @param ifaceName Name of the iface.
           */
          void onDestroyed(String ifaceName);
  
          /**
           * Interface is up.
           *
           * @param ifaceName Name of the iface.
           */
          void onUp(String ifaceName);
  
          /**
           * Interface is down.
           *
           * @param ifaceName Name of the iface.
           */
          void onDown(String ifaceName);
      }
  
      private void initializeNwParamsForClientInterface(@NonNull String ifaceName) {
          try {
              // A runtime crash or shutting down AP mode can leave
              // IP addresses configured, and this affects
              // connectivity when supplicant starts up.
              // Ensure we have no IP addresses before a supplicant start.
              mNwManagementService.clearInterfaceAddresses(ifaceName);
  
              // Set privacy extensions
              mNwManagementService.setInterfaceIpv6PrivacyExtensions(ifaceName, true);
  
              // IPv6 is enabled only as long as access point is connected since:
              // - IPv6 addresses and routes stick around after disconnection
              // - kernel is unaware when connected and fails to start IPv6 negotiation
              // - kernel can start autoconfiguration when 802.1x is not complete
              mNwManagementService.disableIpv6(ifaceName);
          } catch (RemoteException | IllegalStateException e) {
              Log.e(TAG, "Unable to change interface settings", e);
          }
      }
  
      /**
       * Setup an interface for client mode (for connectivity) operations.
       *
       * This method configures an interface in STA mode in all the native daemons
       * (wificond, wpa_supplicant & vendor HAL).
       *
       * @param interfaceCallback Associated callback for notifying status changes for the iface.
       * @return Returns the name of the allocated interface, will be null on failure.
       */
      public String setupInterfaceForClientInConnectivityMode(
              @NonNull InterfaceCallback interfaceCallback) {
          synchronized (mLock) {
              if (!startHal()) {
                  Log.e(TAG, "Failed to start Hal");
                  mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal();
                  return null;
              }
              if (!startSupplicant()) {
                  Log.e(TAG, "Failed to start supplicant");
                  mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToSupplicant();
                  return null;
              }
              Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY);
              if (iface == null) {
                  Log.e(TAG, "Failed to allocate new STA iface");
                  return null;
              }
              iface.externalListener = interfaceCallback;
              iface.name = createStaIface(iface, /* lowPrioritySta */ false);
              if (TextUtils.isEmpty(iface.name)) {
                  Log.e(TAG, "Failed to create STA iface in vendor HAL");
                  mIfaceMgr.removeIface(iface.id);
                  mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal();
                  return null;
              }
              if (mWificondControl.setupInterfaceForClientMode(iface.name) == null) {
                  Log.e(TAG, "Failed to setup iface in wificond on " + iface);
                  teardownInterface(iface.name);
                  mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToWificond();
                  return null;
              }
              if (!mSupplicantStaIfaceHal.setupIface(iface.name)) {
                  Log.e(TAG, "Failed to setup iface in supplicant on " + iface);
                  teardownInterface(iface.name);
                  mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToSupplicant();
                  return null;
              }
              iface.networkObserver = new NetworkObserverInternal(iface.id);
              if (!registerNetworkObserver(iface.networkObserver)) {
                  Log.e(TAG, "Failed to register network observer on " + iface);
                  teardownInterface(iface.name);
                  return null;
              }
              mWifiMonitor.startMonitoring(iface.name);
              // Just to avoid any race conditions with interface state change callbacks,
              // update the interface state before we exit.
              onInterfaceStateChanged(iface, isInterfaceUp(iface.name));
              initializeNwParamsForClientInterface(iface.name);
              Log.i(TAG, "Successfully setup " + iface);
  
              iface.featureSet = getSupportedFeatureSetInternal(iface.name);
              return iface.name;
          }
      }
  
      /**
       * Setup an interface for client mode (for scan) operations.
       *
       * This method configures an interface in STA mode in the native daemons
       * (wificond, vendor HAL).
       *
       * @param interfaceCallback Associated callback for notifying status changes for the iface.
       * @return Returns the name of the allocated interface, will be null on failure.
       */
      public String setupInterfaceForClientInScanMode(
              @NonNull InterfaceCallback interfaceCallback) {
          synchronized (mLock) {
              if (!startHal()) {
                  Log.e(TAG, "Failed to start Hal");
                  mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal();
                  return null;
              }
              Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_STA_FOR_SCAN);
              if (iface == null) {
                  Log.e(TAG, "Failed to allocate new STA iface");
                  return null;
              }
              iface.externalListener = interfaceCallback;
              iface.name = createStaIface(iface, /* lowPrioritySta */ true);
              if (TextUtils.isEmpty(iface.name)) {
                  Log.e(TAG, "Failed to create iface in vendor HAL");
                  mIfaceMgr.removeIface(iface.id);
                  mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToHal();
                  return null;
              }
              if (mWificondControl.setupInterfaceForClientMode(iface.name) == null) {
                  Log.e(TAG, "Failed to setup iface in wificond=" + iface.name);
                  teardownInterface(iface.name);
                  mWifiMetrics.incrementNumSetupClientInterfaceFailureDueToWificond();
                  return null;
              }
              iface.networkObserver = new NetworkObserverInternal(iface.id);
              if (!registerNetworkObserver(iface.networkObserver)) {
                  Log.e(TAG, "Failed to register network observer for iface=" + iface.name);
                  teardownInterface(iface.name);
                  return null;
              }
              mWifiMonitor.startMonitoring(iface.name);
              // Just to avoid any race conditions with interface state change callbacks,
              // update the interface state before we exit.
              onInterfaceStateChanged(iface, isInterfaceUp(iface.name));
              Log.i(TAG, "Successfully setup " + iface);
  
              iface.featureSet = getSupportedFeatureSetInternal(iface.name);
              return iface.name;
          }
      }
  
      /**
       * Setup an interface for Soft AP mode operations.
       *
       * This method configures an interface in AP mode in all the native daemons
       * (wificond, wpa_supplicant & vendor HAL).
       *
       * @param interfaceCallback Associated callback for notifying status changes for the iface.
       * @return Returns the name of the allocated interface, will be null on failure.
       */
      public String setupInterfaceForSoftApMode(@NonNull InterfaceCallback interfaceCallback) {
          synchronized (mLock) {
              if (!startHal()) {
                  Log.e(TAG, "Failed to start Hal");
                  mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHal();
                  return null;
              }
              if (!startHostapd()) {
                  Log.e(TAG, "Failed to start hostapd");
                  mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHostapd();
                  return null;
              }
              Iface iface = mIfaceMgr.allocateIface(Iface.IFACE_TYPE_AP);
              if (iface == null) {
                  Log.e(TAG, "Failed to allocate new AP iface");
                  return null;
              }
              iface.externalListener = interfaceCallback;
              iface.name = createApIface(iface);
              if (TextUtils.isEmpty(iface.name)) {
                  Log.e(TAG, "Failed to create AP iface in vendor HAL");
                  mIfaceMgr.removeIface(iface.id);
                  mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHal();
                  return null;
              }
              if (mWificondControl.setupInterfaceForSoftApMode(iface.name) == null) {
                  Log.e(TAG, "Failed to setup iface in wificond on " + iface);
                  teardownInterface(iface.name);
                  mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToWificond();
                  return null;
              }
              iface.networkObserver = new NetworkObserverInternal(iface.id);
              if (!registerNetworkObserver(iface.networkObserver)) {
                  Log.e(TAG, "Failed to register network observer on " + iface);
                  teardownInterface(iface.name);
                  return null;
              }
              // Just to avoid any race conditions with interface state change callbacks,
              // update the interface state before we exit.
              onInterfaceStateChanged(iface, isInterfaceUp(iface.name));
              Log.i(TAG, "Successfully setup " + iface);
  
              iface.featureSet = getSupportedFeatureSetInternal(iface.name);
              return iface.name;
          }
      }
  
      /**
       *
       * Check if the interface is up or down.
       *
       * @param ifaceName Name of the interface.
       * @return true if iface is up, false if it's down or on error.
       */
      public boolean isInterfaceUp(@NonNull String ifaceName) {
          synchronized (mLock) {
              final Iface iface = mIfaceMgr.getIface(ifaceName);
              if (iface == null) {
                  Log.e(TAG, "Trying to get iface state on invalid iface=" + ifaceName);
                  return false;
              }
              InterfaceConfiguration config = null;
              try {
                  config = mNwManagementService.getInterfaceConfig(ifaceName);
              } catch (RemoteException | IllegalStateException e) {
                  Log.e(TAG, "Unable to get interface config", e);
              }
              if (config == null) {
                  return false;
              }
              return config.isUp();
          }
      }
  
      /**
       * Teardown an interface in Client/AP mode.
       *
       * This method tears down the associated interface from all the native daemons
       * (wificond, wpa_supplicant & vendor HAL).
       * Also, brings down the HAL, supplicant or hostapd as necessary.
       *
       * @param ifaceName Name of the interface.
       */
      public void teardownInterface(@NonNull String ifaceName) {
          synchronized (mLock) {
              final Iface iface = mIfaceMgr.getIface(ifaceName);
              if (iface == null) {
                  Log.e(TAG, "Trying to teardown an invalid iface=" + ifaceName);
                  return;
              }
              // Trigger the iface removal from HAL. The rest of the cleanup will be triggered
              // from the interface destroyed callback.
              if (iface.type == Iface.IFACE_TYPE_STA_FOR_CONNECTIVITY
                      || iface.type == Iface.IFACE_TYPE_STA_FOR_SCAN) {
                  if (!removeStaIface(iface)) {
                      Log.e(TAG, "Failed to remove iface in vendor HAL=" + ifaceName);
                      return;
                  }
              } else if (iface.type == Iface.IFACE_TYPE_AP) {
                  if (!removeApIface(iface)) {
                      Log.e(TAG, "Failed to remove iface in vendor HAL=" + ifaceName);
                      return;
                  }
              }
              Log.i(TAG, "Successfully initiated teardown for iface=" + ifaceName);
          }
      }
  
      /**
       * Teardown all the active interfaces.
       *
       * This method tears down the associated interfaces from all the native daemons
       * (wificond, wpa_supplicant & vendor HAL).
       * Also, brings down the HAL, supplicant or hostapd as necessary.
       */
      public void teardownAllInterfaces() {
          synchronized (mLock) {
              Iterator<Integer> ifaceIdIter = mIfaceMgr.getIfaceIdIter();
              while (ifaceIdIter.hasNext()) {
                  Iface iface = mIfaceMgr.getIface(ifaceIdIter.next());
                  ifaceIdIter.remove();
                  onInterfaceDestroyed(iface);
                  Log.i(TAG, "Successfully torn down " + iface);
              }
              Log.i(TAG, "Successfully torn down all ifaces");
          }
      }
  
      /**
       * Get name of the client interface.
       *
       * This is mainly used by external modules that needs to perform some
       * client operations on the STA interface.
       *
       * TODO(b/70932231): This may need to be reworked once we start supporting STA + STA.
       *
       * @return Interface name of any active client interface, null if no active client interface
       * exist.
       * Return Values for the different scenarios are listed below:
       * a) When there are no client interfaces, returns null.
       * b) when there is 1 client interface, returns the name of that interface.
       * c) When there are 2 or more client interface, returns the name of any client interface.
       */
      public String getClientInterfaceName() {
          synchronized (mLock) {
              return mIfaceMgr.findAnyStaIfaceName();
          }
      }
  
      /**
       * Get name of the softap interface.
       *
       * This is mainly used by external modules that needs to perform some
       * operations on the AP interface.
       *
       * TODO(b/70932231): This may need to be reworked once we start supporting AP + AP.
       *
       * @return Interface name of any active softap interface, null if no active softap interface
       * exist.
       * Return Values for the different scenarios are listed below:
       * a) When there are no softap interfaces, returns null.
       * b) when there is 1 softap interface, returns the name of that interface.
       * c) When there are 2 or more softap interface, returns the name of any softap interface.
       */
      public String getSoftApInterfaceName() {
          synchronized (mLock) {
              return mIfaceMgr.findAnyApIfaceName();
          }
      }
  
      /********************************************************
       * Wificond operations
       ********************************************************/
      /**
       * Result of a signal poll.
       */
      public static class SignalPollResult {
          // RSSI value in dBM.
          public int currentRssi;
          //Transmission bit rate in Mbps.
          public int txBitrate;
          // Association frequency in MHz.
          public int associationFrequency;
          //Last received packet bit rate in Mbps.
          public int rxBitrate;
      }
  
      /**
       * WiFi interface transimission counters.
       */
      public static class TxPacketCounters {
          // Number of successfully transmitted packets.
          public int txSucceeded;
          // Number of tramsmission failures.
          public int txFailed;
      }
  
      /**
       * Callback to notify wificond death.
       */
      public interface WificondDeathEventHandler {
          /**
           * Invoked when the wificond dies.
           */
          void onDeath();
      }
  
      /**
       * Request signal polling to wificond.
       *
       * @param ifaceName Name of the interface.
       * Returns an SignalPollResult object.
       * Returns null on failure.
       */
      public SignalPollResult signalPoll(@NonNull String ifaceName) {
          return mWificondControl.signalPoll(ifaceName);
      }
  
      /**
       * Fetch TX packet counters on current connection from wificond.
       * @param ifaceName Name of the interface.
       * Returns an TxPacketCounters object.
       * Returns null on failure.
       */
      public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) {
          return mWificondControl.getTxPacketCounters(ifaceName);
      }
  
      /**
       * Query the list of valid frequencies for the provided band.
       * The result depends on the on the country code that has been set.
       *
       * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
       * The following bands are supported:
       * WifiScanner.WIFI_BAND_24_GHZ
       * WifiScanner.WIFI_BAND_5_GHZ
       * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
       * @return frequencies vector of valid frequencies (MHz), or null for error.
       * @throws IllegalArgumentException if band is not recognized.
       */
      public int [] getChannelsForBand(int band) {
          return mWificondControl.getChannelsForBand(band);
      }
  
      /**
       * Start a scan using wificond for the given parameters.
       * @param ifaceName Name of the interface.
       * @param scanType Type of scan to perform. One of {@link ScanSettings#SCAN_TYPE_LOW_LATENCY},
       * {@link ScanSettings#SCAN_TYPE_LOW_POWER} or {@link ScanSettings#SCAN_TYPE_HIGH_ACCURACY}.
       * @param freqs list of frequencies to scan for, if null scan all supported channels.
       * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
       * @return Returns true on success.
       */
      public boolean scan(
              @NonNull String ifaceName, int scanType, Set<Integer> freqs,
              List<String> hiddenNetworkSSIDs) {
          return mWificondControl.scan(ifaceName, scanType, freqs, hiddenNetworkSSIDs);
      }
  
      /**
       * Fetch the latest scan result from kernel via wificond.
       * @param ifaceName Name of the interface.
       * @return Returns an ArrayList of ScanDetail.
       * Returns an empty ArrayList on failure.
       */
      public ArrayList<ScanDetail> getScanResults(@NonNull String ifaceName) {
          return mWificondControl.getScanResults(
                  ifaceName, WificondControl.SCAN_TYPE_SINGLE_SCAN);
      }
  
      /**
       * Fetch the latest scan result from kernel via wificond.
       * @param ifaceName Name of the interface.
       * @return Returns an ArrayList of ScanDetail.
       * Returns an empty ArrayList on failure.
       */
      public ArrayList<ScanDetail> getPnoScanResults(@NonNull String ifaceName) {
          return mWificondControl.getScanResults(ifaceName, WificondControl.SCAN_TYPE_PNO_SCAN);
      }
  
      /**
       * Start PNO scan.
       * @param ifaceName Name of the interface.
       * @param pnoSettings Pno scan configuration.
       * @return true on success.
       */
      public boolean startPnoScan(@NonNull String ifaceName, PnoSettings pnoSettings) {
          return mWificondControl.startPnoScan(ifaceName, pnoSettings);
      }
  
      /**
       * Stop PNO scan.
       * @param ifaceName Name of the interface.
       * @return true on success.
       */
      public boolean stopPnoScan(@NonNull String ifaceName) {
          return mWificondControl.stopPnoScan(ifaceName);
      }
  
      /**
       * Callback to notify the results of a
       * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()} call.
       * Note: no callbacks will be triggered if the iface dies while sending a frame.
       */
      public interface SendMgmtFrameCallback {
          /**
           * Called when the management frame was successfully sent and ACKed by the recipient.
           * @param elapsedTimeMs The elapsed time between when the management frame was sent and when
           *                      the ACK was processed, in milliseconds, as measured by wificond.
           *                      This includes the time that the send frame spent queuing before it
           *                      was sent, any firmware retries, and the time the received ACK spent
           *                      queuing before it was processed.
           */
          void onAck(int elapsedTimeMs);
  
          /**
           * Called when the send failed.
           * @param reason The error code for the failure.
           */
          void onFailure(@SendMgmtFrameError int reason);
      }
  
      @Retention(RetentionPolicy.SOURCE)
      @IntDef(prefix = {"SEND_MGMT_FRAME_ERROR_"},
              value = {SEND_MGMT_FRAME_ERROR_UNKNOWN,
                      SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED,
                      SEND_MGMT_FRAME_ERROR_NO_ACK,
                      SEND_MGMT_FRAME_ERROR_TIMEOUT,
                      SEND_MGMT_FRAME_ERROR_ALREADY_STARTED})
      public @interface SendMgmtFrameError {}
  
      // Send management frame error codes
  
      /**
       * Unknown error occurred during call to
       * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}.
       */
      public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1;
  
      /**
       * Specifying the MCS rate in
       * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()} is not
       * supported by this device.
       */
      public static final int SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED = 2;
  
      /**
       * Driver reported that no ACK was received for the frame transmitted using
       * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}.
       */
      public static final int SEND_MGMT_FRAME_ERROR_NO_ACK = 3;
  
      /**
       * Error code for when the driver fails to report on the status of the frame sent by
       * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}
       * after {@link WificondControl#SEND_MGMT_FRAME_TIMEOUT_MS} milliseconds.
       */
      public static final int SEND_MGMT_FRAME_ERROR_TIMEOUT = 4;
  
      /**
       * An existing call to
       * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}
       * is in progress. Another frame cannot be sent until the first call completes.
       */
      public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5;
  
      /**
       * Sends an arbitrary 802.11 management frame on the current channel.
       *
       * @param ifaceName Name of the interface.
       * @param frame Bytes of the 802.11 management frame to be sent, including the header, but not
       *              including the frame check sequence (FCS).
       * @param callback A callback triggered when the transmitted frame is ACKed or the transmission
       *                 fails.
       * @param mcs The MCS index that the frame will be sent at. If mcs < 0, the driver will select
       *            the rate automatically. If the device does not support sending the frame at a
       *            specified MCS rate, the transmission will be aborted and
       *            {@link SendMgmtFrameCallback#onFailure(int)} will be called with reason
       *            {@link #SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED}.
       */
      public void sendMgmtFrame(@NonNull String ifaceName, @NonNull byte[] frame,
              @NonNull SendMgmtFrameCallback callback, int mcs) {
          mWificondControl.sendMgmtFrame(ifaceName, frame, callback, mcs);
      }
  
      /**
       * Sends a probe request to the AP and waits for a response in order to determine whether
       * there is connectivity between the device and AP.
       *
       * @param ifaceName Name of the interface.
       * @param receiverMac the MAC address of the AP that the probe request will be sent to.
       * @param callback callback triggered when the probe was ACKed by the AP, or when
       *                an error occurs after the link probe was started.
       * @param mcs The MCS index that this probe will be sent at. If mcs < 0, the driver will select
       *            the rate automatically. If the device does not support sending the frame at a
       *            specified MCS rate, the transmission will be aborted and
       *            {@link SendMgmtFrameCallback#onFailure(int)} will be called with reason
       *            {@link #SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED}.
       */
      public void probeLink(@NonNull String ifaceName, @NonNull MacAddress receiverMac,
              @NonNull SendMgmtFrameCallback callback, int mcs) {
          if (callback == null) {
              Log.e(TAG, "callback cannot be null!");
              return;
          }
  
          if (receiverMac == null) {
              Log.e(TAG, "Receiver MAC address cannot be null!");
              callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN);
              return;
          }
  
          String senderMacStr = getMacAddress(ifaceName);
          if (senderMacStr == null) {
              Log.e(TAG, "Failed to get this device's MAC Address");
              callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN);
              return;
          }
  
          byte[] frame = buildProbeRequestFrame(
                  receiverMac.toByteArray(),
                  NativeUtil.macAddressToByteArray(senderMacStr));
          sendMgmtFrame(ifaceName, frame, callback, mcs);
      }
  
      // header = 24 bytes, minimal body = 2 bytes, no FCS (will be added by driver)
      private static final int BASIC_PROBE_REQUEST_FRAME_SIZE = 24 + 2;
  
      private byte[] buildProbeRequestFrame(byte[] receiverMac, byte[] transmitterMac) {
          ByteBuffer frame = ByteBuffer.allocate(BASIC_PROBE_REQUEST_FRAME_SIZE);
          // ByteBuffer is big endian by default, switch to little endian
          frame.order(ByteOrder.LITTLE_ENDIAN);
  
          // Protocol version = 0, Type = management, Subtype = Probe Request
          frame.put((byte) 0x40);
  
          // no flags set
          frame.put((byte) 0x00);
  
          // duration = 60 microseconds. Note: this is little endian
          // Note: driver should calculate the duration and replace it before sending, putting a
          // reasonable default value here just in case.
          frame.putShort((short) 0x3c);
  
          // receiver/destination MAC address byte array
          frame.put(receiverMac);
          // sender MAC address byte array
          frame.put(transmitterMac);
          // BSSID (same as receiver address since we are sending to the AP)
          frame.put(receiverMac);
  
          // Generate random sequence number, fragment number = 0
          // Note: driver should replace the sequence number with the correct number that is
          // incremented from the last used sequence number. Putting a random sequence number as a
          // default here just in case.
          // bit 0 is least significant bit, bit 15 is most significant bit
          // bits [0, 7] go in byte 0
          // bits [8, 15] go in byte 1
          // bits [0, 3] represent the fragment number (which is 0)
          // bits [4, 15] represent the sequence number (which is random)
          // clear bits [0, 3] to set fragment number = 0
          short sequenceAndFragmentNumber = (short) (mRandom.nextInt() & 0xfff0);
          frame.putShort(sequenceAndFragmentNumber);
  
          // NL80211 rejects frames with an empty body, so we just need to put a placeholder
          // information element.
          // Tag for SSID
          frame.put((byte) 0x00);
          // Represents broadcast SSID. Not accurate, but works as placeholder.
          frame.put((byte) 0x00);
  
          return frame.array();
      }
  
      /**
       * Callbacks for SoftAp interface.
       */
      public interface SoftApListener {
          /**
           * Invoked when there is some fatal failure in the lower layers.
           */
          void onFailure();
  
          /**
           * Invoked when the number of associated stations changes.
           */
          void onNumAssociatedStationsChanged(int numStations);
  
          /**
           * Invoked when the channel switch event happens.
           */
          void onSoftApChannelSwitched(int frequency, int bandwidth);
      }
  
      private static final int CONNECT_TO_HOSTAPD_RETRY_INTERVAL_MS = 100;
      private static final int CONNECT_TO_HOSTAPD_RETRY_TIMES = 50;
      /**
       * This method is called to wait for establishing connection to hostapd.
       *
       * @return true if connection is established, false otherwise.
       */
      private boolean startAndWaitForHostapdConnection() {
          // Start initialization if not already started.
          if (!mHostapdHal.isInitializationStarted()
                  && !mHostapdHal.initialize()) {
              return false;
          }
          if (!mHostapdHal.startDaemon()) {
              Log.e(TAG, "Failed to startup hostapd");
              return false;
          }
          boolean connected = false;
          int connectTries = 0;
          while (!connected && connectTries++ < CONNECT_TO_HOSTAPD_RETRY_TIMES) {
              // Check if the initialization is complete.
              connected = mHostapdHal.isInitializationComplete();
              if (connected) {
                  break;
              }
              try {
                  Thread.sleep(CONNECT_TO_HOSTAPD_RETRY_INTERVAL_MS);
              } catch (InterruptedException ignore) {
              }
          }
          return connected;
      }
  
      /**
       * Start Soft AP operation using the provided configuration.
       *
       * @param ifaceName Name of the interface.
       * @param config Configuration to use for the soft ap created.
       * @param listener Callback for AP events.
       * @return true on success, false otherwise.
       */
      public boolean startSoftAp(
              @NonNull String ifaceName, WifiConfiguration config, SoftApListener listener) {
          if (!mWificondControl.registerApListener(ifaceName, listener)) {
              Log.e(TAG, "Failed to register ap listener");
              return false;
          }
          if (!mHostapdHal.addAccessPoint(ifaceName, config, listener)) {
              Log.e(TAG, "Failed to add acccess point");
              mWifiMetrics.incrementNumSetupSoftApInterfaceFailureDueToHostapd();
              return false;
          }
          return true;
      }
  
      /**
       * Set MAC address of the given interface
       * @param interfaceName Name of the interface
       * @param mac Mac address to change into
       * @return true on success
       */
      public boolean setMacAddress(String interfaceName, MacAddress mac) {
          // TODO(b/72459123): Suppress interface down/up events from this call
          return mWifiVendorHal.setMacAddress(interfaceName, mac);
      }
  
      /**
       * Get the factory MAC address of the given interface
       * @param interfaceName Name of the interface.
       * @return factory MAC address, or null on a failed call or if feature is unavailable.
       */
      public MacAddress getFactoryMacAddress(@NonNull String interfaceName) {
          return mWifiVendorHal.getFactoryMacAddress(interfaceName);
      }
  
      /********************************************************
       * Hostapd operations
       ********************************************************/
  
      /**
       * Callback to notify hostapd death.
       */
      public interface HostapdDeathEventHandler {
          /**
           * Invoked when the supplicant dies.
           */
          void onDeath();
      }
  
      /********************************************************
       * Supplicant operations
       ********************************************************/
  
      /**
       * Callback to notify supplicant death.
       */
      public interface SupplicantDeathEventHandler {
          /**
           * Invoked when the supplicant dies.
           */
          void onDeath();
      }
  
      /**
       * Set supplicant log level
       *
       * @param turnOnVerbose Whether to turn on verbose logging or not.
       */
      public void setSupplicantLogLevel(boolean turnOnVerbose) {
          mSupplicantStaIfaceHal.setLogLevel(turnOnVerbose);
      }
  
      /**
       * Trigger a reconnection if the iface is disconnected.
       *
       * @param ifaceName Name of the interface.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean reconnect(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.reconnect(ifaceName);
      }
  
      /**
       * Trigger a reassociation even if the iface is currently connected.
       *
       * @param ifaceName Name of the interface.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean reassociate(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.reassociate(ifaceName);
      }
  
      /**
       * Trigger a disconnection from the currently connected network.
       *
       * @param ifaceName Name of the interface.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean disconnect(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.disconnect(ifaceName);
      }
  
      /**
       * Makes a callback to HIDL to getMacAddress from supplicant
       *
       * @param ifaceName Name of the interface.
       * @return string containing the MAC address, or null on a failed call
       */
      public String getMacAddress(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.getMacAddress(ifaceName);
      }
  
      public static final int RX_FILTER_TYPE_V4_MULTICAST = 0;
      public static final int RX_FILTER_TYPE_V6_MULTICAST = 1;
      /**
       * Start filtering out Multicast V4 packets
       * @param ifaceName Name of the interface.
       * @return {@code true} if the operation succeeded, {@code false} otherwise
       *
       * Multicast filtering rules work as follows:
       *
       * The driver can filter multicast (v4 and/or v6) and broadcast packets when in
       * a power optimized mode (typically when screen goes off).
       *
       * In order to prevent the driver from filtering the multicast/broadcast packets, we have to
       * add a DRIVER RXFILTER-ADD rule followed by DRIVER RXFILTER-START to make the rule effective
       *
       * DRIVER RXFILTER-ADD Num
       *   where Num = 0 - Unicast, 1 - Broadcast, 2 - Mutil4 or 3 - Multi6
       *
       * and DRIVER RXFILTER-START
       * In order to stop the usage of these rules, we do
       *
       * DRIVER RXFILTER-STOP
       * DRIVER RXFILTER-REMOVE Num
       *   where Num is as described for RXFILTER-ADD
       *
       * The  SETSUSPENDOPT driver command overrides the filtering rules
       */
      public boolean startFilteringMulticastV4Packets(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.stopRxFilter(ifaceName)
                  && mSupplicantStaIfaceHal.removeRxFilter(
                          ifaceName, RX_FILTER_TYPE_V4_MULTICAST)
                  && mSupplicantStaIfaceHal.startRxFilter(ifaceName);
      }
  
      /**
       * Stop filtering out Multicast V4 packets.
       * @param ifaceName Name of the interface.
       * @return {@code true} if the operation succeeded, {@code false} otherwise
       */
      public boolean stopFilteringMulticastV4Packets(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.stopRxFilter(ifaceName)
                  && mSupplicantStaIfaceHal.addRxFilter(
                          ifaceName, RX_FILTER_TYPE_V4_MULTICAST)
                  && mSupplicantStaIfaceHal.startRxFilter(ifaceName);
      }
  
      /**
       * Start filtering out Multicast V6 packets
       * @param ifaceName Name of the interface.
       * @return {@code true} if the operation succeeded, {@code false} otherwise
       */
      public boolean startFilteringMulticastV6Packets(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.stopRxFilter(ifaceName)
                  && mSupplicantStaIfaceHal.removeRxFilter(
                          ifaceName, RX_FILTER_TYPE_V6_MULTICAST)
                  && mSupplicantStaIfaceHal.startRxFilter(ifaceName);
      }
  
      /**
       * Stop filtering out Multicast V6 packets.
       * @param ifaceName Name of the interface.
       * @return {@code true} if the operation succeeded, {@code false} otherwise
       */
      public boolean stopFilteringMulticastV6Packets(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.stopRxFilter(ifaceName)
                  && mSupplicantStaIfaceHal.addRxFilter(
                          ifaceName, RX_FILTER_TYPE_V6_MULTICAST)
                  && mSupplicantStaIfaceHal.startRxFilter(ifaceName);
      }
  
      public static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED  = 0;
      public static final int BLUETOOTH_COEXISTENCE_MODE_DISABLED = 1;
      public static final int BLUETOOTH_COEXISTENCE_MODE_SENSE    = 2;
      /**
       * Sets the bluetooth coexistence mode.
       *
       * @param ifaceName Name of the interface.
       * @param mode One of {@link #BLUETOOTH_COEXISTENCE_MODE_DISABLED},
       *            {@link #BLUETOOTH_COEXISTENCE_MODE_ENABLED}, or
       *            {@link #BLUETOOTH_COEXISTENCE_MODE_SENSE}.
       * @return Whether the mode was successfully set.
       */
      public boolean setBluetoothCoexistenceMode(@NonNull String ifaceName, int mode) {
          return mSupplicantStaIfaceHal.setBtCoexistenceMode(ifaceName, mode);
      }
  
      /**
       * Enable or disable Bluetooth coexistence scan mode. When this mode is on,
       * some of the low-level scan parameters used by the driver are changed to
       * reduce interference with A2DP streaming.
       *
       * @param ifaceName Name of the interface.
       * @param setCoexScanMode whether to enable or disable this mode
       * @return {@code true} if the command succeeded, {@code false} otherwise.
       */
      public boolean setBluetoothCoexistenceScanMode(
              @NonNull String ifaceName, boolean setCoexScanMode) {
          return mSupplicantStaIfaceHal.setBtCoexistenceScanModeEnabled(
                  ifaceName, setCoexScanMode);
      }
  
      /**
       * Enable or disable suspend mode optimizations.
       *
       * @param ifaceName Name of the interface.
       * @param enabled true to enable, false otherwise.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean setSuspendOptimizations(@NonNull String ifaceName, boolean enabled) {
          return mSupplicantStaIfaceHal.setSuspendModeEnabled(ifaceName, enabled);
      }
  
      /**
       * Set country code.
       *
       * @param ifaceName Name of the interface.
       * @param countryCode 2 byte ASCII string. For ex: US, CA.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean setCountryCode(@NonNull String ifaceName, String countryCode) {
          return mSupplicantStaIfaceHal.setCountryCode(ifaceName, countryCode);
      }
  
      /**
       * Initiate TDLS discover and setup or teardown with the specified peer.
       *
       * @param ifaceName Name of the interface.
       * @param macAddr MAC Address of the peer.
       * @param enable true to start discovery and setup, false to teardown.
       */
      public void startTdls(@NonNull String ifaceName, String macAddr, boolean enable) {
          if (enable) {
              mSupplicantStaIfaceHal.initiateTdlsDiscover(ifaceName, macAddr);
              mSupplicantStaIfaceHal.initiateTdlsSetup(ifaceName, macAddr);
          } else {
              mSupplicantStaIfaceHal.initiateTdlsTeardown(ifaceName, macAddr);
          }
      }
  
      /**
       * Start WPS pin display operation with the specified peer.
       *
       * @param ifaceName Name of the interface.
       * @param bssid BSSID of the peer.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean startWpsPbc(@NonNull String ifaceName, String bssid) {
          return mSupplicantStaIfaceHal.startWpsPbc(ifaceName, bssid);
      }
  
      /**
       * Start WPS pin keypad operation with the specified pin.
       *
       * @param ifaceName Name of the interface.
       * @param pin Pin to be used.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean startWpsPinKeypad(@NonNull String ifaceName, String pin) {
          return mSupplicantStaIfaceHal.startWpsPinKeypad(ifaceName, pin);
      }
  
      /**
       * Start WPS pin display operation with the specified peer.
       *
       * @param ifaceName Name of the interface.
       * @param bssid BSSID of the peer.
       * @return new pin generated on success, null otherwise.
       */
      public String startWpsPinDisplay(@NonNull String ifaceName, String bssid) {
          return mSupplicantStaIfaceHal.startWpsPinDisplay(ifaceName, bssid);
      }
  
      /**
       * Sets whether to use external sim for SIM/USIM processing.
       *
       * @param ifaceName Name of the interface.
       * @param external true to enable, false otherwise.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean setExternalSim(@NonNull String ifaceName, boolean external) {
          return mSupplicantStaIfaceHal.setExternalSim(ifaceName, external);
      }
  
      /**
       * Sim auth response types.
       */
      public static final String SIM_AUTH_RESP_TYPE_GSM_AUTH = "GSM-AUTH";
      public static final String SIM_AUTH_RESP_TYPE_UMTS_AUTH = "UMTS-AUTH";
      public static final String SIM_AUTH_RESP_TYPE_UMTS_AUTS = "UMTS-AUTS";
  
      /**
       * EAP-SIM Error Codes
       */
      public static final int EAP_SIM_NOT_SUBSCRIBED = 1031;
      public static final int EAP_SIM_VENDOR_SPECIFIC_CERT_EXPIRED = 16385;
  
      /**
       * Send the sim auth response for the currently configured network.
       *
       * @param ifaceName Name of the interface.
       * @param type |GSM-AUTH|, |UMTS-AUTH| or |UMTS-AUTS|.
       * @param response Response params.
       * @return true if succeeds, false otherwise.
       */
      public boolean simAuthResponse(
              @NonNull String ifaceName, int id, String type, String response) {
          if (SIM_AUTH_RESP_TYPE_GSM_AUTH.equals(type)) {
              return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthResponse(
                      ifaceName, response);
          } else if (SIM_AUTH_RESP_TYPE_UMTS_AUTH.equals(type)) {
              return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthResponse(
                      ifaceName, response);
          } else if (SIM_AUTH_RESP_TYPE_UMTS_AUTS.equals(type)) {
              return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAutsResponse(
                      ifaceName, response);
          } else {
              return false;
          }
      }
  
      /**
       * Send the eap sim gsm auth failure for the currently configured network.
       *
       * @param ifaceName Name of the interface.
       * @return true if succeeds, false otherwise.
       */
      public boolean simAuthFailedResponse(@NonNull String ifaceName, int id) {
          return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthFailure(ifaceName);
      }
  
      /**
       * Send the eap sim umts auth failure for the currently configured network.
       *
       * @param ifaceName Name of the interface.
       * @return true if succeeds, false otherwise.
       */
      public boolean umtsAuthFailedResponse(@NonNull String ifaceName, int id) {
          return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthFailure(ifaceName);
      }
  
      /**
       * Send the eap identity response for the currently configured network.
       *
       * @param ifaceName Name of the interface.
       * @param unencryptedResponse String to send.
       * @param encryptedResponse String to send.
       * @return true if succeeds, false otherwise.
       */
      public boolean simIdentityResponse(@NonNull String ifaceName, int id,
                                         String unencryptedResponse, String encryptedResponse) {
          return mSupplicantStaIfaceHal.sendCurrentNetworkEapIdentityResponse(ifaceName,
                  unencryptedResponse, encryptedResponse);
      }
  
      /**
       * This get anonymous identity from supplicant and returns it as a string.
       *
       * @param ifaceName Name of the interface.
       * @return anonymous identity string if succeeds, null otherwise.
       */
      public String getEapAnonymousIdentity(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(ifaceName);
      }
  
      /**
       * Start WPS pin registrar operation with the specified peer and pin.
       *
       * @param ifaceName Name of the interface.
       * @param bssid BSSID of the peer.
       * @param pin Pin to be used.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean startWpsRegistrar(@NonNull String ifaceName, String bssid, String pin) {
          return mSupplicantStaIfaceHal.startWpsRegistrar(ifaceName, bssid, pin);
      }
  
      /**
       * Cancels any ongoing WPS requests.
       *
       * @param ifaceName Name of the interface.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean cancelWps(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.cancelWps(ifaceName);
      }
  
      /**
       * Set WPS device name.
       *
       * @param ifaceName Name of the interface.
       * @param name String to be set.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean setDeviceName(@NonNull String ifaceName, String name) {
          return mSupplicantStaIfaceHal.setWpsDeviceName(ifaceName, name);
      }
  
      /**
       * Set WPS device type.
       *
       * @param ifaceName Name of the interface.
       * @param type Type specified as a string. Used format: <categ>-<OUI>-<subcateg>
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean setDeviceType(@NonNull String ifaceName, String type) {
          return mSupplicantStaIfaceHal.setWpsDeviceType(ifaceName, type);
      }
  
      /**
       * Set WPS config methods
       *
       * @param cfg List of config methods.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean setConfigMethods(@NonNull String ifaceName, String cfg) {
          return mSupplicantStaIfaceHal.setWpsConfigMethods(ifaceName, cfg);
      }
  
      /**
       * Set WPS manufacturer.
       *
       * @param ifaceName Name of the interface.
       * @param value String to be set.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean setManufacturer(@NonNull String ifaceName, String value) {
          return mSupplicantStaIfaceHal.setWpsManufacturer(ifaceName, value);
      }
  
      /**
       * Set WPS model name.
       *
       * @param ifaceName Name of the interface.
       * @param value String to be set.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean setModelName(@NonNull String ifaceName, String value) {
          return mSupplicantStaIfaceHal.setWpsModelName(ifaceName, value);
      }
  
      /**
       * Set WPS model number.
       *
       * @param ifaceName Name of the interface.
       * @param value String to be set.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean setModelNumber(@NonNull String ifaceName, String value) {
          return mSupplicantStaIfaceHal.setWpsModelNumber(ifaceName, value);
      }
  
      /**
       * Set WPS serial number.
       *
       * @param ifaceName Name of the interface.
       * @param value String to be set.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean setSerialNumber(@NonNull String ifaceName, String value) {
          return mSupplicantStaIfaceHal.setWpsSerialNumber(ifaceName, value);
      }
  
      /**
       * Enable or disable power save mode.
       *
       * @param ifaceName Name of the interface.
       * @param enabled true to enable, false to disable.
       */
      public void setPowerSave(@NonNull String ifaceName, boolean enabled) {
          mSupplicantStaIfaceHal.setPowerSave(ifaceName, enabled);
      }
  
      /**
       * Enable or disable low latency mode.
       *
       * @param enabled true to enable, false to disable.
       * @return true on success, false on failure
       */
      public boolean setLowLatencyMode(boolean enabled) {
          return mWifiVendorHal.setLowLatencyMode(enabled);
      }
  
      /**
       * Set concurrency priority between P2P & STA operations.
       *
       * @param isStaHigherPriority Set to true to prefer STA over P2P during concurrency operations,
       *                            false otherwise.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean setConcurrencyPriority(boolean isStaHigherPriority) {
          return mSupplicantStaIfaceHal.setConcurrencyPriority(isStaHigherPriority);
      }
  
      /**
       * Enable/Disable auto reconnect functionality in wpa_supplicant.
       *
       * @param ifaceName Name of the interface.
       * @param enable true to enable auto reconnecting, false to disable.
       * @return true if request is sent successfully, false otherwise.
       */
      public boolean enableStaAutoReconnect(@NonNull String ifaceName, boolean enable) {
          return mSupplicantStaIfaceHal.enableAutoReconnect(ifaceName, enable);
      }
  
      /**
       * Migrate all the configured networks from wpa_supplicant.
       *
       * @param ifaceName Name of the interface.
       * @param configs       Map of configuration key to configuration objects corresponding to all
       *                      the networks.
       * @param networkExtras Map of extra configuration parameters stored in wpa_supplicant.conf
       * @return Max priority of all the configs.
       */
      public boolean migrateNetworksFromSupplicant(
              @NonNull String ifaceName, Map<String, WifiConfiguration> configs,
              SparseArray<Map<String, String>> networkExtras) {
          return mSupplicantStaIfaceHal.loadNetworks(ifaceName, configs, networkExtras);
      }
  
      /**
       * Add the provided network configuration to wpa_supplicant and initiate connection to it.
       * This method does the following:
       * 1. Abort any ongoing scan to unblock the connection request.
       * 2. Remove any existing network in wpa_supplicant(This implicitly triggers disconnect).
       * 3. Add a new network to wpa_supplicant.
       * 4. Save the provided configuration to wpa_supplicant.
       * 5. Select the new network in wpa_supplicant.
       * 6. Triggers reconnect command to wpa_supplicant.
       *
       * @param ifaceName Name of the interface.
       * @param configuration WifiConfiguration parameters for the provided network.
       * @return {@code true} if it succeeds, {@code false} otherwise
       */
      public boolean connectToNetwork(@NonNull String ifaceName, WifiConfiguration configuration) {
          // Abort ongoing scan before connect() to unblock connection request.
          mWificondControl.abortScan(ifaceName);
          return mSupplicantStaIfaceHal.connectToNetwork(ifaceName, configuration);
      }
  
      /**
       * Initiates roaming to the already configured network in wpa_supplicant. If the network
       * configuration provided does not match the already configured network, then this triggers
       * a new connection attempt (instead of roam).
       * 1. Abort any ongoing scan to unblock the roam request.
       * 2. First check if we're attempting to connect to the same network as we currently have
       * configured.
       * 3. Set the new bssid for the network in wpa_supplicant.
       * 4. Triggers reassociate command to wpa_supplicant.
       *
       * @param ifaceName Name of the interface.
       * @param configuration WifiConfiguration parameters for the provided network.
       * @return {@code true} if it succeeds, {@code false} otherwise
       */
      public boolean roamToNetwork(@NonNull String ifaceName, WifiConfiguration configuration) {
          // Abort ongoing scan before connect() to unblock roaming request.
          mWificondControl.abortScan(ifaceName);
          return mSupplicantStaIfaceHal.roamToNetwork(ifaceName, configuration);
      }
  
      /**
       * Remove all the networks.
       *
       * @param ifaceName Name of the interface.
       * @return {@code true} if it succeeds, {@code false} otherwise
       */
      public boolean removeAllNetworks(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.removeAllNetworks(ifaceName);
      }
  
      /**
       * Set the BSSID for the currently configured network in wpa_supplicant.
       *
       * @param ifaceName Name of the interface.
       * @return true if successful, false otherwise.
       */
      public boolean setConfiguredNetworkBSSID(@NonNull String ifaceName, String bssid) {
          return mSupplicantStaIfaceHal.setCurrentNetworkBssid(ifaceName, bssid);
      }
  
      /**
       * Initiate ANQP query.
       *
       * @param ifaceName Name of the interface.
       * @param bssid BSSID of the AP to be queried
       * @param anqpIds Set of anqp IDs.
       * @param hs20Subtypes Set of HS20 subtypes.
       * @return true on success, false otherwise.
       */
      public boolean requestAnqp(
              @NonNull String ifaceName, String bssid, Set<Integer> anqpIds,
              Set<Integer> hs20Subtypes) {
          if (bssid == null || ((anqpIds == null || anqpIds.isEmpty())
                  && (hs20Subtypes == null || hs20Subtypes.isEmpty()))) {
              Log.e(TAG, "Invalid arguments for ANQP request.");
              return false;
          }
          ArrayList<Short> anqpIdList = new ArrayList<>();
          for (Integer anqpId : anqpIds) {
              anqpIdList.add(anqpId.shortValue());
          }
          ArrayList<Integer> hs20SubtypeList = new ArrayList<>();
          hs20SubtypeList.addAll(hs20Subtypes);
          return mSupplicantStaIfaceHal.initiateAnqpQuery(
                  ifaceName, bssid, anqpIdList, hs20SubtypeList);
      }
  
      /**
       * Request a passpoint icon file |filename| from the specified AP |bssid|.
       *
       * @param ifaceName Name of the interface.
       * @param bssid BSSID of the AP
       * @param fileName name of the icon file
       * @return true if request is sent successfully, false otherwise
       */
      public boolean requestIcon(@NonNull String ifaceName, String  bssid, String fileName) {
          if (bssid == null || fileName == null) {
              Log.e(TAG, "Invalid arguments for Icon request.");
              return false;
          }
          return mSupplicantStaIfaceHal.initiateHs20IconQuery(ifaceName, bssid, fileName);
      }
  
      /**
       * Get the currently configured network's WPS NFC token.
       *
       * @param ifaceName Name of the interface.
       * @return Hex string corresponding to the WPS NFC token.
       */
      public String getCurrentNetworkWpsNfcConfigurationToken(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.getCurrentNetworkWpsNfcConfigurationToken(ifaceName);
      }
  
      /** Remove the request |networkId| from supplicant if it's the current network,
       * if the current configured network matches |networkId|.
       *
       * @param ifaceName Name of the interface.
       * @param networkId network id of the network to be removed from supplicant.
       */
      public void removeNetworkIfCurrent(@NonNull String ifaceName, int networkId) {
          mSupplicantStaIfaceHal.removeNetworkIfCurrent(ifaceName, networkId);
      }
  
      /*
       * DPP
       */
  
      /**
       * Adds a DPP peer URI to the URI list.
       *
       * @param ifaceName Interface name
       * @param uri Bootstrap (URI) string (e.g. DPP:....)
       * @return ID, or -1 for failure
       */
      public int addDppPeerUri(@NonNull String ifaceName, @NonNull String uri) {
          return mSupplicantStaIfaceHal.addDppPeerUri(ifaceName, uri);
      }
  
      /**
       * Removes a DPP URI to the URI list given an ID.
       *
       * @param ifaceName Interface name
       * @param bootstrapId Bootstrap (URI) ID
       * @return true when operation is successful, or false for failure
       */
      public boolean removeDppUri(@NonNull String ifaceName, int bootstrapId)  {
          return mSupplicantStaIfaceHal.removeDppUri(ifaceName, bootstrapId);
      }
  
      /**
       * Stops/aborts DPP Initiator request
       *
       * @param ifaceName Interface name
       * @return true when operation is successful, or false for failure
       */
      public boolean stopDppInitiator(@NonNull String ifaceName)  {
          return mSupplicantStaIfaceHal.stopDppInitiator(ifaceName);
      }
  
      /**
       * Starts DPP Configurator-Initiator request
       *
       * @param ifaceName Interface name
       * @param peerBootstrapId Peer's bootstrap (URI) ID
       * @param ownBootstrapId Own bootstrap (URI) ID - Optional, 0 for none
       * @param ssid SSID of the selected network
       * @param password Password of the selected network, or
       * @param psk PSK of the selected network in hexadecimal representation
       * @param netRole The network role of the enrollee (STA or AP)
       * @param securityAkm Security AKM to use: PSK, SAE
       * @return true when operation is successful, or false for failure
       */
      public boolean startDppConfiguratorInitiator(@NonNull String ifaceName, int peerBootstrapId,
              int ownBootstrapId, @NonNull String ssid, String password, String psk,
              int netRole, int securityAkm)  {
          return mSupplicantStaIfaceHal.startDppConfiguratorInitiator(ifaceName, peerBootstrapId,
                  ownBootstrapId, ssid, password, psk, netRole, securityAkm);
      }
  
      /**
       * Starts DPP Enrollee-Initiator request
       *
       * @param ifaceName Interface name
       * @param peerBootstrapId Peer's bootstrap (URI) ID
       * @param ownBootstrapId Own bootstrap (URI) ID - Optional, 0 for none
       * @return true when operation is successful, or false for failure
       */
      public boolean startDppEnrolleeInitiator(@NonNull String ifaceName, int peerBootstrapId,
              int ownBootstrapId)  {
          return mSupplicantStaIfaceHal.startDppEnrolleeInitiator(ifaceName, peerBootstrapId,
                  ownBootstrapId);
      }
  
      /**
       * Callback to notify about DPP success, failure and progress events.
       */
      public interface DppEventCallback {
          /**
           * Called when local DPP Enrollee successfully receives a new Wi-Fi configuratrion from the
           * peer DPP configurator.
           *
           * @param newWifiConfiguration New Wi-Fi configuration received from the configurator
           */
          void onSuccessConfigReceived(WifiConfiguration newWifiConfiguration);
  
          /**
           * Called when local DPP configurator successfully sends Wi-Fi configuration to a remote
           * Enrollee.
           */
          void onSuccessConfigSent();
  
          /**
           * DPP Progress event.
           *
           * @param dppStatusCode Status code of the progress event.
           */
          void onProgress(int dppStatusCode);
  
          /**
           * DPP Failure event.
           *
           * @param dppStatusCode Status code of the failure event.
           */
          void onFailure(int dppStatusCode);
      }
  
      /**
       * Registers DPP event callbacks.
       *
       * @param dppEventCallback Callback object.
       */
      public void registerDppEventCallback(DppEventCallback dppEventCallback) {
          mSupplicantStaIfaceHal.registerDppCallback(dppEventCallback);
      }
  
      /********************************************************
       * Vendor HAL operations
       ********************************************************/
      /**
       * Callback to notify vendor HAL death.
       */
      public interface VendorHalDeathEventHandler {
          /**
           * Invoked when the vendor HAL dies.
           */
          void onDeath();
      }
  
      /**
       * Callback to notify when vendor HAL detects that a change in radio mode.
       */
      public interface VendorHalRadioModeChangeEventHandler {
          /**
           * Invoked when the vendor HAL detects a change to MCC mode.
           * MCC (Multi channel concurrency) = Multiple interfaces are active on the same band,
           * different channels, same radios.
           *
           * @param band Band on which MCC is detected (specified by one of the
           *             WifiScanner.WIFI_BAND_* constants)
           */
          void onMcc(int band);
          /**
           * Invoked when the vendor HAL detects a change to SCC mode.
           * SCC (Single channel concurrency) = Multiple interfaces are active on the same band, same
           * channels, same radios.
           *
           * @param band Band on which SCC is detected (specified by one of the
           *             WifiScanner.WIFI_BAND_* constants)
           */
          void onScc(int band);
          /**
           * Invoked when the vendor HAL detects a change to SBS mode.
           * SBS (Single Band Simultaneous) = Multiple interfaces are active on the same band,
           * different channels, different radios.
           *
           * @param band Band on which SBS is detected (specified by one of the
           *             WifiScanner.WIFI_BAND_* constants)
           */
          void onSbs(int band);
          /**
           * Invoked when the vendor HAL detects a change to DBS mode.
           * DBS (Dual Band Simultaneous) = Multiple interfaces are active on the different bands,
           * different channels, different radios.
           */
          void onDbs();
      }
  
      /**
       * Tests whether the HAL is running or not
       */
      public boolean isHalStarted() {
          return mWifiVendorHal.isHalStarted();
      }
  
      // TODO: Change variable names to camel style.
      public static class ScanCapabilities {
          public int  max_scan_cache_size;
          public int  max_scan_buckets;
          public int  max_ap_cache_per_scan;
          public int  max_rssi_sample_size;
          public int  max_scan_reporting_threshold;
      }
  
      /**
       * Gets the scan capabilities
       *
       * @param ifaceName Name of the interface.
       * @param capabilities object to be filled in
       * @return true for success. false for failure
       */
      public boolean getBgScanCapabilities(
              @NonNull String ifaceName, ScanCapabilities capabilities) {
          return mWifiVendorHal.getBgScanCapabilities(ifaceName, capabilities);
      }
  
      public static class ChannelSettings {
          public int frequency;
          public int dwell_time_ms;
          public boolean passive;
      }
  
      public static class BucketSettings {
          public int bucket;
          public int band;
          public int period_ms;
          public int max_period_ms;
          public int step_count;
          public int report_events;
          public int num_channels;
          public ChannelSettings[] channels;
      }
  
      /**
       * Network parameters for hidden networks to be scanned for.
       */
      public static class HiddenNetwork {
          public String ssid;
  
          @Override
          public boolean equals(Object otherObj) {
              if (this == otherObj) {
                  return true;
              } else if (otherObj == null || getClass() != otherObj.getClass()) {
                  return false;
              }
              HiddenNetwork other = (HiddenNetwork) otherObj;
              return Objects.equals(ssid, other.ssid);
          }
  
          @Override
          public int hashCode() {
              return Objects.hash(ssid);
          }
      }
  
      public static final int SCAN_TYPE_LOW_LATENCY = 0;
      public static final int SCAN_TYPE_LOW_POWER = 1;
      public static final int SCAN_TYPE_HIGH_ACCURACY = 2;
  
      public static class ScanSettings {
          /**
           * Type of scan to perform. One of {@link ScanSettings#SCAN_TYPE_LOW_LATENCY},
           * {@link ScanSettings#SCAN_TYPE_LOW_POWER} or {@link ScanSettings#SCAN_TYPE_HIGH_ACCURACY}.
           */
          public int scanType;
          public int base_period_ms;
          public int max_ap_per_scan;
          public int report_threshold_percent;
          public int report_threshold_num_scans;
          public int num_buckets;
          /* Not used for bg scans. Only works for single scans. */
          public HiddenNetwork[] hiddenNetworks;
          public BucketSettings[] buckets;
      }
  
      /**
       * Network parameters to start PNO scan.
       */
      public static class PnoNetwork {
          public String ssid;
          public byte flags;
          public byte auth_bit_field;
          public int[] frequencies;
  
          @Override
          public boolean equals(Object otherObj) {
              if (this == otherObj) {
                  return true;
              } else if (otherObj == null || getClass() != otherObj.getClass()) {
                  return false;
              }
              PnoNetwork other = (PnoNetwork) otherObj;
              return ((Objects.equals(ssid, other.ssid)) && (flags == other.flags)
                      && (auth_bit_field == other.auth_bit_field))
                      && Arrays.equals(frequencies, other.frequencies);
          }
  
          @Override
          public int hashCode() {
              return Objects.hash(ssid, flags, auth_bit_field, frequencies);
          }
      }
  
      /**
       * Parameters to start PNO scan. This holds the list of networks which are going to used for
       * PNO scan.
       */
      public static class PnoSettings {
          public int min5GHzRssi;
          public int min24GHzRssi;
          public int initialScoreMax;
          public int currentConnectionBonus;
          public int sameNetworkBonus;
          public int secureBonus;
          public int band5GHzBonus;
          public int periodInMs;
          public boolean isConnected;
          public PnoNetwork[] networkList;
      }
  
      public static interface ScanEventHandler {
          /**
           * Called for each AP as it is found with the entire contents of the beacon/probe response.
           * Only called when WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT is specified.
           */
          void onFullScanResult(ScanResult fullScanResult, int bucketsScanned);
          /**
           * Callback on an event during a gscan scan.
           * See WifiNative.WIFI_SCAN_* for possible values.
           */
          void onScanStatus(int event);
          /**
           * Called with the current cached scan results when gscan is paused.
           */
          void onScanPaused(WifiScanner.ScanData[] data);
          /**
           * Called with the current cached scan results when gscan is resumed.
           */
          void onScanRestarted();
      }
  
      /**
       * Handler to notify the occurrence of various events during PNO scan.
       */
      public interface PnoEventHandler {
          /**
           * Callback to notify when one of the shortlisted networks is found during PNO scan.
           * @param results List of Scan results received.
           */
          void onPnoNetworkFound(ScanResult[] results);
  
          /**
           * Callback to notify when the PNO scan schedule fails.
           */
          void onPnoScanFailed();
      }
  
      public static final int WIFI_SCAN_RESULTS_AVAILABLE = 0;
      public static final int WIFI_SCAN_THRESHOLD_NUM_SCANS = 1;
      public static final int WIFI_SCAN_THRESHOLD_PERCENT = 2;
      public static final int WIFI_SCAN_FAILED = 3;
  
      /**
       * Starts a background scan.
       * Any ongoing scan will be stopped first
       *
       * @param ifaceName Name of the interface.
       * @param settings     to control the scan
       * @param eventHandler to call with the results
       * @return true for success
       */
      public boolean startBgScan(
              @NonNull String ifaceName, ScanSettings settings, ScanEventHandler eventHandler) {
          return mWifiVendorHal.startBgScan(ifaceName, settings, eventHandler);
      }
  
      /**
       * Stops any ongoing backgound scan
       * @param ifaceName Name of the interface.
       */
      public void stopBgScan(@NonNull String ifaceName) {
          mWifiVendorHal.stopBgScan(ifaceName);
      }
  
      /**
       * Pauses an ongoing backgound scan
       * @param ifaceName Name of the interface.
       */
      public void pauseBgScan(@NonNull String ifaceName) {
          mWifiVendorHal.pauseBgScan(ifaceName);
      }
  
      /**
       * Restarts a paused scan
       * @param ifaceName Name of the interface.
       */
      public void restartBgScan(@NonNull String ifaceName) {
          mWifiVendorHal.restartBgScan(ifaceName);
      }
  
      /**
       * Gets the latest scan results received.
       * @param ifaceName Name of the interface.
       */
      public WifiScanner.ScanData[] getBgScanResults(@NonNull String ifaceName) {
          return mWifiVendorHal.getBgScanResults(ifaceName);
      }
  
      /**
       * Gets the latest link layer stats
       * @param ifaceName Name of the interface.
       */
      public WifiLinkLayerStats getWifiLinkLayerStats(@NonNull String ifaceName) {
          return mWifiVendorHal.getWifiLinkLayerStats(ifaceName);
      }
  
      /**
       * Get the supported features
       *
       * @param ifaceName Name of the interface.
       * @return bitmask defined by WifiManager.WIFI_FEATURE_*
       */
      public long getSupportedFeatureSet(@NonNull String ifaceName) {
          synchronized (mLock) {
              Iface iface = mIfaceMgr.getIface(ifaceName);
              if (iface == null) {
                  Log.e(TAG, "Could not get Iface object for interface " + ifaceName);
                  return 0;
              }
  
              return iface.featureSet;
          }
      }
  
      /**
       * Get the supported features
       *
       * @param ifaceName Name of the interface.
       * @return bitmask defined by WifiManager.WIFI_FEATURE_*
       */
      private long getSupportedFeatureSetInternal(@NonNull String ifaceName) {
          return mSupplicantStaIfaceHal.getAdvancedKeyMgmtCapabilities(ifaceName)
                  | mWifiVendorHal.getSupportedFeatureSet(ifaceName);
      }
  
      /**
       * Set the MAC OUI during scanning.
       * An OUI {Organizationally Unique Identifier} is a 24-bit number that
       * uniquely identifies a vendor or manufacturer.
       *
       * @param ifaceName Name of the interface.
       * @param oui OUI to set.
       * @return true for success
       */
      public boolean setScanningMacOui(@NonNull String ifaceName, byte[] oui) {
          return mWifiVendorHal.setScanningMacOui(ifaceName, oui);
      }
  
      /**
       * Get the APF (Android Packet Filter) capabilities of the device
       * @param ifaceName Name of the interface.
       */
      public ApfCapabilities getApfCapabilities(@NonNull String ifaceName) {
          return mWifiVendorHal.getApfCapabilities(ifaceName);
      }
  
      /**
       * Installs an APF program on this iface, replacing any existing program.
       *
       * @param ifaceName Name of the interface
       * @param filter is the android packet filter program
       * @return true for success
       */
      public boolean installPacketFilter(@NonNull String ifaceName, byte[] filter) {
          return mWifiVendorHal.installPacketFilter(ifaceName, filter);
      }
  
      /**
       * Reads the APF program and data buffer for this iface.
       *
       * @param ifaceName Name of the interface
       * @return the buffer returned by the driver, or null in case of an error
       */
      public byte[] readPacketFilter(@NonNull String ifaceName) {
          return mWifiVendorHal.readPacketFilter(ifaceName);
      }
  
      /**
       * Set country code for this AP iface.
       * @param ifaceName Name of the interface.
       * @param countryCode - two-letter country code (as ISO 3166)
       * @return true for success
       */
      public boolean setCountryCodeHal(@NonNull String ifaceName, String countryCode) {
          return mWifiVendorHal.setCountryCodeHal(ifaceName, countryCode);
      }
  
      //---------------------------------------------------------------------------------
      /* Wifi Logger commands/events */
      public static interface WifiLoggerEventHandler {
          void onRingBufferData(RingBufferStatus status, byte[] buffer);
          void onWifiAlert(int errorCode, byte[] buffer);
      }
  
      /**
       * Registers the logger callback and enables alerts.
       * Ring buffer data collection is only triggered when |startLoggingRingBuffer| is invoked.
       *
       * @param handler Callback to be invoked.
       * @return true on success, false otherwise.
       */
      public boolean setLoggingEventHandler(WifiLoggerEventHandler handler) {
          return mWifiVendorHal.setLoggingEventHandler(handler);
      }
  
      /**
       * Control debug data collection
       *
       * @param verboseLevel 0 to 3, inclusive. 0 stops logging.
       * @param flags        Ignored.
       * @param maxInterval  Maximum interval between reports; ignore if 0.
       * @param minDataSize  Minimum data size in buffer for report; ignore if 0.
       * @param ringName     Name of the ring for which data collection is to start.
       * @return true for success, false otherwise.
       */
      public boolean startLoggingRingBuffer(int verboseLevel, int flags, int maxInterval,
              int minDataSize, String ringName){
          return mWifiVendorHal.startLoggingRingBuffer(
                  verboseLevel, flags, maxInterval, minDataSize, ringName);
      }
  
      /**
       * Logger features exposed.
       * This is a no-op now, will always return -1.
       *
       * @return true on success, false otherwise.
       */
      public int getSupportedLoggerFeatureSet() {
          return mWifiVendorHal.getSupportedLoggerFeatureSet();
      }
  
      /**
       * Stops all logging and resets the logger callback.
       * This stops both the alerts and ring buffer data collection.
       * @return true on success, false otherwise.
       */
      public boolean resetLogHandler() {
          return mWifiVendorHal.resetLogHandler();
      }
  
      /**
       * Vendor-provided wifi driver version string
       *
       * @return String returned from the HAL.
       */
      public String getDriverVersion() {
          return mWifiVendorHal.getDriverVersion();
      }
  
      /**
       * Vendor-provided wifi firmware version string
       *
       * @return String returned from the HAL.
       */
      public String getFirmwareVersion() {
          return mWifiVendorHal.getFirmwareVersion();
      }
  
      public static class RingBufferStatus{
          String name;
          int flag;
          int ringBufferId;
          int ringBufferByteSize;
          int verboseLevel;
          int writtenBytes;
          int readBytes;
          int writtenRecords;
  
          // Bit masks for interpreting |flag|
          public static final int HAS_BINARY_ENTRIES = (1 << 0);
          public static final int HAS_ASCII_ENTRIES = (1 << 1);
          public static final int HAS_PER_PACKET_ENTRIES = (1 << 2);
  
          @Override
          public String toString() {
              return "name: " + name + " flag: " + flag + " ringBufferId: " + ringBufferId +
                      " ringBufferByteSize: " +ringBufferByteSize + " verboseLevel: " +verboseLevel +
                      " writtenBytes: " + writtenBytes + " readBytes: " + readBytes +
                      " writtenRecords: " + writtenRecords;
          }
      }
  
      /**
       * API to get the status of all ring buffers supported by driver
       */
      public RingBufferStatus[] getRingBufferStatus() {
          return mWifiVendorHal.getRingBufferStatus();
      }
  
      /**
       * Indicates to driver that all the data has to be uploaded urgently
       *
       * @param ringName Name of the ring buffer requested.
       * @return true on success, false otherwise.
       */
      public boolean getRingBufferData(String ringName) {
          return mWifiVendorHal.getRingBufferData(ringName);
      }
  
      /**
       * Request hal to flush ring buffers to files
       *
       * @return true on success, false otherwise.
       */
      public boolean flushRingBufferData() {
          return mWifiVendorHal.flushRingBufferData();
      }
  
      /**
       * Request vendor debug info from the firmware
       *
       * @return Raw data obtained from the HAL.
       */
      public byte[] getFwMemoryDump() {
          return mWifiVendorHal.getFwMemoryDump();
      }
  
      /**
       * Request vendor debug info from the driver
       *
       * @return Raw data obtained from the HAL.
       */
      public byte[] getDriverStateDump() {
          return mWifiVendorHal.getDriverStateDump();
      }
  
      //---------------------------------------------------------------------------------
      /* Packet fate API */
  
      @Immutable
      abstract static class FateReport {
          final static int USEC_PER_MSEC = 1000;
          // The driver timestamp is a 32-bit counter, in microseconds. This field holds the
          // maximal value of a driver timestamp in milliseconds.
          final static int MAX_DRIVER_TIMESTAMP_MSEC = (int) (0xffffffffL / 1000);
          final static SimpleDateFormat dateFormatter = new SimpleDateFormat("HH:mm:ss.SSS");
  
          final byte mFate;
          final long mDriverTimestampUSec;
          final byte mFrameType;
          final byte[] mFrameBytes;
          final long mEstimatedWallclockMSec;
  
          FateReport(byte fate, long driverTimestampUSec, byte frameType, byte[] frameBytes) {
              mFate = fate;
              mDriverTimestampUSec = driverTimestampUSec;
              mEstimatedWallclockMSec =
                      convertDriverTimestampUSecToWallclockMSec(mDriverTimestampUSec);
              mFrameType = frameType;
              mFrameBytes = frameBytes;
          }
  
          public String toTableRowString() {
              StringWriter sw = new StringWriter();
              PrintWriter pw = new PrintWriter(sw);
              FrameParser parser = new FrameParser(mFrameType, mFrameBytes);
              dateFormatter.setTimeZone(TimeZone.getDefault());
              pw.format("%-15s  %12s  %-9s  %-32s  %-12s  %-23s  %s\n",
                      mDriverTimestampUSec,
                      dateFormatter.format(new Date(mEstimatedWallclockMSec)),
                      directionToString(), fateToString(), parser.mMostSpecificProtocolString,
                      parser.mTypeString, parser.mResultString);
              return sw.toString();
          }
  
          public String toVerboseStringWithPiiAllowed() {
              StringWriter sw = new StringWriter();
              PrintWriter pw = new PrintWriter(sw);
              FrameParser parser = new FrameParser(mFrameType, mFrameBytes);
              pw.format("Frame direction: %s\n", directionToString());
              pw.format("Frame timestamp: %d\n", mDriverTimestampUSec);
              pw.format("Frame fate: %s\n", fateToString());
              pw.format("Frame type: %s\n", frameTypeToString(mFrameType));
              pw.format("Frame protocol: %s\n", parser.mMostSpecificProtocolString);
              pw.format("Frame protocol type: %s\n", parser.mTypeString);
              pw.format("Frame length: %d\n", mFrameBytes.length);
              pw.append("Frame bytes");
              pw.append(HexDump.dumpHexString(mFrameBytes));  // potentially contains PII
              pw.append("\n");
              return sw.toString();
          }
  
          /* Returns a header to match the output of toTableRowString(). */
          public static String getTableHeader() {
              StringWriter sw = new StringWriter();
              PrintWriter pw = new PrintWriter(sw);
              pw.format("\n%-15s  %-12s  %-9s  %-32s  %-12s  %-23s  %s\n",
                      "Time usec", "Walltime", "Direction", "Fate", "Protocol", "Type", "Result");
              pw.format("%-15s  %-12s  %-9s  %-32s  %-12s  %-23s  %s\n",
                      "---------", "--------", "---------", "----", "--------", "----", "------");
              return sw.toString();
          }
  
          protected abstract String directionToString();
  
          protected abstract String fateToString();
  
          private static String frameTypeToString(byte frameType) {
              switch (frameType) {
                  case WifiLoggerHal.FRAME_TYPE_UNKNOWN:
                      return "unknown";
                  case WifiLoggerHal.FRAME_TYPE_ETHERNET_II:
                      return "data";
                  case WifiLoggerHal.FRAME_TYPE_80211_MGMT:
                      return "802.11 management";
                  default:
                      return Byte.toString(frameType);
              }
          }
  
          /**
           * Converts a driver timestamp to a wallclock time, based on the current
           * BOOTTIME to wallclock mapping. The driver timestamp is a 32-bit counter of
           * microseconds, with the same base as BOOTTIME.
           */
          private static long convertDriverTimestampUSecToWallclockMSec(long driverTimestampUSec) {
              final long wallclockMillisNow = System.currentTimeMillis();
              final long boottimeMillisNow = SystemClock.elapsedRealtime();
              final long driverTimestampMillis = driverTimestampUSec / USEC_PER_MSEC;
  
              long boottimeTimestampMillis = boottimeMillisNow % MAX_DRIVER_TIMESTAMP_MSEC;
              if (boottimeTimestampMillis < driverTimestampMillis) {
                  // The 32-bit microsecond count has wrapped between the time that the driver
                  // recorded the packet, and the call to this function. Adjust the BOOTTIME
                  // timestamp, to compensate.
                  //
                  // Note that overflow is not a concern here, since the result is less than
                  // 2 * MAX_DRIVER_TIMESTAMP_MSEC. (Given the modulus operation above,
                  // boottimeTimestampMillis must be less than MAX_DRIVER_TIMESTAMP_MSEC.) And, since
                  // MAX_DRIVER_TIMESTAMP_MSEC is an int, 2 * MAX_DRIVER_TIMESTAMP_MSEC must fit
                  // within a long.
                  boottimeTimestampMillis += MAX_DRIVER_TIMESTAMP_MSEC;
              }
  
              final long millisSincePacketTimestamp = boottimeTimestampMillis - driverTimestampMillis;
              return wallclockMillisNow - millisSincePacketTimestamp;
          }
      }
  
      /**
       * Represents the fate information for one outbound packet.
       */
      @Immutable
      public static final class TxFateReport extends FateReport {
          TxFateReport(byte fate, long driverTimestampUSec, byte frameType, byte[] frameBytes) {
              super(fate, driverTimestampUSec, frameType, frameBytes);
          }
  
          @Override
          protected String directionToString() {
              return "TX";
          }
  
          @Override
          protected String fateToString() {
              switch (mFate) {
                  case WifiLoggerHal.TX_PKT_FATE_ACKED:
                      return "acked";
                  case WifiLoggerHal.TX_PKT_FATE_SENT:
                      return "sent";
                  case WifiLoggerHal.TX_PKT_FATE_FW_QUEUED:
                      return "firmware queued";
                  case WifiLoggerHal.TX_PKT_FATE_FW_DROP_INVALID:
                      return "firmware dropped (invalid frame)";
                  case WifiLoggerHal.TX_PKT_FATE_FW_DROP_NOBUFS:
                      return "firmware dropped (no bufs)";
                  case WifiLoggerHal.TX_PKT_FATE_FW_DROP_OTHER:
                      return "firmware dropped (other)";
                  case WifiLoggerHal.TX_PKT_FATE_DRV_QUEUED:
                      return "driver queued";
                  case WifiLoggerHal.TX_PKT_FATE_DRV_DROP_INVALID:
                      return "driver dropped (invalid frame)";
                  case WifiLoggerHal.TX_PKT_FATE_DRV_DROP_NOBUFS:
                      return "driver dropped (no bufs)";
                  case WifiLoggerHal.TX_PKT_FATE_DRV_DROP_OTHER:
                      return "driver dropped (other)";
                  default:
                      return Byte.toString(mFate);
              }
          }
      }
  
      /**
       * Represents the fate information for one inbound packet.
       */
      @Immutable
      public static final class RxFateReport extends FateReport {
          RxFateReport(byte fate, long driverTimestampUSec, byte frameType, byte[] frameBytes) {
              super(fate, driverTimestampUSec, frameType, frameBytes);
          }
  
          @Override
          protected String directionToString() {
              return "RX";
          }
  
          @Override
          protected String fateToString() {
              switch (mFate) {
                  case WifiLoggerHal.RX_PKT_FATE_SUCCESS:
                      return "success";
                  case WifiLoggerHal.RX_PKT_FATE_FW_QUEUED:
                      return "firmware queued";
                  case WifiLoggerHal.RX_PKT_FATE_FW_DROP_FILTER:
                      return "firmware dropped (filter)";
                  case WifiLoggerHal.RX_PKT_FATE_FW_DROP_INVALID:
                      return "firmware dropped (invalid frame)";
                  case WifiLoggerHal.RX_PKT_FATE_FW_DROP_NOBUFS:
                      return "firmware dropped (no bufs)";
                  case WifiLoggerHal.RX_PKT_FATE_FW_DROP_OTHER:
                      return "firmware dropped (other)";
                  case WifiLoggerHal.RX_PKT_FATE_DRV_QUEUED:
                      return "driver queued";
                  case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_FILTER:
                      return "driver dropped (filter)";
                  case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_INVALID:
                      return "driver dropped (invalid frame)";
                  case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_NOBUFS:
                      return "driver dropped (no bufs)";
                  case WifiLoggerHal.RX_PKT_FATE_DRV_DROP_OTHER:
                      return "driver dropped (other)";
                  default:
                      return Byte.toString(mFate);
              }
          }
      }
  
      /**
       * Ask the HAL to enable packet fate monitoring. Fails unless HAL is started.
       *
       * @param ifaceName Name of the interface.
       * @return true for success, false otherwise.
       */
      public boolean startPktFateMonitoring(@NonNull String ifaceName) {
          return mWifiVendorHal.startPktFateMonitoring(ifaceName);
      }
  
      /**
       * Fetch the most recent TX packet fates from the HAL. Fails unless HAL is started.
       *
       * @param ifaceName Name of the interface.
       * @return true for success, false otherwise.
       */
      public boolean getTxPktFates(@NonNull String ifaceName, TxFateReport[] reportBufs) {
          return mWifiVendorHal.getTxPktFates(ifaceName, reportBufs);
      }
  
      /**
       * Fetch the most recent RX packet fates from the HAL. Fails unless HAL is started.
       * @param ifaceName Name of the interface.
       */
      public boolean getRxPktFates(@NonNull String ifaceName, RxFateReport[] reportBufs) {
          return mWifiVendorHal.getRxPktFates(ifaceName, reportBufs);
      }
  
      /**
       * Get the tx packet counts for the interface.
       *
       * @param ifaceName Name of the interface.
       * @return tx packet counts
       */
      public long getTxPackets(@NonNull String ifaceName) {
          return TrafficStats.getTxPackets(ifaceName);
      }
  
      /**
       * Get the rx packet counts for the interface.
       *
       * @param ifaceName Name of the interface
       * @return rx packet counts
       */
      public long getRxPackets(@NonNull String ifaceName) {
          return TrafficStats.getRxPackets(ifaceName);
      }
  
      /**
       * Start sending the specified keep alive packets periodically.
       *
       * @param ifaceName Name of the interface.
       * @param slot Integer used to identify each request.
       * @param dstMac Destination MAC Address
       * @param packet Raw packet contents to send.
       * @param protocol The ethernet protocol type
       * @param period Period to use for sending these packets.
       * @return 0 for success, -1 for error
       */
      public int startSendingOffloadedPacket(@NonNull String ifaceName, int slot,
              byte[] dstMac, byte[] packet, int protocol, int period) {
          byte[] srcMac = NativeUtil.macAddressToByteArray(getMacAddress(ifaceName));
          return mWifiVendorHal.startSendingOffloadedPacket(
                  ifaceName, slot, srcMac, dstMac, packet, protocol, period);
      }
  
      /**
       * Stop sending the specified keep alive packets.
       *
       * @param ifaceName Name of the interface.
       * @param slot id - same as startSendingOffloadedPacket call.
       * @return 0 for success, -1 for error
       */
      public int stopSendingOffloadedPacket(@NonNull String ifaceName, int slot) {
          return mWifiVendorHal.stopSendingOffloadedPacket(ifaceName, slot);
      }
  
      public static interface WifiRssiEventHandler {
          void onRssiThresholdBreached(byte curRssi);
      }
  
      /**
       * Start RSSI monitoring on the currently connected access point.
       *
       * @param ifaceName        Name of the interface.
       * @param maxRssi          Maximum RSSI threshold.
       * @param minRssi          Minimum RSSI threshold.
       * @param rssiEventHandler Called when RSSI goes above maxRssi or below minRssi
       * @return 0 for success, -1 for failure
       */
      public int startRssiMonitoring(
              @NonNull String ifaceName, byte maxRssi, byte minRssi,
              WifiRssiEventHandler rssiEventHandler) {
          return mWifiVendorHal.startRssiMonitoring(
                  ifaceName, maxRssi, minRssi, rssiEventHandler);
      }
  
      /**
       * Stop RSSI monitoring on the currently connected access point.
       *
       * @param ifaceName Name of the interface.
       * @return 0 for success, -1 for failure
       */
      public int stopRssiMonitoring(@NonNull String ifaceName) {
          return mWifiVendorHal.stopRssiMonitoring(ifaceName);
      }
  
      /**
       * Fetch the host wakeup reasons stats from wlan driver.
       *
       * @return the |WlanWakeReasonAndCounts| object retrieved from the wlan driver.
       */
      public WlanWakeReasonAndCounts getWlanWakeReasonCount() {
          return mWifiVendorHal.getWlanWakeReasonCount();
      }
  
      /**
       * Enable/Disable Neighbour discovery offload functionality in the firmware.
       *
       * @param ifaceName Name of the interface.
       * @param enabled true to enable, false to disable.
       * @return true for success, false otherwise.
       */
      public boolean configureNeighborDiscoveryOffload(@NonNull String ifaceName, boolean enabled) {
          return mWifiVendorHal.configureNeighborDiscoveryOffload(ifaceName, enabled);
      }
  
      // Firmware roaming control.
  
      /**
       * Class to retrieve firmware roaming capability parameters.
       */
      public static class RoamingCapabilities {
          public int  maxBlacklistSize;
          public int  maxWhitelistSize;
      }
  
      /**
       * Query the firmware roaming capabilities.
       * @param ifaceName Name of the interface.
       * @return true for success, false otherwise.
       */
      public boolean getRoamingCapabilities(
              @NonNull String ifaceName, RoamingCapabilities capabilities) {
          return mWifiVendorHal.getRoamingCapabilities(ifaceName, capabilities);
      }
  
      /**
       * Macros for controlling firmware roaming.
       */
      public static final int DISABLE_FIRMWARE_ROAMING = 0;
      public static final int ENABLE_FIRMWARE_ROAMING = 1;
  
      /**
       * Indicates success for enableFirmwareRoaming
       */
      public static final int SET_FIRMWARE_ROAMING_SUCCESS = 0;
  
      /**
       * Indicates failure for enableFirmwareRoaming
       */
      public static final int SET_FIRMWARE_ROAMING_FAILURE = 1;
  
      /**
       * Indicates temporary failure for enableFirmwareRoaming - try again later
       */
      public static final int SET_FIRMWARE_ROAMING_BUSY = 2;
  
      /**
       * Enable/disable firmware roaming.
       *
       * @param ifaceName Name of the interface.
       * @return SET_FIRMWARE_ROAMING_SUCCESS, SET_FIRMWARE_ROAMING_FAILURE,
       *         or SET_FIRMWARE_ROAMING_BUSY
       */
      public int enableFirmwareRoaming(@NonNull String ifaceName, int state) {
          return mWifiVendorHal.enableFirmwareRoaming(ifaceName, state);
      }
  
      /**
       * Class for specifying the roaming configurations.
       */
      public static class RoamingConfig {
          public ArrayList<String> blacklistBssids;
          public ArrayList<String> whitelistSsids;
      }
  
      /**
       * Set firmware roaming configurations.
       * @param ifaceName Name of the interface.
       */
      public boolean configureRoaming(@NonNull String ifaceName, RoamingConfig config) {
          return mWifiVendorHal.configureRoaming(ifaceName, config);
      }
  
      /**
       * Reset firmware roaming configuration.
       * @param ifaceName Name of the interface.
       */
      public boolean resetRoamingConfiguration(@NonNull String ifaceName) {
          // Pass in an empty RoamingConfig object which translates to zero size
          // blacklist and whitelist to reset the firmware roaming configuration.
          return mWifiVendorHal.configureRoaming(ifaceName, new RoamingConfig());
      }
  
      /**
       * Select one of the pre-configured transmit power level scenarios or reset it back to normal.
       * Primarily used for meeting SAR requirements.
       *
       * @param sarInfo The collection of inputs used to select the SAR scenario.
       * @return true for success; false for failure or if the HAL version does not support this API.
       */
      public boolean selectTxPowerScenario(SarInfo sarInfo) {
          return mWifiVendorHal.selectTxPowerScenario(sarInfo);
      }
  
      /********************************************************
       * JNI operations
       ********************************************************/
      /* Register native functions */
      static {
          /* Native functions are defined in libwifi-service.so */
          System.loadLibrary("wifi-service");
          registerNatives();
      }
  
      private static native int registerNatives();
      /* kernel logging support */
      private static native byte[] readKernelLogNative();
  
      /**
       * Fetches the latest kernel logs.
       */
      public synchronized String readKernelLog() {
          byte[] bytes = readKernelLogNative();
          if (bytes != null) {
              CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
              try {
                  CharBuffer decoded = decoder.decode(ByteBuffer.wrap(bytes));
                  return decoded.toString();
              } catch (CharacterCodingException cce) {
                  return new String(bytes, StandardCharsets.ISO_8859_1);
              }
          } else {
              return "*** failed to read kernel log ***";
          }
      }
  }
  