content: /*
   * Copyright (C) 2017 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.NonNull;
  import android.content.Context;
  import android.hardware.wifi.hostapd.V1_0.HostapdStatus;
  import android.hardware.wifi.hostapd.V1_0.HostapdStatusCode;
  import android.hardware.wifi.hostapd.V1_0.IHostapd;
  import android.hidl.manager.V1_0.IServiceManager;
  import android.hidl.manager.V1_0.IServiceNotification;
  import android.net.wifi.WifiConfiguration;
  import android.os.Handler;
  import android.os.HwRemoteBinder;
  import android.os.Looper;
  import android.os.RemoteException;
  import android.util.Log;
  
  import com.android.internal.R;
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.server.wifi.WifiNative.HostapdDeathEventHandler;
  import com.android.server.wifi.util.ApConfigUtil;
  import com.android.server.wifi.util.NativeUtil;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.NoSuchElementException;
  import java.util.Random;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.TimeUnit;
  
  import javax.annotation.concurrent.ThreadSafe;
  
  /**
   * To maintain thread-safety, the locking protocol is that every non-static method (regardless of
   * access level) acquires mLock.
   */
  @ThreadSafe
  public class HostapdHal {
      private static final String TAG = "HostapdHal";
      @VisibleForTesting
      public static final String HAL_INSTANCE_NAME = "default";
      @VisibleForTesting
      public static final long WAIT_FOR_DEATH_TIMEOUT_MS = 50L;
  
      private final Object mLock = new Object();
      private boolean mVerboseLoggingEnabled = false;
      private final Handler mEventHandler;
      private final boolean mEnableAcs;
      private final boolean mEnableIeee80211AC;
      private final List<android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange>
              mAcsChannelRanges;
      private boolean mForceApChannel = false;
      private int mForcedApChannel;
  
      // Hostapd HAL interface objects
      private IServiceManager mIServiceManager = null;
      private IHostapd mIHostapd;
      private HashMap<String, WifiNative.SoftApListener> mSoftApListeners = new HashMap<>();
      private HostapdDeathEventHandler mDeathEventHandler;
      private ServiceManagerDeathRecipient mServiceManagerDeathRecipient;
      private HostapdDeathRecipient mHostapdDeathRecipient;
      // Death recipient cookie registered for current supplicant instance.
      private long mDeathRecipientCookie = 0;
  
      private final IServiceNotification mServiceNotificationCallback =
              new IServiceNotification.Stub() {
          public void onRegistration(String fqName, String name, boolean preexisting) {
              synchronized (mLock) {
                  if (mVerboseLoggingEnabled) {
                      Log.i(TAG, "IServiceNotification.onRegistration for: " + fqName
                              + ", " + name + " preexisting=" + preexisting);
                  }
                  if (!initHostapdService()) {
                      Log.e(TAG, "initalizing IHostapd failed.");
                      hostapdServiceDiedHandler(mDeathRecipientCookie);
                  } else {
                      Log.i(TAG, "Completed initialization of IHostapd.");
                  }
              }
          }
      };
      private class ServiceManagerDeathRecipient implements HwRemoteBinder.DeathRecipient {
          @Override
          public void serviceDied(long cookie) {
              mEventHandler.post(() -> {
                  synchronized (mLock) {
                      Log.w(TAG, "IServiceManager died: cookie=" + cookie);
                      hostapdServiceDiedHandler(mDeathRecipientCookie);
                      mIServiceManager = null; // Will need to register a new ServiceNotification
                  }
              });
          }
      }
      private class HostapdDeathRecipient implements HwRemoteBinder.DeathRecipient {
          @Override
          public void serviceDied(long cookie) {
              mEventHandler.post(() -> {
                  synchronized (mLock) {
                      Log.w(TAG, "IHostapd/IHostapd died: cookie=" + cookie);
                      hostapdServiceDiedHandler(cookie);
                  }
              });
          }
      }
  
      public HostapdHal(Context context, Looper looper) {
          mEventHandler = new Handler(looper);
          mEnableAcs = context.getResources().getBoolean(R.bool.config_wifi_softap_acs_supported);
          mEnableIeee80211AC =
                  context.getResources().getBoolean(R.bool.config_wifi_softap_ieee80211ac_supported);
          mAcsChannelRanges = toAcsChannelRanges(context.getResources().getString(
                  R.string.config_wifi_softap_acs_supported_channel_list));
  
          mServiceManagerDeathRecipient = new ServiceManagerDeathRecipient();
          mHostapdDeathRecipient = new HostapdDeathRecipient();
      }
  
      /**
       * Enable/Disable verbose logging.
       *
       * @param enable true to enable, false to disable.
       */
      void enableVerboseLogging(boolean enable) {
          synchronized (mLock) {
              mVerboseLoggingEnabled = enable;
          }
      }
  
      /**
       * Uses the IServiceManager to check if the device is running V1_1 of the HAL from the VINTF for
       * the device.
       * @return true if supported, false otherwise.
       */
      private boolean isV1_1() {
          synchronized (mLock) {
              if (mIServiceManager == null) {
                  Log.e(TAG, "isV1_1: called but mServiceManager is null!?");
                  return false;
              }
              try {
                  return (mIServiceManager.getTransport(
                          android.hardware.wifi.hostapd.V1_1.IHostapd.kInterfaceName,
                          HAL_INSTANCE_NAME)
                          != IServiceManager.Transport.EMPTY);
              } catch (RemoteException e) {
                  Log.e(TAG, "Exception while operating on IServiceManager: " + e);
                  handleRemoteException(e, "getTransport");
                  return false;
              }
          }
      }
  
      /**
       * Link to death for IServiceManager object.
       * @return true on success, false otherwise.
       */
      private boolean linkToServiceManagerDeath() {
          synchronized (mLock) {
              if (mIServiceManager == null) return false;
              try {
                  if (!mIServiceManager.linkToDeath(mServiceManagerDeathRecipient, 0)) {
                      Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
                      hostapdServiceDiedHandler(mDeathRecipientCookie);
                      mIServiceManager = null; // Will need to register a new ServiceNotification
                      return false;
                  }
              } catch (RemoteException e) {
                  Log.e(TAG, "IServiceManager.linkToDeath exception", e);
                  mIServiceManager = null; // Will need to register a new ServiceNotification
                  return false;
              }
              return true;
          }
      }
  
      /**
       * Registers a service notification for the IHostapd service, which triggers intialization of
       * the IHostapd
       * @return true if the service notification was successfully registered
       */
      public boolean initialize() {
          synchronized (mLock) {
              if (mVerboseLoggingEnabled) {
                  Log.i(TAG, "Registering IHostapd service ready callback.");
              }
              mIHostapd = null;
              if (mIServiceManager != null) {
                  // Already have an IServiceManager and serviceNotification registered, don't
                  // don't register another.
                  return true;
              }
              try {
                  mIServiceManager = getServiceManagerMockable();
                  if (mIServiceManager == null) {
                      Log.e(TAG, "Failed to get HIDL Service Manager");
                      return false;
                  }
                  if (!linkToServiceManagerDeath()) {
                      return false;
                  }
                  /* TODO(b/33639391) : Use the new IHostapd.registerForNotifications() once it
                     exists */
                  if (!mIServiceManager.registerForNotifications(
                          IHostapd.kInterfaceName, "", mServiceNotificationCallback)) {
                      Log.e(TAG, "Failed to register for notifications to "
                              + IHostapd.kInterfaceName);
                      mIServiceManager = null; // Will need to register a new ServiceNotification
                      return false;
                  }
              } catch (RemoteException e) {
                  Log.e(TAG, "Exception while trying to register a listener for IHostapd service: "
                          + e);
                  hostapdServiceDiedHandler(mDeathRecipientCookie);
                  mIServiceManager = null; // Will need to register a new ServiceNotification
                  return false;
              }
              return true;
          }
      }
  
      /**
       * Link to death for IHostapd object.
       * @return true on success, false otherwise.
       */
      private boolean linkToHostapdDeath(HwRemoteBinder.DeathRecipient deathRecipient, long cookie) {
          synchronized (mLock) {
              if (mIHostapd == null) return false;
              try {
                  if (!mIHostapd.linkToDeath(deathRecipient, cookie)) {
                      Log.wtf(TAG, "Error on linkToDeath on IHostapd");
                      hostapdServiceDiedHandler(mDeathRecipientCookie);
                      return false;
                  }
              } catch (RemoteException e) {
                  Log.e(TAG, "IHostapd.linkToDeath exception", e);
                  return false;
              }
              return true;
          }
      }
  
      private boolean registerCallback(
              android.hardware.wifi.hostapd.V1_1.IHostapdCallback callback) {
          synchronized (mLock) {
              String methodStr = "registerCallback_1_1";
              try {
                  android.hardware.wifi.hostapd.V1_1.IHostapd iHostapdV1_1 = getHostapdMockableV1_1();
                  if (iHostapdV1_1 == null) return false;
                  HostapdStatus status =  iHostapdV1_1.registerCallback(callback);
                  return checkStatusAndLogFailure(status, methodStr);
              } catch (RemoteException e) {
                  handleRemoteException(e, methodStr);
                  return false;
              }
          }
      }
  
      /**
       * Initialize the IHostapd object.
       * @return true on success, false otherwise.
       */
      private boolean initHostapdService() {
          synchronized (mLock) {
              try {
                  mIHostapd = getHostapdMockable();
              } catch (RemoteException e) {
                  Log.e(TAG, "IHostapd.getService exception: " + e);
                  return false;
              } catch (NoSuchElementException e) {
                  Log.e(TAG, "IHostapd.getService exception: " + e);
                  return false;
              }
              if (mIHostapd == null) {
                  Log.e(TAG, "Got null IHostapd service. Stopping hostapd HIDL startup");
                  return false;
              }
              if (!linkToHostapdDeath(mHostapdDeathRecipient, ++mDeathRecipientCookie)) {
                  mIHostapd = null;
                  return false;
              }
              // Register for callbacks for 1.1 hostapd.
              if (isV1_1() && !registerCallback(new HostapdCallback())) {
                  mIHostapd = null;
                  return false;
              }
          }
          return true;
      }
  
      /**
       * Enable force-soft-AP-channel mode which takes effect when soft AP starts next time
       * @param forcedApChannel The forced IEEE channel number
       */
      void enableForceSoftApChannel(int forcedApChannel) {
          mForceApChannel = true;
          mForcedApChannel = forcedApChannel;
      }
  
      /**
       * Disable force-soft-AP-channel mode which take effect when soft AP starts next time
       */
      void disableForceSoftApChannel() {
          mForceApChannel = false;
      }
  
      /**
       * Add and start a new access point.
       *
       * @param ifaceName Name of the interface.
       * @param config Configuration to use for the AP.
       * @param listener Callback for AP events.
       * @return true on success, false otherwise.
       */
      public boolean addAccessPoint(@NonNull String ifaceName, @NonNull WifiConfiguration config,
                                    @NonNull WifiNative.SoftApListener listener) {
          synchronized (mLock) {
              final String methodStr = "addAccessPoint";
              IHostapd.IfaceParams ifaceParams = new IHostapd.IfaceParams();
              ifaceParams.ifaceName = ifaceName;
              ifaceParams.hwModeParams.enable80211N = true;
              ifaceParams.hwModeParams.enable80211AC = mEnableIeee80211AC;
              try {
                  ifaceParams.channelParams.band = getBand(config);
              } catch (IllegalArgumentException e) {
                  Log.e(TAG, "Unrecognized apBand " + config.apBand);
                  return false;
              }
              if (mForceApChannel) {
                  ifaceParams.channelParams.enableAcs = false;
                  ifaceParams.channelParams.channel = mForcedApChannel;
                  if (mForcedApChannel <= ApConfigUtil.HIGHEST_2G_AP_CHANNEL) {
                      ifaceParams.channelParams.band = IHostapd.Band.BAND_2_4_GHZ;
                  } else {
                      ifaceParams.channelParams.band = IHostapd.Band.BAND_5_GHZ;
                  }
              } else if (mEnableAcs) {
                  ifaceParams.channelParams.enableAcs = true;
                  ifaceParams.channelParams.acsShouldExcludeDfs = true;
              } else {
                  // Downgrade IHostapd.Band.BAND_ANY to IHostapd.Band.BAND_2_4_GHZ if ACS
                  // is not supported.
                  // We should remove this workaround once channel selection is moved from
                  // ApConfigUtil to here.
                  if (ifaceParams.channelParams.band == IHostapd.Band.BAND_ANY) {
                      Log.d(TAG, "ACS is not supported on this device, using 2.4 GHz band.");
                      ifaceParams.channelParams.band = IHostapd.Band.BAND_2_4_GHZ;
                  }
                  ifaceParams.channelParams.enableAcs = false;
                  ifaceParams.channelParams.channel = config.apChannel;
              }
  
              IHostapd.NetworkParams nwParams = new IHostapd.NetworkParams();
              // TODO(b/67745880) Note that config.SSID is intended to be either a
              // hex string or "double quoted".
              // However, it seems that whatever is handing us these configurations does not obey
              // this convention.
              nwParams.ssid.addAll(NativeUtil.stringToByteArrayList(config.SSID));
              nwParams.isHidden = config.hiddenSSID;
              nwParams.encryptionType = getEncryptionType(config);
              nwParams.pskPassphrase = (config.preSharedKey != null) ? config.preSharedKey : "";
              if (!checkHostapdAndLogFailure(methodStr)) return false;
              try {
                  HostapdStatus status;
                  if (isV1_1()) {
                      android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams ifaceParams1_1 =
                              new android.hardware.wifi.hostapd.V1_1.IHostapd.IfaceParams();
                      ifaceParams1_1.V1_0 = ifaceParams;
                      if (mEnableAcs) {
                          ifaceParams1_1.channelParams.acsChannelRanges.addAll(mAcsChannelRanges);
                      }
                      android.hardware.wifi.hostapd.V1_1.IHostapd iHostapdV1_1 =
                              getHostapdMockableV1_1();
                      if (iHostapdV1_1 == null) return false;
                      status = iHostapdV1_1.addAccessPoint_1_1(ifaceParams1_1, nwParams);
                  } else {
                      status = mIHostapd.addAccessPoint(ifaceParams, nwParams);
                  }
                  if (!checkStatusAndLogFailure(status, methodStr)) {
                      return false;
                  }
                  mSoftApListeners.put(ifaceName, listener);
                  return true;
              } catch (RemoteException e) {
                  handleRemoteException(e, methodStr);
                  return false;
              }
          }
      }
  
      /**
       * Remove a previously started access point.
       *
       * @param ifaceName Name of the interface.
       * @return true on success, false otherwise.
       */
      public boolean removeAccessPoint(@NonNull String ifaceName) {
          synchronized (mLock) {
              final String methodStr = "removeAccessPoint";
              if (!checkHostapdAndLogFailure(methodStr)) return false;
              try {
                  HostapdStatus status = mIHostapd.removeAccessPoint(ifaceName);
                  if (!checkStatusAndLogFailure(status, methodStr)) {
                      return false;
                  }
                  mSoftApListeners.remove(ifaceName);
                  return true;
              } catch (RemoteException e) {
                  handleRemoteException(e, methodStr);
                  return false;
              }
          }
      }
  
      /**
       * Registers a death notification for hostapd.
       * @return Returns true on success.
       */
      public boolean registerDeathHandler(@NonNull HostapdDeathEventHandler handler) {
          if (mDeathEventHandler != null) {
              Log.e(TAG, "Death handler already present");
          }
          mDeathEventHandler = handler;
          return true;
      }
  
      /**
       * Deregisters a death notification for hostapd.
       * @return Returns true on success.
       */
      public boolean deregisterDeathHandler() {
          if (mDeathEventHandler == null) {
              Log.e(TAG, "No Death handler present");
          }
          mDeathEventHandler = null;
          return true;
      }
  
      /**
       * Clear internal state.
       */
      private void clearState() {
          synchronized (mLock) {
              mIHostapd = null;
          }
      }
  
      /**
       * Handle hostapd death.
       */
      private void hostapdServiceDiedHandler(long cookie) {
          synchronized (mLock) {
              if (mDeathRecipientCookie != cookie) {
                  Log.i(TAG, "Ignoring stale death recipient notification");
                  return;
              }
              clearState();
              if (mDeathEventHandler != null) {
                  mDeathEventHandler.onDeath();
              }
          }
      }
  
      /**
       * Signals whether Initialization completed successfully.
       */
      public boolean isInitializationStarted() {
          synchronized (mLock) {
              return mIServiceManager != null;
          }
      }
  
      /**
       * Signals whether Initialization completed successfully.
       */
      public boolean isInitializationComplete() {
          synchronized (mLock) {
              return mIHostapd != null;
          }
      }
  
      /**
       * Start the hostapd daemon.
       *
       * @return true on success, false otherwise.
       */
      public boolean startDaemon() {
          synchronized (mLock) {
              try {
                  // This should startup hostapd daemon using the lazy start HAL mechanism.
                  getHostapdMockable();
              } catch (RemoteException e) {
                  Log.e(TAG, "Exception while trying to start hostapd: "
                          + e);
                  hostapdServiceDiedHandler(mDeathRecipientCookie);
                  return false;
              } catch (NoSuchElementException e) {
                  // We're starting the daemon, so expect |NoSuchElementException|.
                  Log.d(TAG, "Successfully triggered start of hostapd using HIDL");
              }
              return true;
          }
      }
  
      /**
       * Terminate the hostapd daemon & wait for it's death.
       */
      public void terminate() {
          synchronized (mLock) {
              // Register for a new death listener to block until hostapd is dead.
              final long waitForDeathCookie = new Random().nextLong();
              final CountDownLatch waitForDeathLatch = new CountDownLatch(1);
              linkToHostapdDeath((cookie) -> {
                  Log.d(TAG, "IHostapd died: cookie=" + cookie);
                  if (cookie != waitForDeathCookie) return;
                  waitForDeathLatch.countDown();
              }, waitForDeathCookie);
  
              final String methodStr = "terminate";
              if (!checkHostapdAndLogFailure(methodStr)) return;
              try {
                  mIHostapd.terminate();
              } catch (RemoteException e) {
                  handleRemoteException(e, methodStr);
              }
  
              // Now wait for death listener callback to confirm that it's dead.
              try {
                  if (!waitForDeathLatch.await(WAIT_FOR_DEATH_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
                      Log.w(TAG, "Timed out waiting for confirmation of hostapd death");
                  }
              } catch (InterruptedException e) {
                  Log.w(TAG, "Failed to wait for hostapd death");
              }
          }
      }
  
      /**
       * Wrapper functions to access static HAL methods, created to be mockable in unit tests
       */
      @VisibleForTesting
      protected IServiceManager getServiceManagerMockable() throws RemoteException {
          synchronized (mLock) {
              return IServiceManager.getService();
          }
      }
  
      @VisibleForTesting
      protected IHostapd getHostapdMockable() throws RemoteException {
          synchronized (mLock) {
              return IHostapd.getService();
          }
      }
  
      @VisibleForTesting
      protected android.hardware.wifi.hostapd.V1_1.IHostapd getHostapdMockableV1_1()
              throws RemoteException {
          synchronized (mLock) {
              try {
                  return android.hardware.wifi.hostapd.V1_1.IHostapd.castFrom(mIHostapd);
              } catch (NoSuchElementException e) {
                  Log.e(TAG, "Failed to get IHostapd", e);
                  return null;
              }
          }
      }
  
      private static int getEncryptionType(WifiConfiguration localConfig) {
          int encryptionType;
          switch (localConfig.getAuthType()) {
              case WifiConfiguration.KeyMgmt.NONE:
                  encryptionType = IHostapd.EncryptionType.NONE;
                  break;
              case WifiConfiguration.KeyMgmt.WPA_PSK:
                  encryptionType = IHostapd.EncryptionType.WPA;
                  break;
              case WifiConfiguration.KeyMgmt.WPA2_PSK:
                  encryptionType = IHostapd.EncryptionType.WPA2;
                  break;
              default:
                  // We really shouldn't default to None, but this was how NetworkManagementService
                  // used to do this.
                  encryptionType = IHostapd.EncryptionType.NONE;
                  break;
          }
          return encryptionType;
      }
  
      private static int getBand(WifiConfiguration localConfig) {
          int bandType;
          switch (localConfig.apBand) {
              case WifiConfiguration.AP_BAND_2GHZ:
                  bandType = IHostapd.Band.BAND_2_4_GHZ;
                  break;
              case WifiConfiguration.AP_BAND_5GHZ:
                  bandType = IHostapd.Band.BAND_5_GHZ;
                  break;
              case WifiConfiguration.AP_BAND_ANY:
                  bandType = IHostapd.Band.BAND_ANY;
                  break;
              default:
                  throw new IllegalArgumentException();
          }
          return bandType;
      }
  
      /**
       * Convert channel list string like '1-6,11' to list of AcsChannelRanges
       */
      private List<android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange>
              toAcsChannelRanges(String channelListStr) {
          ArrayList<android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange> acsChannelRanges =
                  new ArrayList<>();
          String[] channelRanges = channelListStr.split(",");
          for (String channelRange : channelRanges) {
              android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange acsChannelRange =
                      new android.hardware.wifi.hostapd.V1_1.IHostapd.AcsChannelRange();
              try {
                  if (channelRange.contains("-")) {
                      String[] channels  = channelRange.split("-");
                      if (channels.length != 2) {
                          Log.e(TAG, "Unrecognized channel range, length is " + channels.length);
                          continue;
                      }
                      int start = Integer.parseInt(channels[0]);
                      int end = Integer.parseInt(channels[1]);
                      if (start > end) {
                          Log.e(TAG, "Invalid channel range, from " + start + " to " + end);
                          continue;
                      }
                      acsChannelRange.start = start;
                      acsChannelRange.end = end;
                  } else {
                      acsChannelRange.start = Integer.parseInt(channelRange);
                      acsChannelRange.end = acsChannelRange.start;
                  }
              } catch (NumberFormatException e) {
                  // Ignore malformed value
                  Log.e(TAG, "Malformed channel value detected: " + e);
                  continue;
              }
              acsChannelRanges.add(acsChannelRange);
          }
          return acsChannelRanges;
      }
  
      /**
       * Returns false if Hostapd is null, and logs failure to call methodStr
       */
      private boolean checkHostapdAndLogFailure(String methodStr) {
          synchronized (mLock) {
              if (mIHostapd == null) {
                  Log.e(TAG, "Can't call " + methodStr + ", IHostapd is null");
                  return false;
              }
              return true;
          }
      }
  
      /**
       * Returns true if provided status code is SUCCESS, logs debug message and returns false
       * otherwise
       */
      private boolean checkStatusAndLogFailure(HostapdStatus status,
              String methodStr) {
          synchronized (mLock) {
              if (status.code != HostapdStatusCode.SUCCESS) {
                  Log.e(TAG, "IHostapd." + methodStr + " failed: " + status.code
                          + ", " + status.debugMessage);
                  return false;
              } else {
                  if (mVerboseLoggingEnabled) {
                      Log.d(TAG, "IHostapd." + methodStr + " succeeded");
                  }
                  return true;
              }
          }
      }
  
      private void handleRemoteException(RemoteException e, String methodStr) {
          synchronized (mLock) {
              hostapdServiceDiedHandler(mDeathRecipientCookie);
              Log.e(TAG, "IHostapd." + methodStr + " failed with exception", e);
          }
      }
  
      private class HostapdCallback extends
              android.hardware.wifi.hostapd.V1_1.IHostapdCallback.Stub {
          @Override
          public void onFailure(String ifaceName) {
              Log.w(TAG, "Failure on iface " + ifaceName);
              WifiNative.SoftApListener listener = mSoftApListeners.get(ifaceName);
              if (listener != null) {
                  listener.onFailure();
              }
          }
      }
  }
  