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.annotation.Nullable;
  import android.hardware.wifi.V1_0.IWifi;
  import android.hardware.wifi.V1_0.IWifiApIface;
  import android.hardware.wifi.V1_0.IWifiChip;
  import android.hardware.wifi.V1_0.IWifiChipEventCallback;
  import android.hardware.wifi.V1_0.IWifiEventCallback;
  import android.hardware.wifi.V1_0.IWifiIface;
  import android.hardware.wifi.V1_0.IWifiNanIface;
  import android.hardware.wifi.V1_0.IWifiP2pIface;
  import android.hardware.wifi.V1_0.IWifiRttController;
  import android.hardware.wifi.V1_0.IWifiStaIface;
  import android.hardware.wifi.V1_0.IfaceType;
  import android.hardware.wifi.V1_0.WifiDebugRingBufferStatus;
  import android.hardware.wifi.V1_0.WifiStatus;
  import android.hardware.wifi.V1_0.WifiStatusCode;
  import android.hidl.manager.V1_0.IServiceNotification;
  import android.hidl.manager.V1_2.IServiceManager;
  import android.os.Handler;
  import android.os.HidlSupport.Mutable;
  import android.os.HwRemoteBinder;
  import android.os.Looper;
  import android.os.RemoteException;
  import android.util.Log;
  import android.util.LongSparseArray;
  import android.util.MutableBoolean;
  import android.util.MutableInt;
  import android.util.Pair;
  import android.util.SparseArray;
  
  import com.android.internal.annotations.VisibleForTesting;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * Handles device management through the HAL (HIDL) interface.
   */
  public class HalDeviceManager {
      private static final String TAG = "HalDevMgr";
      private static final boolean VDBG = false;
      private boolean mDbg = false;
  
      private static final int START_HAL_RETRY_INTERVAL_MS = 20;
      // Number of attempts a start() is re-tried. A value of 0 means no retries after a single
      // attempt.
      @VisibleForTesting
      public static final int START_HAL_RETRY_TIMES = 3;
  
      private final Clock mClock;
      private final Handler mEventHandler;
      private WifiDeathRecipient mIWifiDeathRecipient;
      private ServiceManagerDeathRecipient mServiceManagerDeathRecipient;
  
      // cache the value for supporting vendor HAL or not
      private boolean mIsVendorHalSupported = false;
  
      // public API
      public HalDeviceManager(Clock clock, Looper looper) {
          mClock = clock;
          mEventHandler = new Handler(looper);
          mIWifiDeathRecipient = new WifiDeathRecipient();
          mServiceManagerDeathRecipient = new ServiceManagerDeathRecipient();
  
          mInterfaceAvailableForRequestListeners.put(IfaceType.STA, new HashMap<>());
          mInterfaceAvailableForRequestListeners.put(IfaceType.AP, new HashMap<>());
          mInterfaceAvailableForRequestListeners.put(IfaceType.P2P, new HashMap<>());
          mInterfaceAvailableForRequestListeners.put(IfaceType.NAN, new HashMap<>());
      }
  
      /* package */ void enableVerboseLogging(int verbose) {
          if (verbose > 0) {
              mDbg = true;
          } else {
              mDbg = false;
          }
          if (VDBG) {
              mDbg = true; // just override
          }
      }
  
      /**
       * Actually starts the HalDeviceManager: separate from constructor since may want to phase
       * at a later time.
       *
       * TODO: if decide that no need for separating construction from initialization (e.g. both are
       * done at injector) then move to constructor.
       */
      public void initialize() {
          initializeInternal();
      }
  
      /**
       * Register a ManagerStatusListener to get information about the status of the manager. Use the
       * isReady() and isStarted() methods to check status immediately after registration and when
       * triggered.
       *
       * It is safe to re-register the same callback object - duplicates are detected and only a
       * single copy kept.
       *
       * @param listener ManagerStatusListener listener object.
       * @param handler Handler on which to dispatch listener. Null implies the listener will be
       *                invoked synchronously from the context of the client which triggered the
       *                state change.
       */
      public void registerStatusListener(@NonNull ManagerStatusListener listener,
              @Nullable Handler handler) {
          synchronized (mLock) {
              if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener, handler))) {
                  Log.w(TAG, "registerStatusListener: duplicate registration ignored");
              }
          }
      }
  
      /**
       * Returns whether the vendor HAL is supported on this device or not.
       */
      public boolean isSupported() {
          return mIsVendorHalSupported;
      }
  
      /**
       * Returns the current status of the HalDeviceManager: whether or not it is ready to execute
       * commands. A return of 'false' indicates that the HAL service (IWifi) is not available. Use
       * the registerStatusListener() to listener for status changes.
       */
      public boolean isReady() {
          return mIsReady;
      }
  
      /**
       * Returns the current status of Wi-Fi: started (true) or stopped (false).
       *
       * Note: direct call to HIDL.
       */
      public boolean isStarted() {
          return isWifiStarted();
      }
  
      /**
       * Attempts to start Wi-Fi (using HIDL). Returns the success (true) or failure (false) or
       * the start operation. Will also dispatch any registered ManagerStatusCallback.onStart() on
       * success.
       *
       * Note: direct call to HIDL.
       */
      public boolean start() {
          return startWifi();
      }
  
      /**
       * Stops Wi-Fi. Will also dispatch any registeredManagerStatusCallback.onStop().
       *
       * Note: direct call to HIDL - failure is not-expected.
       */
      public void stop() {
          stopWifi();
          mWifi = null;
      }
  
      /**
       * HAL device manager status change listener.
       */
      public interface ManagerStatusListener {
          /**
           * Indicates that the status of the HalDeviceManager has changed. Use isReady() and
           * isStarted() to obtain status information.
           */
          void onStatusChanged();
      }
  
      /**
       * Return the set of supported interface types across all Wi-Fi chips on the device.
       *
       * @return A set of IfaceTypes constants (possibly empty, e.g. on error).
       */
      public Set<Integer> getSupportedIfaceTypes() {
          return getSupportedIfaceTypesInternal(null);
      }
  
      /**
       * Return the set of supported interface types for the specified Wi-Fi chip.
       *
       * @return A set of IfaceTypes constants  (possibly empty, e.g. on error).
       */
      public Set<Integer> getSupportedIfaceTypes(IWifiChip chip) {
          return getSupportedIfaceTypesInternal(chip);
      }
  
      // interface-specific behavior
  
      /**
       * Create a STA interface if possible. Changes chip mode and removes conflicting interfaces if
       * needed and permitted by priority.
       *
       * @param lowPrioritySta Indicates whether the requested STA is a low priority STA. The priority
       *                       and preemption rules for low priority STA are:
       *                       - Do not destroy any interface for it (even another low priority STA)
       *                       - Destroy it for any other request
       * @param destroyedListener Optional (nullable) listener to call when the allocated interface
       *                          is removed. Will only be registered and used if an interface is
       *                          created successfully.
       * @param handler Handler on which to dispatch listener. Null implies the listener will be
       *                invoked synchronously from the context of the client which triggered the
       *                iface destruction.
       * @return A newly created interface - or null if the interface could not be created.
       */
      public IWifiStaIface createStaIface(boolean lowPrioritySta,
              @Nullable InterfaceDestroyedListener destroyedListener, @Nullable Handler handler) {
          return (IWifiStaIface) createIface(IfaceType.STA, lowPrioritySta, destroyedListener,
                  handler);
      }
  
      /**
       * Create AP interface if possible (see createStaIface doc).
       */
      public IWifiApIface createApIface(@Nullable InterfaceDestroyedListener destroyedListener,
              @Nullable Handler handler) {
          return (IWifiApIface) createIface(IfaceType.AP, false, destroyedListener, handler);
      }
  
      /**
       * Create P2P interface if possible (see createStaIface doc).
       */
      public IWifiP2pIface createP2pIface(@Nullable InterfaceDestroyedListener destroyedListener,
              @Nullable Handler handler) {
          return (IWifiP2pIface) createIface(IfaceType.P2P, false, destroyedListener, handler);
      }
  
      /**
       * Create NAN interface if possible (see createStaIface doc).
       */
      public IWifiNanIface createNanIface(@Nullable InterfaceDestroyedListener destroyedListener,
              @Nullable Handler handler) {
          return (IWifiNanIface) createIface(IfaceType.NAN, false, destroyedListener, handler);
      }
  
      /**
       * Removes (releases/destroys) the given interface. Will trigger any registered
       * InterfaceDestroyedListeners and possibly some InterfaceAvailableForRequestListeners if we
       * can potentially create some other interfaces as a result of removing this interface.
       */
      public boolean removeIface(IWifiIface iface) {
          boolean success = removeIfaceInternal(iface);
          dispatchAvailableForRequestListeners();
          return success;
      }
  
      /**
       * Returns the IWifiChip corresponding to the specified interface (or null on error).
       *
       * Note: clients must not perform chip mode changes or interface management (create/delete)
       * operations on IWifiChip directly. However, they can use the IWifiChip interface to perform
       * other functions - e.g. calling the debug/trace methods.
       */
      public IWifiChip getChip(IWifiIface iface) {
          String name = getName(iface);
          int type = getType(iface);
          if (VDBG) Log.d(TAG, "getChip: iface(name)=" + name);
  
          synchronized (mLock) {
              InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(Pair.create(name, type));
              if (cacheEntry == null) {
                  Log.e(TAG, "getChip: no entry for iface(name)=" + name);
                  return null;
              }
  
              return cacheEntry.chip;
          }
      }
  
      /**
       * Register an InterfaceDestroyedListener to the specified iface - returns true on success
       * and false on failure. This listener is in addition to the one registered when the interface
       * was created - allowing non-creators to monitor interface status.
       *
       * @param destroyedListener Listener to call when the allocated interface is removed.
       *                          Will only be registered and used if an interface is created
       *                          successfully.
       * @param handler Handler on which to dispatch listener. Null implies the listener will be
       *                invoked synchronously from the context of the client which triggered the
       *                iface destruction.
       */
      public boolean registerDestroyedListener(IWifiIface iface,
              @NonNull InterfaceDestroyedListener destroyedListener,
              @Nullable Handler handler) {
          String name = getName(iface);
          int type = getType(iface);
          if (VDBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name);
  
          synchronized (mLock) {
              InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(Pair.create(name, type));
              if (cacheEntry == null) {
                  Log.e(TAG, "registerDestroyedListener: no entry for iface(name)=" + name);
                  return false;
              }
  
              return cacheEntry.destroyedListeners.add(
                      new InterfaceDestroyedListenerProxy(name, destroyedListener, handler));
          }
      }
  
      /**
       * Register a listener to be called when an interface of the specified type could be requested.
       * No guarantees are provided (some other entity could request it first). The listener is
       * active from registration until unregistration - using
       * unregisterInterfaceAvailableForRequestListener().
       *
       * Only a single instance of a listener will be registered (even if the specified looper is
       * different).
       *
       * Note that if it is possible to create the specified interface type at registration time
       * then the callback will be triggered immediately.
       *
       * @param ifaceType The interface type (IfaceType) to be monitored.
       * @param listener Listener to call when an interface of the requested
       *                 type could be created
       * @param handler Handler on which to dispatch listener. Null implies the listener will be
       *                invoked synchronously from the context of the client which triggered the
       *                mode change.
       */
      public void registerInterfaceAvailableForRequestListener(int ifaceType,
              @NonNull InterfaceAvailableForRequestListener listener, @Nullable Handler handler) {
          if (VDBG) {
              Log.d(TAG, "registerInterfaceAvailableForRequestListener: ifaceType=" + ifaceType
                      + ", listener=" + listener + ", handler=" + handler);
          }
  
          synchronized (mLock) {
              InterfaceAvailableForRequestListenerProxy proxy =
                      new InterfaceAvailableForRequestListenerProxy(listener, handler);
              if (mInterfaceAvailableForRequestListeners.get(ifaceType).containsKey(proxy)) {
                  if (VDBG) {
                      Log.d(TAG,
                              "registerInterfaceAvailableForRequestListener: dup listener skipped: "
                                      + listener);
                  }
                  return;
              }
              mInterfaceAvailableForRequestListeners.get(ifaceType).put(proxy, null);
          }
  
          WifiChipInfo[] chipInfos = getAllChipInfo();
          if (chipInfos == null) {
              Log.e(TAG,
                      "registerInterfaceAvailableForRequestListener: no chip info found - but "
                              + "possibly registered pre-started - ignoring");
              return;
          }
          dispatchAvailableForRequestListenersForType(ifaceType, chipInfos);
      }
  
      /**
       * Unregisters a listener registered with registerInterfaceAvailableForRequestListener().
       */
      public void unregisterInterfaceAvailableForRequestListener(
              int ifaceType,
              InterfaceAvailableForRequestListener listener) {
          if (VDBG) {
              Log.d(TAG, "unregisterInterfaceAvailableForRequestListener: ifaceType=" + ifaceType);
          }
  
          synchronized (mLock) {
              mInterfaceAvailableForRequestListeners.get(ifaceType).remove(
                      new InterfaceAvailableForRequestListenerProxy(listener, null));
          }
      }
  
      /**
       * Register a callback object for RTT life-cycle events. The callback object registration
       * indicates that an RTT controller should be created whenever possible. The callback object
       * will be called with a new RTT controller whenever it is created (or at registration time
       * if an RTT controller already exists). The callback object will also be triggered whenever
       * an existing RTT controller is destroyed (the previous copies must be discarded by the
       * recipient).
       *
       * @param callback InterfaceRttControllerLifecycleCallback object.
       * @param handler Handler on which to dispatch callback
       */
      public void registerRttControllerLifecycleCallback(
              @NonNull InterfaceRttControllerLifecycleCallback callback, @NonNull Handler handler) {
          if (VDBG) {
              Log.d(TAG, "registerRttControllerLifecycleCallback: callback=" + callback + ", handler="
                      + handler);
          }
  
          if (callback == null || handler == null) {
              Log.wtf(TAG, "registerRttControllerLifecycleCallback with nulls!? callback=" + callback
                      + ", handler=" + handler);
              return;
          }
  
          synchronized (mLock) {
              InterfaceRttControllerLifecycleCallbackProxy proxy =
                      new InterfaceRttControllerLifecycleCallbackProxy(callback, handler);
              if (!mRttControllerLifecycleCallbacks.add(proxy)) {
                  Log.d(TAG,
                          "registerRttControllerLifecycleCallback: registering an existing callback="
                                  + callback);
                  return;
              }
  
              if (mIWifiRttController == null) {
                  mIWifiRttController = createRttControllerIfPossible();
              }
              if (mIWifiRttController != null) {
                  proxy.onNewRttController(mIWifiRttController);
              }
          }
      }
  
      /**
       * Return the name of the input interface or null on error.
       */
      public static String getName(IWifiIface iface) {
          if (iface == null) {
              return "<null>";
          }
  
          Mutable<String> nameResp = new Mutable<>();
          try {
              iface.getName((WifiStatus status, String name) -> {
                  if (status.code == WifiStatusCode.SUCCESS) {
                      nameResp.value = name;
                  } else {
                      Log.e(TAG, "Error on getName: " + statusString(status));
                  }
              });
          } catch (RemoteException e) {
              Log.e(TAG, "Exception on getName: " + e);
          }
  
          return nameResp.value;
      }
  
      /**
       * Called when interface is destroyed.
       */
      public interface InterfaceDestroyedListener {
          /**
           * Called for every interface on which registered when destroyed - whether
           * destroyed by releaseIface() or through chip mode change or through Wi-Fi
           * going down.
           *
           * Can be registered when the interface is requested with createXxxIface() - will
           * only be valid if the interface creation was successful - i.e. a non-null was returned.
           *
           * @param ifaceName Name of the interface that was destroyed.
           */
          void onDestroyed(@NonNull String ifaceName);
      }
  
      /**
       * Called when an interface type availability for creation is changed.
       */
      public interface InterfaceAvailableForRequestListener {
          /**
           * Called when an interface type availability for creation is updated. Registered with
           * registerInterfaceAvailableForRequestListener() and unregistered with
           * unregisterInterfaceAvailableForRequestListener().
           */
          void onAvailabilityChanged(boolean isAvailable);
      }
  
      /**
       * Called on RTT controller lifecycle events. RTT controller is a singleton which will be
       * created when possible (after first lifecycle registration) and destroyed if necessary.
       *
       * Determination of availability is determined by the HAL. Creation attempts (if requested
       * by registration of interface) will be done on any mode changes.
       */
      public interface InterfaceRttControllerLifecycleCallback {
          /**
           * Called when an RTT controller was created (or for newly registered listeners - if it
           * was already available). The controller provided by this callback may be destroyed by
           * the HAL at which point the {@link #onRttControllerDestroyed()} will be called.
           *
           * Note: this callback can be triggered to replace an existing controller (instead of
           * calling the Destroyed callback in between).
           *
           * @param controller The RTT controller object.
           */
          void onNewRttController(@NonNull IWifiRttController controller);
  
          /**
           * Called when the previously provided RTT controller is destroyed. Clients must discard
           * their copy. A new copy may be provided later by
           * {@link #onNewRttController(IWifiRttController)}.
           */
          void onRttControllerDestroyed();
      }
  
      // internal state
  
      /* This "PRIORITY" is not for deciding interface elimination (that is controlled by
       * allowedToDeleteIfaceTypeForRequestedType. This priority is used for:
       * - Comparing 2 configuration options
       * - Order of dispatch of available for request listeners
       */
      private static final int[] IFACE_TYPES_BY_PRIORITY =
              {IfaceType.AP, IfaceType.STA, IfaceType.P2P, IfaceType.NAN};
  
      private final Object mLock = new Object();
  
      private IServiceManager mServiceManager;
      private IWifi mWifi;
      private IWifiRttController mIWifiRttController;
      private final WifiEventCallback mWifiEventCallback = new WifiEventCallback();
      private final Set<ManagerStatusListenerProxy> mManagerStatusListeners = new HashSet<>();
      private final Set<InterfaceRttControllerLifecycleCallbackProxy>
              mRttControllerLifecycleCallbacks = new HashSet<>();
      private final SparseArray<Map<InterfaceAvailableForRequestListenerProxy, Boolean>>
              mInterfaceAvailableForRequestListeners = new SparseArray<>();
      private final SparseArray<IWifiChipEventCallback.Stub> mDebugCallbacks = new SparseArray<>();
      private boolean mIsReady;
  
      /*
       * This is the only place where we cache HIDL information in this manager. Necessary since
       * we need to keep a list of registered destroyed listeners. Will be validated regularly
       * in getAllChipInfoAndValidateCache().
       */
      private final Map<Pair<String, Integer>, InterfaceCacheEntry> mInterfaceInfoCache =
              new HashMap<>();
  
      private class InterfaceCacheEntry {
          public IWifiChip chip;
          public int chipId;
          public String name;
          public int type;
          public Set<InterfaceDestroyedListenerProxy> destroyedListeners = new HashSet<>();
          public long creationTime;
          public boolean isLowPriority;
  
          @Override
          public String toString() {
              StringBuilder sb = new StringBuilder();
              sb.append("{name=").append(name).append(", type=").append(type)
                      .append(", destroyedListeners.size()=").append(destroyedListeners.size())
                      .append(", creationTime=").append(creationTime).append(
                      ", isLowPriority=").append(isLowPriority).append("}");
              return sb.toString();
          }
      }
  
      private class WifiIfaceInfo {
          public String name;
          public IWifiIface iface;
      }
  
      private class WifiChipInfo {
          public IWifiChip chip;
          public int chipId;
          public ArrayList<IWifiChip.ChipMode> availableModes;
          public boolean currentModeIdValid;
          public int currentModeId;
          public WifiIfaceInfo[][] ifaces = new WifiIfaceInfo[IFACE_TYPES_BY_PRIORITY.length][];
  
          @Override
          public String toString() {
              StringBuilder sb = new StringBuilder();
              sb.append("{chipId=").append(chipId).append(", availableModes=").append(availableModes)
                      .append(", currentModeIdValid=").append(currentModeIdValid)
                      .append(", currentModeId=").append(currentModeId);
              for (int type: IFACE_TYPES_BY_PRIORITY) {
                  sb.append(", ifaces[" + type + "].length=").append(ifaces[type].length);
              }
              sb.append(")");
              return sb.toString();
          }
      }
  
      /**
       * Wrapper function to access the HIDL services. Created to be mockable in unit-tests.
       */
      protected IWifi getWifiServiceMockable() {
          try {
              return IWifi.getService(true /* retry */);
          } catch (RemoteException e) {
              Log.e(TAG, "Exception getting IWifi service: " + e);
              return null;
          }
      }
  
      protected IServiceManager getServiceManagerMockable() {
          try {
              return IServiceManager.getService();
          } catch (RemoteException e) {
              Log.e(TAG, "Exception getting IServiceManager: " + e);
              return null;
          }
      }
  
      // internal implementation
  
      private void initializeInternal() {
          initIServiceManagerIfNecessary();
          if (mIsVendorHalSupported) {
              initIWifiIfNecessary();
          }
      }
  
      private void teardownInternal() {
          managerStatusListenerDispatch();
          dispatchAllDestroyedListeners();
          mInterfaceAvailableForRequestListeners.get(IfaceType.STA).clear();
          mInterfaceAvailableForRequestListeners.get(IfaceType.AP).clear();
          mInterfaceAvailableForRequestListeners.get(IfaceType.P2P).clear();
          mInterfaceAvailableForRequestListeners.get(IfaceType.NAN).clear();
  
          mIWifiRttController = null;
          dispatchRttControllerLifecycleOnDestroyed();
          mRttControllerLifecycleCallbacks.clear();
      }
  
      private class ServiceManagerDeathRecipient implements HwRemoteBinder.DeathRecipient {
          @Override
          public void serviceDied(long cookie) {
              mEventHandler.post(() -> {
                  Log.wtf(TAG, "IServiceManager died: cookie=" + cookie);
                  synchronized (mLock) {
                      mServiceManager = null;
                      // theoretically can call initServiceManager again here - but
                      // there's no point since most likely system is going to reboot
                  }
              });
          }
      }
  
      private final IServiceNotification mServiceNotificationCallback =
              new IServiceNotification.Stub() {
                  @Override
                  public void onRegistration(String fqName, String name,
                                             boolean preexisting) {
                      Log.d(TAG, "IWifi registration notification: fqName=" + fqName
                              + ", name=" + name + ", preexisting=" + preexisting);
                      synchronized (mLock) {
                          initIWifiIfNecessary();
                      }
                  }
              };
  
      /**
       * Failures of IServiceManager are most likely system breaking in any case. Behavior here
       * will be to WTF and continue.
       */
      private void initIServiceManagerIfNecessary() {
          if (mDbg) Log.d(TAG, "initIServiceManagerIfNecessary");
  
          synchronized (mLock) {
              if (mServiceManager != null) {
                  return;
              }
  
              mServiceManager = getServiceManagerMockable();
              if (mServiceManager == null) {
                  Log.wtf(TAG, "Failed to get IServiceManager instance");
              } else {
                  try {
                      if (!mServiceManager.linkToDeath(
                              mServiceManagerDeathRecipient, /* don't care */ 0)) {
                          Log.wtf(TAG, "Error on linkToDeath on IServiceManager");
                          mServiceManager = null;
                          return;
                      }
  
                      if (!mServiceManager.registerForNotifications(IWifi.kInterfaceName, "",
                              mServiceNotificationCallback)) {
                          Log.wtf(TAG, "Failed to register a listener for IWifi service");
                          mServiceManager = null;
                      }
                  } catch (RemoteException e) {
                      Log.wtf(TAG, "Exception while operating on IServiceManager: " + e);
                      mServiceManager = null;
                  }
  
                  // Cache the result for the supporting vendor hal or not
                  mIsVendorHalSupported = isSupportedInternal();
              }
          }
      }
  
      /**
       * Uses the IServiceManager to query if the vendor HAL is present in the VINTF for the device
       * or not.
       * @return true if supported, false otherwise.
       */
      private boolean isSupportedInternal() {
          if (VDBG) Log.d(TAG, "isSupportedInternal");
  
          synchronized (mLock) {
              if (mServiceManager == null) {
                  Log.e(TAG, "isSupported: called but mServiceManager is null!?");
                  return false;
              }
              try {
                  List<String> wifiServices =
                          mServiceManager.listManifestByInterface(IWifi.kInterfaceName);
                  return !wifiServices.isEmpty();
              } catch (RemoteException e) {
                  Log.wtf(TAG, "Exception while operating on IServiceManager: " + e);
                  return false;
              }
          }
      }
  
      private class WifiDeathRecipient implements HwRemoteBinder.DeathRecipient {
          @Override
          public void serviceDied(long cookie) {
              mEventHandler.post(() -> {
                  Log.e(TAG, "IWifi HAL service died! Have a listener for it ... cookie=" + cookie);
                  synchronized (mLock) { // prevents race condition with surrounding method
                      mWifi = null;
                      mIsReady = false;
                      teardownInternal();
                      // don't restart: wait for registration notification
                  }
              });
          }
      }
  
      /**
       * Initialize IWifi and register death listener and event callback.
       *
       * - It is possible that IWifi is not ready - we have a listener on IServiceManager for it.
       * - It is not expected that any of the registrations will fail. Possible indication that
       *   service died after we obtained a handle to it.
       *
       * Here and elsewhere we assume that death listener will do the right thing!
      */
      private void initIWifiIfNecessary() {
          if (mDbg) Log.d(TAG, "initIWifiIfNecessary");
  
          synchronized (mLock) {
              if (mWifi != null) {
                  return;
              }
  
              try {
                  mWifi = getWifiServiceMockable();
                  if (mWifi == null) {
                      Log.e(TAG, "IWifi not (yet) available - but have a listener for it ...");
                      return;
                  }
  
                  if (!mWifi.linkToDeath(mIWifiDeathRecipient, /* don't care */ 0)) {
                      Log.e(TAG, "Error on linkToDeath on IWifi - will retry later");
                      return;
                  }
  
                  WifiStatus status = mWifi.registerEventCallback(mWifiEventCallback);
                  if (status.code != WifiStatusCode.SUCCESS) {
                      Log.e(TAG, "IWifi.registerEventCallback failed: " + statusString(status));
                      mWifi = null;
                      return;
                  }
                  // Stopping wifi just in case. This would also trigger the status callback.
                  stopWifi();
                  mIsReady = true;
              } catch (RemoteException e) {
                  Log.e(TAG, "Exception while operating on IWifi: " + e);
              }
          }
      }
  
      /**
       * Registers event listeners on all IWifiChips after a successful start: DEBUG only!
       *
       * We don't need the listeners since any callbacks are just confirmation of status codes we
       * obtain directly from mode changes or interface creation/deletion.
       *
       * Relies (to the degree we care) on the service removing all listeners when Wi-Fi is stopped.
       */
      private void initIWifiChipDebugListeners() {
          if (VDBG) Log.d(TAG, "initIWifiChipDebugListeners");
  
          if (!VDBG) {
              return;
          }
  
          synchronized (mLock) {
              try {
                  MutableBoolean statusOk = new MutableBoolean(false);
                  Mutable<ArrayList<Integer>> chipIdsResp = new Mutable<>();
  
                  // get all chip IDs
                  mWifi.getChipIds((WifiStatus status, ArrayList<Integer> chipIds) -> {
                      statusOk.value = status.code == WifiStatusCode.SUCCESS;
                      if (statusOk.value) {
                          chipIdsResp.value = chipIds;
                      } else {
                          Log.e(TAG, "getChipIds failed: " + statusString(status));
                      }
                  });
                  if (!statusOk.value) {
                      return;
                  }
  
                  Log.d(TAG, "getChipIds=" + chipIdsResp.value);
                  if (chipIdsResp.value.size() == 0) {
                      Log.e(TAG, "Should have at least 1 chip!");
                      return;
                  }
  
                  // register a callback for each chip
                  Mutable<IWifiChip> chipResp = new Mutable<>();
                  for (Integer chipId: chipIdsResp.value) {
                      mWifi.getChip(chipId, (WifiStatus status, IWifiChip chip) -> {
                          statusOk.value = status.code == WifiStatusCode.SUCCESS;
                          if (statusOk.value) {
                              chipResp.value = chip;
                          } else {
                              Log.e(TAG, "getChip failed: " + statusString(status));
                          }
                      });
                      if (!statusOk.value) {
                          continue; // still try next one?
                      }
  
                      IWifiChipEventCallback.Stub callback =
                              new IWifiChipEventCallback.Stub() {
                                  @Override
                                  public void onChipReconfigured(int modeId) throws RemoteException {
                                      Log.d(TAG, "onChipReconfigured: modeId=" + modeId);
                                  }
  
                                  @Override
                                  public void onChipReconfigureFailure(WifiStatus status)
                                          throws RemoteException {
                                      Log.d(TAG, "onChipReconfigureFailure: status=" + statusString(
                                              status));
                                  }
  
                                  @Override
                                  public void onIfaceAdded(int type, String name)
                                          throws RemoteException {
                                      Log.d(TAG, "onIfaceAdded: type=" + type + ", name=" + name);
                                  }
  
                                  @Override
                                  public void onIfaceRemoved(int type, String name)
                                          throws RemoteException {
                                      Log.d(TAG, "onIfaceRemoved: type=" + type + ", name=" + name);
                                  }
  
                                  @Override
                                  public void onDebugRingBufferDataAvailable(
                                          WifiDebugRingBufferStatus status,
                                          ArrayList<Byte> data) throws RemoteException {
                                      Log.d(TAG, "onDebugRingBufferDataAvailable");
                                  }
  
                                  @Override
                                  public void onDebugErrorAlert(int errorCode,
                                          ArrayList<Byte> debugData)
                                          throws RemoteException {
                                      Log.d(TAG, "onDebugErrorAlert");
                                  }
                              };
                      mDebugCallbacks.put(chipId, callback); // store to prevent GC: needed by HIDL
                      WifiStatus status = chipResp.value.registerEventCallback(callback);
                      if (status.code != WifiStatusCode.SUCCESS) {
                          Log.e(TAG, "registerEventCallback failed: " + statusString(status));
                          continue; // still try next one?
                      }
                  }
              } catch (RemoteException e) {
                  Log.e(TAG, "initIWifiChipDebugListeners: exception: " + e);
                  return;
              }
          }
      }
  
      /**
       * Get current information about all the chips in the system: modes, current mode (if any), and
       * any existing interfaces.
       *
       * Intended to be called whenever we need to configure the chips - information is NOT cached (to
       * reduce the likelihood that we get out-of-sync).
       */
      private WifiChipInfo[] getAllChipInfo() {
          if (VDBG) Log.d(TAG, "getAllChipInfo");
  
          synchronized (mLock) {
              if (mWifi == null) {
                  Log.e(TAG, "getAllChipInfo: called but mWifi is null!?");
                  return null;
              }
  
              try {
                  MutableBoolean statusOk = new MutableBoolean(false);
                  Mutable<ArrayList<Integer>> chipIdsResp = new Mutable<>();
  
                  // get all chip IDs
                  mWifi.getChipIds((WifiStatus status, ArrayList<Integer> chipIds) -> {
                      statusOk.value = status.code == WifiStatusCode.SUCCESS;
                      if (statusOk.value) {
                          chipIdsResp.value = chipIds;
                      } else {
                          Log.e(TAG, "getChipIds failed: " + statusString(status));
                      }
                  });
                  if (!statusOk.value) {
                      return null;
                  }
  
                  if (VDBG) Log.d(TAG, "getChipIds=" + chipIdsResp.value);
                  if (chipIdsResp.value.size() == 0) {
                      Log.e(TAG, "Should have at least 1 chip!");
                      return null;
                  }
  
                  int chipInfoIndex = 0;
                  WifiChipInfo[] chipsInfo = new WifiChipInfo[chipIdsResp.value.size()];
  
                  Mutable<IWifiChip> chipResp = new Mutable<>();
                  for (Integer chipId: chipIdsResp.value) {
                      mWifi.getChip(chipId, (WifiStatus status, IWifiChip chip) -> {
                          statusOk.value = status.code == WifiStatusCode.SUCCESS;
                          if (statusOk.value) {
                              chipResp.value = chip;
                          } else {
                              Log.e(TAG, "getChip failed: " + statusString(status));
                          }
                      });
                      if (!statusOk.value) {
                          return null;
                      }
  
                      Mutable<ArrayList<IWifiChip.ChipMode>> availableModesResp = new Mutable<>();
                      chipResp.value.getAvailableModes(
                              (WifiStatus status, ArrayList<IWifiChip.ChipMode> modes) -> {
                                  statusOk.value = status.code == WifiStatusCode.SUCCESS;
                                  if (statusOk.value) {
                                      availableModesResp.value = modes;
                                  } else {
                                      Log.e(TAG, "getAvailableModes failed: " + statusString(status));
                                  }
                              });
                      if (!statusOk.value) {
                          return null;
                      }
  
                      MutableBoolean currentModeValidResp = new MutableBoolean(false);
                      MutableInt currentModeResp = new MutableInt(0);
                      chipResp.value.getMode((WifiStatus status, int modeId) -> {
                          statusOk.value = status.code == WifiStatusCode.SUCCESS;
                          if (statusOk.value) {
                              currentModeValidResp.value = true;
                              currentModeResp.value = modeId;
                          } else if (status.code == WifiStatusCode.ERROR_NOT_AVAILABLE) {
                              statusOk.value = true; // valid response
                          } else {
                              Log.e(TAG, "getMode failed: " + statusString(status));
                          }
                      });
                      if (!statusOk.value) {
                          return null;
                      }
  
                      Mutable<ArrayList<String>> ifaceNamesResp = new Mutable<>();
                      MutableInt ifaceIndex = new MutableInt(0);
  
                      chipResp.value.getStaIfaceNames(
                              (WifiStatus status, ArrayList<String> ifnames) -> {
                                  statusOk.value = status.code == WifiStatusCode.SUCCESS;
                                  if (statusOk.value) {
                                      ifaceNamesResp.value = ifnames;
                                  } else {
                                      Log.e(TAG, "getStaIfaceNames failed: " + statusString(status));
                                  }
                              });
                      if (!statusOk.value) {
                          return null;
                      }
  
                      WifiIfaceInfo[] staIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
                      for (String ifaceName: ifaceNamesResp.value) {
                          chipResp.value.getStaIface(ifaceName,
                                  (WifiStatus status, IWifiStaIface iface) -> {
                                      statusOk.value = status.code == WifiStatusCode.SUCCESS;
                                      if (statusOk.value) {
                                          WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
                                          ifaceInfo.name = ifaceName;
                                          ifaceInfo.iface = iface;
                                          staIfaces[ifaceIndex.value++] = ifaceInfo;
                                      } else {
                                          Log.e(TAG, "getStaIface failed: " + statusString(status));
                                      }
                                  });
                          if (!statusOk.value) {
                              return null;
                          }
                      }
  
                      ifaceIndex.value = 0;
                      chipResp.value.getApIfaceNames(
                              (WifiStatus status, ArrayList<String> ifnames) -> {
                                  statusOk.value = status.code == WifiStatusCode.SUCCESS;
                                  if (statusOk.value) {
                                      ifaceNamesResp.value = ifnames;
                                  } else {
                                      Log.e(TAG, "getApIfaceNames failed: " + statusString(status));
                                  }
                              });
                      if (!statusOk.value) {
                          return null;
                      }
  
                      WifiIfaceInfo[] apIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
                      for (String ifaceName: ifaceNamesResp.value) {
                          chipResp.value.getApIface(ifaceName,
                                  (WifiStatus status, IWifiApIface iface) -> {
                                      statusOk.value = status.code == WifiStatusCode.SUCCESS;
                                      if (statusOk.value) {
                                          WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
                                          ifaceInfo.name = ifaceName;
                                          ifaceInfo.iface = iface;
                                          apIfaces[ifaceIndex.value++] = ifaceInfo;
                                      } else {
                                          Log.e(TAG, "getApIface failed: " + statusString(status));
                                      }
                                  });
                          if (!statusOk.value) {
                              return null;
                          }
                      }
  
                      ifaceIndex.value = 0;
                      chipResp.value.getP2pIfaceNames(
                              (WifiStatus status, ArrayList<String> ifnames) -> {
                                  statusOk.value = status.code == WifiStatusCode.SUCCESS;
                                  if (statusOk.value) {
                                      ifaceNamesResp.value = ifnames;
                                  } else {
                                      Log.e(TAG, "getP2pIfaceNames failed: " + statusString(status));
                                  }
                              });
                      if (!statusOk.value) {
                          return null;
                      }
  
                      WifiIfaceInfo[] p2pIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
                      for (String ifaceName: ifaceNamesResp.value) {
                          chipResp.value.getP2pIface(ifaceName,
                                  (WifiStatus status, IWifiP2pIface iface) -> {
                                      statusOk.value = status.code == WifiStatusCode.SUCCESS;
                                      if (statusOk.value) {
                                          WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
                                          ifaceInfo.name = ifaceName;
                                          ifaceInfo.iface = iface;
                                          p2pIfaces[ifaceIndex.value++] = ifaceInfo;
                                      } else {
                                          Log.e(TAG, "getP2pIface failed: " + statusString(status));
                                      }
                                  });
                          if (!statusOk.value) {
                              return null;
                          }
                      }
  
                      ifaceIndex.value = 0;
                      chipResp.value.getNanIfaceNames(
                              (WifiStatus status, ArrayList<String> ifnames) -> {
                                  statusOk.value = status.code == WifiStatusCode.SUCCESS;
                                  if (statusOk.value) {
                                      ifaceNamesResp.value = ifnames;
                                  } else {
                                      Log.e(TAG, "getNanIfaceNames failed: " + statusString(status));
                                  }
                              });
                      if (!statusOk.value) {
                          return null;
                      }
  
                      WifiIfaceInfo[] nanIfaces = new WifiIfaceInfo[ifaceNamesResp.value.size()];
                      for (String ifaceName: ifaceNamesResp.value) {
                          chipResp.value.getNanIface(ifaceName,
                                  (WifiStatus status, IWifiNanIface iface) -> {
                                      statusOk.value = status.code == WifiStatusCode.SUCCESS;
                                      if (statusOk.value) {
                                          WifiIfaceInfo ifaceInfo = new WifiIfaceInfo();
                                          ifaceInfo.name = ifaceName;
                                          ifaceInfo.iface = iface;
                                          nanIfaces[ifaceIndex.value++] = ifaceInfo;
                                      } else {
                                          Log.e(TAG, "getNanIface failed: " + statusString(status));
                                      }
                                  });
                          if (!statusOk.value) {
                              return null;
                          }
                      }
  
                      WifiChipInfo chipInfo = new WifiChipInfo();
                      chipsInfo[chipInfoIndex++] = chipInfo;
  
                      chipInfo.chip = chipResp.value;
                      chipInfo.chipId = chipId;
                      chipInfo.availableModes = availableModesResp.value;
                      chipInfo.currentModeIdValid = currentModeValidResp.value;
                      chipInfo.currentModeId = currentModeResp.value;
                      chipInfo.ifaces[IfaceType.STA] = staIfaces;
                      chipInfo.ifaces[IfaceType.AP] = apIfaces;
                      chipInfo.ifaces[IfaceType.P2P] = p2pIfaces;
                      chipInfo.ifaces[IfaceType.NAN] = nanIfaces;
                  }
  
                  return chipsInfo;
              } catch (RemoteException e) {
                  Log.e(TAG, "getAllChipInfoAndValidateCache exception: " + e);
              }
          }
  
          return null;
      }
  
      /**
       * Checks the local state of this object (the cached state) against the input 'chipInfos'
       * state (which is a live representation of the Wi-Fi firmware status - read through the HAL).
       * Returns 'true' if there are no discrepancies - 'false' otherwise.
       *
       * A discrepancy is if any local state contains references to a chip or interface which are not
       * found on the information read from the chip.
       */
      private boolean validateInterfaceCache(WifiChipInfo[] chipInfos) {
          if (VDBG) Log.d(TAG, "validateInterfaceCache");
  
          synchronized (mLock) {
              for (InterfaceCacheEntry entry: mInterfaceInfoCache.values()) {
                  // search for chip
                  WifiChipInfo matchingChipInfo = null;
                  for (WifiChipInfo ci: chipInfos) {
                      if (ci.chipId == entry.chipId) {
                          matchingChipInfo = ci;
                          break;
                      }
                  }
                  if (matchingChipInfo == null) {
                      Log.e(TAG, "validateInterfaceCache: no chip found for " + entry);
                      return false;
                  }
  
                  // search for interface
                  WifiIfaceInfo[] ifaceInfoList = matchingChipInfo.ifaces[entry.type];
                  if (ifaceInfoList == null) {
                      Log.e(TAG, "validateInterfaceCache: invalid type on entry " + entry);
                      return false;
                  }
  
                  boolean matchFound = false;
                  for (WifiIfaceInfo ifaceInfo: ifaceInfoList) {
                      if (ifaceInfo.name.equals(entry.name)) {
                          matchFound = true;
                          break;
                      }
                  }
                  if (!matchFound) {
                      Log.e(TAG, "validateInterfaceCache: no interface found for " + entry);
                      return false;
                  }
              }
          }
  
          return true;
      }
  
      private boolean isWifiStarted() {
          if (VDBG) Log.d(TAG, "isWifiStart");
  
          synchronized (mLock) {
              try {
                  if (mWifi == null) {
                      Log.w(TAG, "isWifiStarted called but mWifi is null!?");
                      return false;
                  } else {
                      return mWifi.isStarted();
                  }
              } catch (RemoteException e) {
                  Log.e(TAG, "isWifiStarted exception: " + e);
                  return false;
              }
          }
      }
  
      private boolean startWifi() {
          if (VDBG) Log.d(TAG, "startWifi");
          initIWifiIfNecessary();
          synchronized (mLock) {
              try {
                  if (mWifi == null) {
                      Log.w(TAG, "startWifi called but mWifi is null!?");
                      return false;
                  } else {
                      int triedCount = 0;
                      while (triedCount <= START_HAL_RETRY_TIMES) {
                          WifiStatus status = mWifi.start();
                          if (status.code == WifiStatusCode.SUCCESS) {
                              initIWifiChipDebugListeners();
                              managerStatusListenerDispatch();
                              if (triedCount != 0) {
                                  Log.d(TAG, "start IWifi succeeded after trying "
                                           + triedCount + " times");
                              }
                              return true;
                          } else if (status.code == WifiStatusCode.ERROR_NOT_AVAILABLE) {
                              // Should retry. Hal might still be stopping.
                              Log.e(TAG, "Cannot start IWifi: " + statusString(status)
                                      + ", Retrying...");
                              try {
                                  Thread.sleep(START_HAL_RETRY_INTERVAL_MS);
                              } catch (InterruptedException ignore) {
                                  // no-op
                              }
                              triedCount++;
                          } else {
                              // Should not retry on other failures.
                              Log.e(TAG, "Cannot start IWifi: " + statusString(status));
                              return false;
                          }
                      }
                      Log.e(TAG, "Cannot start IWifi after trying " + triedCount + " times");
                      return false;
                  }
              } catch (RemoteException e) {
                  Log.e(TAG, "startWifi exception: " + e);
                  return false;
              }
          }
      }
  
      private void stopWifi() {
          if (VDBG) Log.d(TAG, "stopWifi");
  
          synchronized (mLock) {
              try {
                  if (mWifi == null) {
                      Log.w(TAG, "stopWifi called but mWifi is null!?");
                  } else {
                      WifiStatus status = mWifi.stop();
                      if (status.code != WifiStatusCode.SUCCESS) {
                          Log.e(TAG, "Cannot stop IWifi: " + statusString(status));
                      }
  
                      // even on failure since WTF??
                      teardownInternal();
                  }
              } catch (RemoteException e) {
                  Log.e(TAG, "stopWifi exception: " + e);
              }
          }
      }
  
      private class WifiEventCallback extends IWifiEventCallback.Stub {
          @Override
          public void onStart() throws RemoteException {
              mEventHandler.post(() -> {
                  if (VDBG) Log.d(TAG, "IWifiEventCallback.onStart");
                  // NOP: only happens in reaction to my calls - will handle directly
              });
          }
  
          @Override
          public void onStop() throws RemoteException {
              mEventHandler.post(() -> {
                  if (VDBG) Log.d(TAG, "IWifiEventCallback.onStop");
                  // NOP: only happens in reaction to my calls - will handle directly
              });
          }
  
          @Override
          public void onFailure(WifiStatus status) throws RemoteException {
              mEventHandler.post(() -> {
                  Log.e(TAG, "IWifiEventCallback.onFailure: " + statusString(status));
                  teardownInternal();
              });
              // No need to do anything else: listeners may (will) re-start Wi-Fi
          }
      }
  
      private void managerStatusListenerDispatch() {
          synchronized (mLock) {
              for (ManagerStatusListenerProxy cb : mManagerStatusListeners) {
                  cb.trigger();
              }
          }
      }
  
      private class ManagerStatusListenerProxy  extends
              ListenerProxy<ManagerStatusListener> {
          ManagerStatusListenerProxy(ManagerStatusListener statusListener, Handler handler) {
              super(statusListener, handler, "ManagerStatusListenerProxy");
          }
  
          @Override
          protected void action() {
              mListener.onStatusChanged();
          }
      }
  
      Set<Integer> getSupportedIfaceTypesInternal(IWifiChip chip) {
          Set<Integer> results = new HashSet<>();
  
          WifiChipInfo[] chipInfos = getAllChipInfo();
          if (chipInfos == null) {
              Log.e(TAG, "getSupportedIfaceTypesInternal: no chip info found");
              return results;
          }
  
          MutableInt chipIdIfProvided = new MutableInt(0); // NOT using 0 as a magic value
          if (chip != null) {
              MutableBoolean statusOk = new MutableBoolean(false);
              try {
                  chip.getId((WifiStatus status, int id) -> {
                      if (status.code == WifiStatusCode.SUCCESS) {
                          chipIdIfProvided.value = id;
                          statusOk.value = true;
                      } else {
                          Log.e(TAG, "getSupportedIfaceTypesInternal: IWifiChip.getId() error: "
                                  + statusString(status));
                          statusOk.value = false;
                      }
                  });
              } catch (RemoteException e) {
                  Log.e(TAG, "getSupportedIfaceTypesInternal IWifiChip.getId() exception: " + e);
                  return results;
              }
              if (!statusOk.value) {
                  return results;
              }
          }
  
          for (WifiChipInfo wci: chipInfos) {
              if (chip != null && wci.chipId != chipIdIfProvided.value) {
                  continue;
              }
  
              for (IWifiChip.ChipMode cm: wci.availableModes) {
                  for (IWifiChip.ChipIfaceCombination cic: cm.availableCombinations) {
                      for (IWifiChip.ChipIfaceCombinationLimit cicl: cic.limits) {
                          for (int type: cicl.types) {
                              results.add(type);
                          }
                      }
                  }
              }
          }
  
          return results;
      }
  
      private IWifiIface createIface(int ifaceType, boolean lowPriority,
              InterfaceDestroyedListener destroyedListener, Handler handler) {
          if (mDbg) {
              Log.d(TAG, "createIface: ifaceType=" + ifaceType + ", lowPriority=" + lowPriority);
          }
  
          synchronized (mLock) {
              WifiChipInfo[] chipInfos = getAllChipInfo();
              if (chipInfos == null) {
                  Log.e(TAG, "createIface: no chip info found");
                  stopWifi(); // major error: shutting down
                  return null;
              }
  
              if (!validateInterfaceCache(chipInfos)) {
                  Log.e(TAG, "createIface: local cache is invalid!");
                  stopWifi(); // major error: shutting down
                  return null;
              }
  
              IWifiIface iface = createIfaceIfPossible(chipInfos, ifaceType, lowPriority,
                      destroyedListener, handler);
              if (iface != null) { // means that some configuration has changed
                  if (!dispatchAvailableForRequestListeners()) {
                      return null; // catastrophic failure - shut down
                  }
              }
  
              return iface;
          }
      }
  
      private IWifiIface createIfaceIfPossible(WifiChipInfo[] chipInfos, int ifaceType,
              boolean lowPriority, InterfaceDestroyedListener destroyedListener, Handler handler) {
          if (VDBG) {
              Log.d(TAG, "createIfaceIfPossible: chipInfos=" + Arrays.deepToString(chipInfos)
                      + ", ifaceType=" + ifaceType + ", lowPriority=" + lowPriority);
          }
          synchronized (mLock) {
              IfaceCreationData bestIfaceCreationProposal = null;
              for (WifiChipInfo chipInfo: chipInfos) {
                  for (IWifiChip.ChipMode chipMode: chipInfo.availableModes) {
                      for (IWifiChip.ChipIfaceCombination chipIfaceCombo : chipMode
                              .availableCombinations) {
                          int[][] expandedIfaceCombos = expandIfaceCombos(chipIfaceCombo);
                          if (VDBG) {
                              Log.d(TAG, chipIfaceCombo + " expands to "
                                      + Arrays.deepToString(expandedIfaceCombos));
                          }
  
                          for (int[] expandedIfaceCombo: expandedIfaceCombos) {
                              IfaceCreationData currentProposal = canIfaceComboSupportRequest(
                                      chipInfo, chipMode, expandedIfaceCombo, ifaceType, lowPriority);
                              if (compareIfaceCreationData(currentProposal,
                                      bestIfaceCreationProposal)) {
                                  if (VDBG) Log.d(TAG, "new proposal accepted");
                                  bestIfaceCreationProposal = currentProposal;
                              }
                          }
                      }
                  }
              }
  
              if (bestIfaceCreationProposal != null) {
                  IWifiIface iface = executeChipReconfiguration(bestIfaceCreationProposal, ifaceType);
                  if (iface != null) {
                      InterfaceCacheEntry cacheEntry = new InterfaceCacheEntry();
  
                      cacheEntry.chip = bestIfaceCreationProposal.chipInfo.chip;
                      cacheEntry.chipId = bestIfaceCreationProposal.chipInfo.chipId;
                      cacheEntry.name = getName(iface);
                      cacheEntry.type = ifaceType;
                      if (destroyedListener != null) {
                          cacheEntry.destroyedListeners.add(
                                  new InterfaceDestroyedListenerProxy(
                                          cacheEntry.name, destroyedListener, handler));
                      }
                      cacheEntry.creationTime = mClock.getUptimeSinceBootMillis();
                      cacheEntry.isLowPriority = lowPriority;
  
                      if (mDbg) Log.d(TAG, "createIfaceIfPossible: added cacheEntry=" + cacheEntry);
                      mInterfaceInfoCache.put(
                              Pair.create(cacheEntry.name, cacheEntry.type), cacheEntry);
                      return iface;
                  }
              }
          }
  
          return null;
      }
  
      // similar to createIfaceIfPossible - but simpler code: not looking for best option just
      // for any option (so terminates on first one).
      private boolean isItPossibleToCreateIface(WifiChipInfo[] chipInfos, int ifaceType) {
          if (VDBG) {
              Log.d(TAG, "isItPossibleToCreateIface: chipInfos=" + Arrays.deepToString(chipInfos)
                      + ", ifaceType=" + ifaceType);
          }
  
          for (WifiChipInfo chipInfo: chipInfos) {
              for (IWifiChip.ChipMode chipMode: chipInfo.availableModes) {
                  for (IWifiChip.ChipIfaceCombination chipIfaceCombo : chipMode
                          .availableCombinations) {
                      int[][] expandedIfaceCombos = expandIfaceCombos(chipIfaceCombo);
                      if (VDBG) {
                          Log.d(TAG, chipIfaceCombo + " expands to "
                                  + Arrays.deepToString(expandedIfaceCombos));
                      }
  
                      for (int[] expandedIfaceCombo: expandedIfaceCombos) {
                          if (canIfaceComboSupportRequest(chipInfo, chipMode, expandedIfaceCombo,
                                  ifaceType, false) != null) {
                              return true;
                          }
                      }
                  }
              }
          }
  
          return false;
      }
  
      /**
       * Expands (or provides an alternative representation) of the ChipIfaceCombination as all
       * possible combinations of interface.
       *
       * Returns [# of combinations][4 (IfaceType)]
       *
       * Note: there could be duplicates - allow (inefficient but ...).
       * TODO: optimize by using a Set as opposed to a []: will remove duplicates. Will need to
       * provide correct hashes.
       */
      private int[][] expandIfaceCombos(IWifiChip.ChipIfaceCombination chipIfaceCombo) {
          int numOfCombos = 1;
          for (IWifiChip.ChipIfaceCombinationLimit limit: chipIfaceCombo.limits) {
              for (int i = 0; i < limit.maxIfaces; ++i) {
                  numOfCombos *= limit.types.size();
              }
          }
  
          int[][] expandedIfaceCombos = new int[numOfCombos][IFACE_TYPES_BY_PRIORITY.length];
  
          int span = numOfCombos; // span of an individual type (or sub-tree size)
          for (IWifiChip.ChipIfaceCombinationLimit limit: chipIfaceCombo.limits) {
              for (int i = 0; i < limit.maxIfaces; ++i) {
                  span /= limit.types.size();
                  for (int k = 0; k < numOfCombos; ++k) {
                      expandedIfaceCombos[k][limit.types.get((k / span) % limit.types.size())]++;
                  }
              }
          }
  
          return expandedIfaceCombos;
      }
  
      private class IfaceCreationData {
          public WifiChipInfo chipInfo;
          public int chipModeId;
          public List<WifiIfaceInfo> interfacesToBeRemovedFirst;
  
          @Override
          public String toString() {
              StringBuilder sb = new StringBuilder();
              sb.append("{chipInfo=").append(chipInfo).append(", chipModeId=").append(chipModeId)
                      .append(", interfacesToBeRemovedFirst=").append(interfacesToBeRemovedFirst)
                      .append(")");
              return sb.toString();
          }
      }
  
      /**
       * Checks whether the input chip-iface-combo can support the requested interface type: if not
       * then returns null, if yes then returns information containing the list of interfaces which
       * would have to be removed first before the requested interface can be created.
       *
       * Note: the list of interfaces to be removed is EMPTY if a chip mode change is required - in
       * that case ALL the interfaces on the current chip have to be removed first.
       *
       * Response determined based on:
       * - Mode configuration: i.e. could the mode support the interface type in principle
       * - Priority information: i.e. are we 'allowed' to remove interfaces in order to create the
       *   requested interface
       */
      private IfaceCreationData canIfaceComboSupportRequest(WifiChipInfo chipInfo,
              IWifiChip.ChipMode chipMode, int[] chipIfaceCombo, int ifaceType, boolean lowPriority) {
          if (VDBG) {
              Log.d(TAG, "canIfaceComboSupportRequest: chipInfo=" + chipInfo + ", chipMode="
                      + chipMode + ", chipIfaceCombo=" + chipIfaceCombo + ", ifaceType=" + ifaceType
                      + ", lowPriority=" + lowPriority);
          }
  
          // short-circuit: does the chipIfaceCombo even support the requested type?
          if (chipIfaceCombo[ifaceType] == 0) {
              if (VDBG) Log.d(TAG, "Requested type not supported by combo");
              return null;
          }
  
          boolean isChipModeChangeProposed =
                  chipInfo.currentModeIdValid && chipInfo.currentModeId != chipMode.id;
  
          // short-circuit: can't change chip-mode if an existing interface on this chip has a higher
          // priority than the requested interface
          if (isChipModeChangeProposed) {
              for (int type: IFACE_TYPES_BY_PRIORITY) {
                  if (chipInfo.ifaces[type].length != 0) {
                      if (lowPriority) {
                          if (VDBG) {
                              Log.d(TAG, "Couldn't delete existing type " + type
                                      + " interfaces for a low priority request");
                          }
                          return null;
                      }
                      if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType,
                              chipInfo.ifaces, chipInfo.ifaces[type].length)) {
                          if (VDBG) {
                              Log.d(TAG, "Couldn't delete existing type " + type
                                      + " interfaces for requested type");
                          }
                          return null;
                      }
                  }
              }
  
              // but if priority allows the mode change then we're good to go
              IfaceCreationData ifaceCreationData = new IfaceCreationData();
              ifaceCreationData.chipInfo = chipInfo;
              ifaceCreationData.chipModeId = chipMode.id;
  
              return ifaceCreationData;
          }
  
          // possibly supported
          List<WifiIfaceInfo> interfacesToBeRemovedFirst = new ArrayList<>();
  
          for (int type: IFACE_TYPES_BY_PRIORITY) {
              int tooManyInterfaces = chipInfo.ifaces[type].length - chipIfaceCombo[type];
  
              // need to count the requested interface as well
              if (type == ifaceType) {
                  tooManyInterfaces += 1;
              }
  
              if (tooManyInterfaces > 0) { // may need to delete some
                  if (lowPriority) {
                      if (VDBG) {
                          Log.d(TAG, "Couldn't delete existing type " + type
                                  + " interfaces for a low priority request");
                      }
                      return null;
                  }
  
                  if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType, chipInfo.ifaces,
                          tooManyInterfaces)) {
                      if (VDBG) {
                          Log.d(TAG, "Would need to delete some higher priority interfaces");
                      }
                      return null;
                  }
  
                  // delete the most recently created interfaces or LOW priority interfaces
                  interfacesToBeRemovedFirst = selectInterfacesToDelete(tooManyInterfaces,
                          chipInfo.ifaces[type]);
              }
          }
  
          IfaceCreationData ifaceCreationData = new IfaceCreationData();
          ifaceCreationData.chipInfo = chipInfo;
          ifaceCreationData.chipModeId = chipMode.id;
          ifaceCreationData.interfacesToBeRemovedFirst = interfacesToBeRemovedFirst;
  
          return ifaceCreationData;
      }
  
      /**
       * Compares two options to create an interface and determines which is the 'best'. Returns
       * true if proposal 1 (val1) is better, other false.
       *
       * Note: both proposals are 'acceptable' bases on priority criteria.
       *
       * Criteria:
       * - Proposal is better if it means removing fewer high priority interfaces
       */
      private boolean compareIfaceCreationData(IfaceCreationData val1, IfaceCreationData val2) {
          if (VDBG) Log.d(TAG, "compareIfaceCreationData: val1=" + val1 + ", val2=" + val2);
  
          // deal with trivial case of one or the other being null
          if (val1 == null) {
              return false;
          } else if (val2 == null) {
              return true;
          }
  
          for (int type: IFACE_TYPES_BY_PRIORITY) {
              // # of interfaces to be deleted: the list or all interfaces of the type if mode change
              int numIfacesToDelete1 = 0;
              if (val1.chipInfo.currentModeIdValid
                      && val1.chipInfo.currentModeId != val1.chipModeId) {
                  numIfacesToDelete1 = val1.chipInfo.ifaces[type].length;
              } else {
                  numIfacesToDelete1 = val1.interfacesToBeRemovedFirst.size();
              }
  
              int numIfacesToDelete2 = 0;
              if (val2.chipInfo.currentModeIdValid
                      && val2.chipInfo.currentModeId != val2.chipModeId) {
                  numIfacesToDelete2 = val2.chipInfo.ifaces[type].length;
              } else {
                  numIfacesToDelete2 = val2.interfacesToBeRemovedFirst.size();
              }
  
              if (numIfacesToDelete1 < numIfacesToDelete2) {
                  if (VDBG) {
                      Log.d(TAG, "decision based on type=" + type + ": " + numIfacesToDelete1
                              + " < " + numIfacesToDelete2);
                  }
                  return true;
              }
          }
  
          // arbitrary - flip a coin
          if (VDBG) Log.d(TAG, "proposals identical - flip a coin");
          return false;
      }
  
      /**
       * Returns true if we're allowed to delete the existing interface type for the requested
       * interface type.
       *
       * Rules - applies in order:
       *
       * General rules:
       * 1. No interface will be destroyed for a requested interface of the same type
       * 2. No interface will be destroyed if one of the requested interfaces already exists
       * 3. If there are >1 interface of an existing type, then it is ok to destroy that type
       *    interface
       *
       * Type-specific rules (but note that the general rules are appied first):
       * 4. Request for AP or STA will destroy any other interface
       * 5. Request for P2P will destroy NAN-only (but will destroy a second STA per #3)
       * 6. Request for NAN will not destroy any interface (but will destroy a second STA per #3)
       *
       * Note: the 'numNecessaryInterfaces' is used to specify how many interfaces would be needed to
       * be deleted. This is used to determine whether there are that many low priority interfaces
       * of the requested type to delete.
       */
      private boolean allowedToDeleteIfaceTypeForRequestedType(int existingIfaceType,
              int requestedIfaceType, WifiIfaceInfo[][] currentIfaces, int numNecessaryInterfaces) {
          // rule 0: check for any low priority interfaces
          int numAvailableLowPriorityInterfaces = 0;
          synchronized (mLock) {
              for (InterfaceCacheEntry entry : mInterfaceInfoCache.values()) {
                  if (entry.type == existingIfaceType && entry.isLowPriority) {
                      numAvailableLowPriorityInterfaces++;
                  }
              }
          }
          if (numAvailableLowPriorityInterfaces >= numNecessaryInterfaces) {
              return true;
          }
  
          // rule 1
          if (existingIfaceType == requestedIfaceType) {
              return false;
          }
  
          // rule 2
          if (currentIfaces[requestedIfaceType].length != 0) {
              return false;
          }
  
          // rule 3
          if (currentIfaces[existingIfaceType].length > 1) {
              return true;
          }
  
          // rule 6
          if (requestedIfaceType == IfaceType.NAN) {
              return false;
          }
  
          // rule 5
          if (requestedIfaceType == IfaceType.P2P) {
              return existingIfaceType == IfaceType.NAN;
          }
  
          // rule 4, the requestIfaceType is either AP or STA
          return true;
      }
  
      /**
       * Selects the interfaces to delete.
       *
       * Rule: select low priority interfaces and then other interfaces in order of creation time.
       *
       * @param excessInterfaces Number of interfaces which need to be selected.
       * @param interfaces Array of interfaces.
       */
      private List<WifiIfaceInfo> selectInterfacesToDelete(int excessInterfaces,
              WifiIfaceInfo[] interfaces) {
          if (VDBG) {
              Log.d(TAG, "selectInterfacesToDelete: excessInterfaces=" + excessInterfaces
                      + ", interfaces=" + Arrays.toString(interfaces));
          }
  
          boolean lookupError = false;
          LongSparseArray<WifiIfaceInfo> orderedListLowPriority = new LongSparseArray<>();
          LongSparseArray<WifiIfaceInfo> orderedList = new LongSparseArray<>();
          for (WifiIfaceInfo info : interfaces) {
              InterfaceCacheEntry cacheEntry;
              synchronized (mLock) {
                  cacheEntry = mInterfaceInfoCache.get(Pair.create(info.name, getType(info.iface)));
              }
              if (cacheEntry == null) {
                  Log.e(TAG,
                          "selectInterfacesToDelete: can't find cache entry with name=" + info.name);
                  lookupError = true;
                  break;
              }
              if (cacheEntry.isLowPriority) {
                  orderedListLowPriority.append(cacheEntry.creationTime, info);
              } else {
                  orderedList.append(cacheEntry.creationTime, info);
              }
          }
  
          if (lookupError) {
              Log.e(TAG, "selectInterfacesToDelete: falling back to arbitrary selection");
              return Arrays.asList(Arrays.copyOf(interfaces, excessInterfaces));
          } else {
              List<WifiIfaceInfo> result = new ArrayList<>(excessInterfaces);
              for (int i = 0; i < excessInterfaces; ++i) {
                  int lowPriorityNextIndex = orderedListLowPriority.size() - i - 1;
                  if (lowPriorityNextIndex >= 0) {
                      result.add(orderedListLowPriority.valueAt(lowPriorityNextIndex));
                  } else {
                      result.add(orderedList.valueAt(
                              orderedList.size() - i + orderedListLowPriority.size() - 1));
                  }
              }
              return result;
          }
      }
  
      /**
       * Performs chip reconfiguration per the input:
       * - Removes the specified interfaces
       * - Reconfigures the chip to the new chip mode (if necessary)
       * - Creates the new interface
       *
       * Returns the newly created interface or a null on any error.
       */
      private IWifiIface executeChipReconfiguration(IfaceCreationData ifaceCreationData,
              int ifaceType) {
          if (mDbg) {
              Log.d(TAG, "executeChipReconfiguration: ifaceCreationData=" + ifaceCreationData
                      + ", ifaceType=" + ifaceType);
          }
          synchronized (mLock) {
              try {
                  // is this a mode change?
                  boolean isModeConfigNeeded = !ifaceCreationData.chipInfo.currentModeIdValid
                          || ifaceCreationData.chipInfo.currentModeId != ifaceCreationData.chipModeId;
                  if (mDbg) Log.d(TAG, "isModeConfigNeeded=" + isModeConfigNeeded);
  
                  // first delete interfaces/change modes
                  if (isModeConfigNeeded) {
                      // remove all interfaces pre mode-change
                      // TODO: is this necessary? note that even if we don't want to explicitly
                      // remove the interfaces we do need to call the onDeleted callbacks - which
                      // this does
                      for (WifiIfaceInfo[] ifaceInfos: ifaceCreationData.chipInfo.ifaces) {
                          for (WifiIfaceInfo ifaceInfo: ifaceInfos) {
                              removeIfaceInternal(ifaceInfo.iface); // ignore return value
                          }
                      }
  
                      WifiStatus status = ifaceCreationData.chipInfo.chip.configureChip(
                              ifaceCreationData.chipModeId);
                      updateRttControllerOnModeChange();
                      if (status.code != WifiStatusCode.SUCCESS) {
                          Log.e(TAG, "executeChipReconfiguration: configureChip error: "
                                  + statusString(status));
                          return null;
                      }
                  } else {
                      // remove all interfaces on the delete list
                      for (WifiIfaceInfo ifaceInfo: ifaceCreationData.interfacesToBeRemovedFirst) {
                          removeIfaceInternal(ifaceInfo.iface); // ignore return value
                      }
                  }
  
                  // create new interface
                  Mutable<WifiStatus> statusResp = new Mutable<>();
                  Mutable<IWifiIface> ifaceResp = new Mutable<>();
                  switch (ifaceType) {
                      case IfaceType.STA:
                          ifaceCreationData.chipInfo.chip.createStaIface(
                                  (WifiStatus status, IWifiStaIface iface) -> {
                                      statusResp.value = status;
                                      ifaceResp.value = iface;
                                  });
                          break;
                      case IfaceType.AP:
                          ifaceCreationData.chipInfo.chip.createApIface(
                                  (WifiStatus status, IWifiApIface iface) -> {
                                      statusResp.value = status;
                                      ifaceResp.value = iface;
                                  });
                          break;
                      case IfaceType.P2P:
                          ifaceCreationData.chipInfo.chip.createP2pIface(
                                  (WifiStatus status, IWifiP2pIface iface) -> {
                                      statusResp.value = status;
                                      ifaceResp.value = iface;
                                  });
                          break;
                      case IfaceType.NAN:
                          ifaceCreationData.chipInfo.chip.createNanIface(
                                  (WifiStatus status, IWifiNanIface iface) -> {
                                      statusResp.value = status;
                                      ifaceResp.value = iface;
                                  });
                          break;
                  }
  
                  if (statusResp.value.code != WifiStatusCode.SUCCESS) {
                      Log.e(TAG, "executeChipReconfiguration: failed to create interface ifaceType="
                              + ifaceType + ": " + statusString(statusResp.value));
                      return null;
                  }
  
                  return ifaceResp.value;
              } catch (RemoteException e) {
                  Log.e(TAG, "executeChipReconfiguration exception: " + e);
                  return null;
              }
          }
      }
  
      private boolean removeIfaceInternal(IWifiIface iface) {
          String name = getName(iface);
          int type = getType(iface);
          if (mDbg) Log.d(TAG, "removeIfaceInternal: iface(name)=" + name + ", type=" + type);
  
          if (type == -1) {
              Log.e(TAG, "removeIfaceInternal: can't get type -- iface(name)=" + name);
              return false;
          }
  
          synchronized (mLock) {
              if (mWifi == null) {
                  Log.e(TAG, "removeIfaceInternal: null IWifi -- iface(name)=" + name);
                  return false;
              }
  
              IWifiChip chip = getChip(iface);
              if (chip == null) {
                  Log.e(TAG, "removeIfaceInternal: null IWifiChip -- iface(name)=" + name);
                  return false;
              }
  
              if (name == null) {
                  Log.e(TAG, "removeIfaceInternal: can't get name");
                  return false;
              }
  
              WifiStatus status = null;
              try {
                  switch (type) {
                      case IfaceType.STA:
                          status = chip.removeStaIface(name);
                          break;
                      case IfaceType.AP:
                          status = chip.removeApIface(name);
                          break;
                      case IfaceType.P2P:
                          status = chip.removeP2pIface(name);
                          break;
                      case IfaceType.NAN:
                          status = chip.removeNanIface(name);
                          break;
                      default:
                          Log.wtf(TAG, "removeIfaceInternal: invalid type=" + type);
                          return false;
                  }
              } catch (RemoteException e) {
                  Log.e(TAG, "IWifiChip.removeXxxIface exception: " + e);
              }
  
              // dispatch listeners no matter what status
              dispatchDestroyedListeners(name, type);
  
              if (status != null && status.code == WifiStatusCode.SUCCESS) {
                  return true;
              } else {
                  Log.e(TAG, "IWifiChip.removeXxxIface failed: " + statusString(status));
                  return false;
              }
          }
      }
  
      // dispatch all available for request listeners of the specified type AND clean-out the list:
      // listeners are called once at most!
      private boolean dispatchAvailableForRequestListeners() {
          if (VDBG) Log.d(TAG, "dispatchAvailableForRequestListeners");
  
          synchronized (mLock) {
              WifiChipInfo[] chipInfos = getAllChipInfo();
              if (chipInfos == null) {
                  Log.e(TAG, "dispatchAvailableForRequestListeners: no chip info found");
                  stopWifi(); // major error: shutting down
                  return false;
              }
              if (VDBG) {
                  Log.d(TAG, "dispatchAvailableForRequestListeners: chipInfos="
                          + Arrays.deepToString(chipInfos));
              }
  
              for (int ifaceType : IFACE_TYPES_BY_PRIORITY) {
                  dispatchAvailableForRequestListenersForType(ifaceType, chipInfos);
              }
          }
  
          return true;
      }
  
      private void dispatchAvailableForRequestListenersForType(int ifaceType,
              WifiChipInfo[] chipInfos) {
          if (VDBG) Log.d(TAG, "dispatchAvailableForRequestListenersForType: ifaceType=" + ifaceType);
  
          synchronized (mLock) {
              Map<InterfaceAvailableForRequestListenerProxy, Boolean> listeners =
                      mInterfaceAvailableForRequestListeners.get(ifaceType);
  
              if (listeners.size() == 0) {
                  return;
              }
  
              boolean isAvailable = isItPossibleToCreateIface(chipInfos, ifaceType);
  
              if (VDBG) {
                  Log.d(TAG, "Interface available for: ifaceType=" + ifaceType + " = " + isAvailable);
              }
              for (Map.Entry<InterfaceAvailableForRequestListenerProxy, Boolean> listenerEntry :
                      listeners.entrySet()) {
                  if (listenerEntry.getValue() == null || listenerEntry.getValue() != isAvailable) {
                      if (VDBG) {
                          Log.d(TAG, "Interface available listener dispatched: ifaceType=" + ifaceType
                                  + ", listener=" + listenerEntry.getKey());
                      }
                      listenerEntry.getKey().triggerWithArg(isAvailable);
                  }
                  listenerEntry.setValue(isAvailable);
              }
          }
      }
  
      // dispatch all destroyed listeners registered for the specified interface AND remove the
      // cache entry
      private void dispatchDestroyedListeners(String name, int type) {
          if (VDBG) Log.d(TAG, "dispatchDestroyedListeners: iface(name)=" + name);
  
          synchronized (mLock) {
              InterfaceCacheEntry entry = mInterfaceInfoCache.get(Pair.create(name, type));
              if (entry == null) {
                  Log.e(TAG, "dispatchDestroyedListeners: no cache entry for iface(name)=" + name);
                  return;
              }
  
              for (InterfaceDestroyedListenerProxy listener : entry.destroyedListeners) {
                  listener.trigger();
              }
              entry.destroyedListeners.clear(); // for insurance (though cache entry is removed)
              mInterfaceInfoCache.remove(Pair.create(name, type));
          }
      }
  
      // dispatch all destroyed listeners registered to all interfaces
      private void dispatchAllDestroyedListeners() {
          if (VDBG) Log.d(TAG, "dispatchAllDestroyedListeners");
  
          synchronized (mLock) {
              Iterator<Map.Entry<Pair<String, Integer>, InterfaceCacheEntry>> it =
                      mInterfaceInfoCache.entrySet().iterator();
              while (it.hasNext()) {
                  InterfaceCacheEntry entry = it.next().getValue();
                  for (InterfaceDestroyedListenerProxy listener : entry.destroyedListeners) {
                      listener.trigger();
                  }
                  entry.destroyedListeners.clear(); // for insurance (though cache entry is removed)
                  it.remove();
              }
          }
      }
  
      private abstract class ListenerProxy<LISTENER>  {
          protected LISTENER mListener;
          private Handler mHandler;
  
          // override equals & hash to make sure that the container HashSet is unique with respect to
          // the contained listener
          @Override
          public boolean equals(Object obj) {
              return mListener == ((ListenerProxy<LISTENER>) obj).mListener;
          }
  
          @Override
          public int hashCode() {
              return mListener.hashCode();
          }
  
          void trigger() {
              if (mHandler != null) {
                  mHandler.post(() -> {
                      action();
                  });
              } else {
                  action();
              }
          }
  
          void triggerWithArg(boolean arg) {
              if (mHandler != null) {
                  mHandler.post(() -> {
                      actionWithArg(arg);
                  });
              } else {
                  actionWithArg(arg);
              }
          }
  
          protected void action() {}
          protected void actionWithArg(boolean arg) {}
  
          ListenerProxy(LISTENER listener, Handler handler, String tag) {
              mListener = listener;
              mHandler = handler;
          }
      }
  
      private class InterfaceDestroyedListenerProxy extends
              ListenerProxy<InterfaceDestroyedListener> {
          private final String mIfaceName;
          InterfaceDestroyedListenerProxy(@NonNull String ifaceName,
                                          InterfaceDestroyedListener destroyedListener,
                                          Handler handler) {
              super(destroyedListener, handler, "InterfaceDestroyedListenerProxy");
              mIfaceName = ifaceName;
          }
  
          @Override
          protected void action() {
              mListener.onDestroyed(mIfaceName);
          }
      }
  
      private class InterfaceAvailableForRequestListenerProxy extends
              ListenerProxy<InterfaceAvailableForRequestListener> {
          InterfaceAvailableForRequestListenerProxy(
                  InterfaceAvailableForRequestListener destroyedListener, Handler handler) {
              super(destroyedListener, handler, "InterfaceAvailableForRequestListenerProxy");
          }
  
          @Override
          protected void actionWithArg(boolean isAvailable) {
              mListener.onAvailabilityChanged(isAvailable);
          }
      }
  
      private class InterfaceRttControllerLifecycleCallbackProxy implements
              InterfaceRttControllerLifecycleCallback {
          private InterfaceRttControllerLifecycleCallback mCallback;
          private Handler mHandler;
  
          InterfaceRttControllerLifecycleCallbackProxy(
                  InterfaceRttControllerLifecycleCallback callback, Handler handler) {
              mCallback = callback;
              mHandler = handler;
          }
  
          // override equals & hash to make sure that the container HashSet is unique with respect to
          // the contained listener
          @Override
          public boolean equals(Object obj) {
              return mCallback == ((InterfaceRttControllerLifecycleCallbackProxy) obj).mCallback;
          }
  
          @Override
          public int hashCode() {
              return mCallback.hashCode();
          }
  
          @Override
          public void onNewRttController(IWifiRttController controller) {
              mHandler.post(() -> mCallback.onNewRttController(controller));
          }
  
          @Override
          public void onRttControllerDestroyed() {
              mHandler.post(() -> mCallback.onRttControllerDestroyed());
          }
      }
  
      private void dispatchRttControllerLifecycleOnNew() {
          if (VDBG) {
              Log.v(TAG, "dispatchRttControllerLifecycleOnNew: # cbs="
                      + mRttControllerLifecycleCallbacks.size());
          }
          for (InterfaceRttControllerLifecycleCallbackProxy cbp : mRttControllerLifecycleCallbacks) {
              cbp.onNewRttController(mIWifiRttController);
          }
      }
  
      private void dispatchRttControllerLifecycleOnDestroyed() {
          for (InterfaceRttControllerLifecycleCallbackProxy cbp : mRttControllerLifecycleCallbacks) {
              cbp.onRttControllerDestroyed();
          }
      }
  
  
      /**
       * Updates the RttController when the chip mode is changed:
       * - Handles callbacks to registered listeners
       * - Handles creation of new RttController
       */
      private void updateRttControllerOnModeChange() {
          synchronized (mLock) {
              boolean controllerDestroyed = mIWifiRttController != null;
              mIWifiRttController = null;
              if (mRttControllerLifecycleCallbacks.size() == 0) {
                  Log.d(TAG, "updateRttController: no one is interested in RTT controllers");
                  return;
              }
  
              IWifiRttController newRttController = createRttControllerIfPossible();
              if (newRttController == null) {
                  if (controllerDestroyed) {
                      dispatchRttControllerLifecycleOnDestroyed();
                  }
              } else {
                  mIWifiRttController = newRttController;
                  dispatchRttControllerLifecycleOnNew();
              }
          }
      }
  
      /**
       * Try to create a new RttController.
       *
       * @return The new RttController - or null on failure.
       */
      private IWifiRttController createRttControllerIfPossible() {
          synchronized (mLock) {
              if (!isWifiStarted()) {
                  Log.d(TAG, "createRttControllerIfPossible: Wifi is not started");
                  return null;
              }
  
              WifiChipInfo[] chipInfos = getAllChipInfo();
              if (chipInfos == null) {
                  Log.d(TAG, "createRttControllerIfPossible: no chip info found - most likely chip "
                          + "not up yet");
                  return null;
              }
  
              for (WifiChipInfo chipInfo : chipInfos) {
                  if (!chipInfo.currentModeIdValid) {
                      if (VDBG) {
                          Log.d(TAG, "createRttControllerIfPossible: chip not configured yet: "
                                  + chipInfo);
                      }
                      continue;
                  }
  
                  Mutable<IWifiRttController> rttResp = new Mutable<>();
                  try {
                      chipInfo.chip.createRttController(null,
                              (WifiStatus status, IWifiRttController rtt) -> {
                                  if (status.code == WifiStatusCode.SUCCESS) {
                                      rttResp.value = rtt;
                                  } else {
                                      Log.e(TAG, "IWifiChip.createRttController failed: "
                                              + statusString(status));
                                  }
                              });
                  } catch (RemoteException e) {
                      Log.e(TAG, "IWifiChip.createRttController exception: " + e);
                  }
                  if (rttResp.value != null) {
                      return rttResp.value;
                  }
              }
          }
  
          Log.w(TAG, "createRttControllerIfPossible: not available from any of the chips");
          return null;
      }
  
      // general utilities
  
      private static String statusString(WifiStatus status) {
          if (status == null) {
              return "status=null";
          }
          StringBuilder sb = new StringBuilder();
          sb.append(status.code).append(" (").append(status.description).append(")");
          return sb.toString();
      }
  
      // Will return -1 for invalid results! Otherwise will return one of the 4 valid values.
      private static int getType(IWifiIface iface) {
          MutableInt typeResp = new MutableInt(-1);
          try {
              iface.getType((WifiStatus status, int type) -> {
                  if (status.code == WifiStatusCode.SUCCESS) {
                      typeResp.value = type;
                  } else {
                      Log.e(TAG, "Error on getType: " + statusString(status));
                  }
              });
          } catch (RemoteException e) {
              Log.e(TAG, "Exception on getType: " + e);
          }
  
          return typeResp.value;
      }
  
      /**
       * Dump the internal state of the class.
       */
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          pw.println("HalDeviceManager:");
          pw.println("  mServiceManager: " + mServiceManager);
          pw.println("  mWifi: " + mWifi);
          pw.println("  mManagerStatusListeners: " + mManagerStatusListeners);
          pw.println("  mInterfaceAvailableForRequestListeners: "
                  + mInterfaceAvailableForRequestListeners);
          pw.println("  mInterfaceInfoCache: " + mInterfaceInfoCache);
      }
  }
  