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.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.IWifiIface;
  import android.hardware.wifi.V1_0.IWifiStaIface;
  import android.hardware.wifi.V1_0.IWifiStaIfaceEventCallback;
  import android.hardware.wifi.V1_0.IfaceType;
  import android.hardware.wifi.V1_0.StaBackgroundScanBucketEventReportSchemeMask;
  import android.hardware.wifi.V1_0.StaBackgroundScanBucketParameters;
  import android.hardware.wifi.V1_0.StaBackgroundScanParameters;
  import android.hardware.wifi.V1_0.StaLinkLayerIfaceStats;
  import android.hardware.wifi.V1_0.StaLinkLayerRadioStats;
  import android.hardware.wifi.V1_0.StaLinkLayerStats;
  import android.hardware.wifi.V1_0.StaRoamingConfig;
  import android.hardware.wifi.V1_0.StaRoamingState;
  import android.hardware.wifi.V1_0.StaScanData;
  import android.hardware.wifi.V1_0.StaScanDataFlagMask;
  import android.hardware.wifi.V1_0.StaScanResult;
  import android.hardware.wifi.V1_0.WifiBand;
  import android.hardware.wifi.V1_0.WifiDebugHostWakeReasonStats;
  import android.hardware.wifi.V1_0.WifiDebugPacketFateFrameType;
  import android.hardware.wifi.V1_0.WifiDebugRingBufferFlags;
  import android.hardware.wifi.V1_0.WifiDebugRingBufferStatus;
  import android.hardware.wifi.V1_0.WifiDebugRxPacketFate;
  import android.hardware.wifi.V1_0.WifiDebugRxPacketFateReport;
  import android.hardware.wifi.V1_0.WifiDebugTxPacketFate;
  import android.hardware.wifi.V1_0.WifiDebugTxPacketFateReport;
  import android.hardware.wifi.V1_0.WifiInformationElement;
  import android.hardware.wifi.V1_0.WifiStatus;
  import android.hardware.wifi.V1_0.WifiStatusCode;
  import android.net.MacAddress;
  import android.net.apf.ApfCapabilities;
  import android.net.wifi.ScanResult;
  import android.net.wifi.WifiManager;
  import android.net.wifi.WifiScanner;
  import android.net.wifi.WifiSsid;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.RemoteException;
  import android.text.TextUtils;
  import android.util.Log;
  import android.util.MutableBoolean;
  import android.util.MutableLong;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.internal.util.ArrayUtils;
  import com.android.internal.util.HexDump;
  import com.android.server.wifi.HalDeviceManager.InterfaceDestroyedListener;
  import com.android.server.wifi.WifiLinkLayerStats.ChannelStats;
  import com.android.server.wifi.util.BitMask;
  import com.android.server.wifi.util.NativeUtil;
  
  import com.google.errorprone.annotations.CompileTimeConstant;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Set;
  import java.util.stream.Collectors;
  
  /**
   * Vendor HAL via HIDL
   */
  public class WifiVendorHal {
  
      private static final WifiLog sNoLog = new FakeWifiLog();
  
      /**
       * Chatty logging should use mVerboseLog
       */
      @VisibleForTesting
      WifiLog mVerboseLog = sNoLog;
  
      /**
       * Errors should use mLog
       */
      @VisibleForTesting
      WifiLog mLog = new LogcatLog("WifiVendorHal");
  
      /**
       * Enables or disables verbose logging
       *
       * @param verbose - with the obvious interpretation
       */
      public void enableVerboseLogging(boolean verbose) {
          synchronized (sLock) {
              if (verbose) {
                  mVerboseLog = mLog;
                  enter("verbose=true").flush();
              } else {
                  enter("verbose=false").flush();
                  mVerboseLog = sNoLog;
              }
          }
      }
  
      /**
       * Checks for a successful status result.
       *
       * Failures are logged to mLog.
       *
       * @param status is the WifiStatus generated by a hal call
       * @return true for success, false for failure
       */
      private boolean ok(WifiStatus status) {
          if (status.code == WifiStatusCode.SUCCESS) return true;
  
          Thread cur = Thread.currentThread();
          StackTraceElement[] trace = cur.getStackTrace();
  
          mLog.err("% failed %")
                  .c(niceMethodName(trace, 3))
                  .c(status.toString())
                  .flush();
  
          return false;
      }
  
      /**
       * Logs the argument along with the method name.
       *
       * Always returns its argument.
       */
      private boolean boolResult(boolean result) {
          if (mVerboseLog == sNoLog) return result;
          // Currently only seen if verbose logging is on
  
          Thread cur = Thread.currentThread();
          StackTraceElement[] trace = cur.getStackTrace();
  
          mVerboseLog.err("% returns %")
                  .c(niceMethodName(trace, 3))
                  .c(result)
                  .flush();
  
          return result;
      }
  
      /**
       * Logs the argument along with the method name.
       *
       * Always returns its argument.
       */
      private String stringResult(String result) {
          if (mVerboseLog == sNoLog) return result;
          // Currently only seen if verbose logging is on
  
          Thread cur = Thread.currentThread();
          StackTraceElement[] trace = cur.getStackTrace();
  
          mVerboseLog.err("% returns %")
                  .c(niceMethodName(trace, 3))
                  .c(result)
                  .flush();
  
          return result;
      }
  
      /**
       * Logs the argument along with the method name.
       *
       * Always returns its argument.
       */
      private byte[] byteArrayResult(byte[] result) {
          if (mVerboseLog == sNoLog) return result;
          // Currently only seen if verbose logging is on
  
          Thread cur = Thread.currentThread();
          StackTraceElement[] trace = cur.getStackTrace();
  
          mVerboseLog.err("% returns %")
                  .c(niceMethodName(trace, 3))
                  .c(result == null ? "(null)" : HexDump.dumpHexString(result))
                  .flush();
  
          return result;
      }
  
      /**
       * Logs at method entry
       *
       * @param format string with % placeholders
       * @return LogMessage formatter (remember to .flush())
       */
      private WifiLog.LogMessage enter(@CompileTimeConstant final String format) {
          if (mVerboseLog == sNoLog) return sNoLog.info(format);
          return mVerboseLog.trace(format, 1);
      }
  
      /**
       * Gets the method name and line number from a stack trace.
       *
       * Attempts to skip frames created by lambdas to get a human-sensible name.
       *
       * @param trace, fo example obtained by Thread.currentThread().getStackTrace()
       * @param start  frame number to log, typically 3
       * @return string containing the method name and line number
       */
      private static String niceMethodName(StackTraceElement[] trace, int start) {
          if (start >= trace.length) return "";
          StackTraceElement s = trace[start];
          String name = s.getMethodName();
          if (name.contains("lambda$")) {
              // Try to find a friendlier method name
              String myFile = s.getFileName();
              if (myFile != null) {
                  for (int i = start + 1; i < trace.length; i++) {
                      if (myFile.equals(trace[i].getFileName())) {
                          name = trace[i].getMethodName();
                          break;
                      }
                  }
              }
          }
          return (name + "(l." + s.getLineNumber() + ")");
      }
  
      // Vendor HAL HIDL interface objects.
      private IWifiChip mIWifiChip;
      private HashMap<String, IWifiStaIface> mIWifiStaIfaces = new HashMap<>();
      private HashMap<String, IWifiApIface> mIWifiApIfaces = new HashMap<>();
      private final HalDeviceManager mHalDeviceManager;
      private final HalDeviceManagerStatusListener mHalDeviceManagerStatusCallbacks;
      private final IWifiStaIfaceEventCallback mIWifiStaIfaceEventCallback;
      private final ChipEventCallback mIWifiChipEventCallback;
      private final ChipEventCallbackV12 mIWifiChipEventCallbackV12;
  
      // Plumbing for event handling.
      //
      // Being final fields, they can be accessed without synchronization under
      // some reasonable assumptions. See
      // https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.5
      private final Looper mLooper;
      private final Handler mHalEventHandler;
  
      public WifiVendorHal(HalDeviceManager halDeviceManager,
                           Looper looper) {
          mHalDeviceManager = halDeviceManager;
          mLooper = looper;
          mHalEventHandler = new Handler(looper);
          mHalDeviceManagerStatusCallbacks = new HalDeviceManagerStatusListener();
          mIWifiStaIfaceEventCallback = new StaIfaceEventCallback();
          mIWifiChipEventCallback = new ChipEventCallback();
          mIWifiChipEventCallbackV12 = new ChipEventCallbackV12();
      }
  
      public static final Object sLock = new Object();
  
      private void handleRemoteException(RemoteException e) {
          String methodName = niceMethodName(Thread.currentThread().getStackTrace(), 3);
          mVerboseLog.err("% RemoteException in HIDL call %").c(methodName).c(e.toString()).flush();
          clearState();
      }
  
      private WifiNative.VendorHalDeathEventHandler mDeathEventHandler;
  
      /**
       * Initialize the Hal device manager and register for status callbacks.
       *
       * @param handler Handler to notify if the vendor HAL dies.
       * @return true on success, false otherwise.
       */
      public boolean initialize(WifiNative.VendorHalDeathEventHandler handler) {
          synchronized (sLock) {
              mHalDeviceManager.initialize();
              mHalDeviceManager.registerStatusListener(
                      mHalDeviceManagerStatusCallbacks, mHalEventHandler);
              mDeathEventHandler = handler;
              return true;
          }
      }
  
      private WifiNative.VendorHalRadioModeChangeEventHandler mRadioModeChangeEventHandler;
  
      /**
       * Register to listen for radio mode change events from the HAL.
       *
       * @param handler Handler to notify when the vendor HAL detects a radio mode change.
       */
      public void registerRadioModeChangeHandler(
              WifiNative.VendorHalRadioModeChangeEventHandler handler) {
          synchronized (sLock) {
              mRadioModeChangeEventHandler = handler;
          }
      }
  
      /**
       * Returns whether the vendor HAL is supported on this device or not.
       */
      public boolean isVendorHalSupported() {
          synchronized (sLock) {
              return mHalDeviceManager.isSupported();
          }
      }
  
      /**
       * Bring up the HIDL Vendor HAL and configure for AP (Access Point) mode
       *
       * @return true for success
       */
      public boolean startVendorHalAp() {
          synchronized (sLock) {
              if (!startVendorHal()) {
                  return false;
              }
              if (TextUtils.isEmpty(createApIface(null))) {
                  stopVendorHal();
                  return false;
              }
              return true;
          }
      }
  
      /**
       * Bring up the HIDL Vendor HAL and configure for STA (Station) mode
       *
       * @return true for success
       */
      public boolean startVendorHalSta() {
          synchronized (sLock) {
              if (!startVendorHal()) {
                  return false;
              }
              if (TextUtils.isEmpty(createStaIface(false, null))) {
                  stopVendorHal();
                  return false;
              }
              return true;
          }
      }
  
      /**
       * Bring up the HIDL Vendor HAL.
       * @return true on success, false otherwise.
       */
      public boolean startVendorHal() {
          synchronized (sLock) {
              if (!mHalDeviceManager.start()) {
                  mLog.err("Failed to start vendor HAL").flush();
                  return false;
              }
              mLog.info("Vendor Hal started successfully").flush();
              return true;
          }
      }
  
      /** Helper method to lookup the corresponding STA iface object using iface name. */
      private IWifiStaIface getStaIface(@NonNull String ifaceName) {
          synchronized (sLock) {
              return mIWifiStaIfaces.get(ifaceName);
          }
      }
  
      private class StaInterfaceDestroyedListenerInternal implements InterfaceDestroyedListener {
          private final InterfaceDestroyedListener mExternalListener;
  
          StaInterfaceDestroyedListenerInternal(InterfaceDestroyedListener externalListener) {
              mExternalListener = externalListener;
          }
  
          @Override
          public void onDestroyed(@NonNull String ifaceName) {
              synchronized (sLock) {
                  mIWifiStaIfaces.remove(ifaceName);
              }
              if (mExternalListener != null) {
                  mExternalListener.onDestroyed(ifaceName);
              }
          }
      }
  
      /**
       * Create a STA iface using {@link HalDeviceManager}.
       *
       * @param lowPrioritySta The requested STA has a low request priority (lower probability of
       *                       getting created, higher probability of getting destroyed).
       * @param destroyedListener Listener to be invoked when the interface is destroyed.
       * @return iface name on success, null otherwise.
       */
      public String createStaIface(boolean lowPrioritySta,
              InterfaceDestroyedListener destroyedListener) {
          synchronized (sLock) {
              IWifiStaIface iface = mHalDeviceManager.createStaIface(lowPrioritySta,
                      new StaInterfaceDestroyedListenerInternal(destroyedListener), null);
              if (iface == null) {
                  mLog.err("Failed to create STA iface").flush();
                  return stringResult(null);
              }
              String ifaceName = mHalDeviceManager.getName((IWifiIface) iface);
              if (TextUtils.isEmpty(ifaceName)) {
                  mLog.err("Failed to get iface name").flush();
                  return stringResult(null);
              }
              if (!registerStaIfaceCallback(iface)) {
                  mLog.err("Failed to register STA iface callback").flush();
                  return stringResult(null);
              }
              if (!retrieveWifiChip((IWifiIface) iface)) {
                  mLog.err("Failed to get wifi chip").flush();
                  return stringResult(null);
              }
              enableLinkLayerStats(iface);
              mIWifiStaIfaces.put(ifaceName, iface);
              return ifaceName;
          }
      }
  
      /**
       * Remove a STA iface using {@link HalDeviceManager}.
       *
       * @param ifaceName Name of the interface being removed.
       * @return true on success, false otherwise.
       */
      public boolean removeStaIface(@NonNull String ifaceName) {
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return boolResult(false);
  
              if (!mHalDeviceManager.removeIface((IWifiIface) iface)) {
                  mLog.err("Failed to remove STA iface").flush();
                  return boolResult(false);
              }
              mIWifiStaIfaces.remove(ifaceName);
              return true;
          }
      }
  
      /** Helper method to lookup the corresponding AP iface object using iface name. */
      private IWifiApIface getApIface(@NonNull String ifaceName) {
          synchronized (sLock) {
              return mIWifiApIfaces.get(ifaceName);
          }
      }
  
      private class ApInterfaceDestroyedListenerInternal implements InterfaceDestroyedListener {
          private final InterfaceDestroyedListener mExternalListener;
  
          ApInterfaceDestroyedListenerInternal(InterfaceDestroyedListener externalListener) {
              mExternalListener = externalListener;
          }
  
          @Override
          public void onDestroyed(@NonNull String ifaceName) {
              synchronized (sLock) {
                  mIWifiApIfaces.remove(ifaceName);
              }
              if (mExternalListener != null) {
                  mExternalListener.onDestroyed(ifaceName);
              }
          }
      }
  
  
      /**
       * Create a AP iface using {@link HalDeviceManager}.
       *
       * @param destroyedListener Listener to be invoked when the interface is destroyed.
       * @return iface name on success, null otherwise.
       */
      public String createApIface(InterfaceDestroyedListener destroyedListener) {
          synchronized (sLock) {
              IWifiApIface iface = mHalDeviceManager.createApIface(
                      new ApInterfaceDestroyedListenerInternal(destroyedListener), null);
              if (iface == null) {
                  mLog.err("Failed to create AP iface").flush();
                  return stringResult(null);
              }
              String ifaceName = mHalDeviceManager.getName((IWifiIface) iface);
              if (TextUtils.isEmpty(ifaceName)) {
                  mLog.err("Failed to get iface name").flush();
                  return stringResult(null);
              }
              if (!retrieveWifiChip((IWifiIface) iface)) {
                  mLog.err("Failed to get wifi chip").flush();
                  return stringResult(null);
              }
              mIWifiApIfaces.put(ifaceName, iface);
              return ifaceName;
          }
      }
  
      /**
       * Remove an AP iface using {@link HalDeviceManager}.
       *
       * @param ifaceName Name of the interface being removed.
       * @return true on success, false otherwise.
       */
      public boolean removeApIface(@NonNull String ifaceName) {
          synchronized (sLock) {
              IWifiApIface iface = getApIface(ifaceName);
              if (iface == null) return boolResult(false);
  
              if (!mHalDeviceManager.removeIface((IWifiIface) iface)) {
                  mLog.err("Failed to remove AP iface").flush();
                  return boolResult(false);
              }
              mIWifiApIfaces.remove(ifaceName);
              return true;
          }
      }
  
      private boolean retrieveWifiChip(IWifiIface iface) {
          synchronized (sLock) {
              boolean registrationNeeded = mIWifiChip == null;
              mIWifiChip = mHalDeviceManager.getChip(iface);
              if (mIWifiChip == null) {
                  mLog.err("Failed to get the chip created for the Iface").flush();
                  return false;
              }
              if (!registrationNeeded) {
                  return true;
              }
              if (!registerChipCallback()) {
                  mLog.err("Failed to register chip callback").flush();
                  mIWifiChip = null;
                  return false;
              }
              return true;
          }
      }
  
      /**
       * Registers the sta iface callback.
       */
      private boolean registerStaIfaceCallback(IWifiStaIface iface) {
          synchronized (sLock) {
              if (iface == null) return boolResult(false);
              if (mIWifiStaIfaceEventCallback == null) return boolResult(false);
              try {
                  WifiStatus status =
                          iface.registerEventCallback(mIWifiStaIfaceEventCallback);
                  return ok(status);
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Registers the sta iface callback.
       */
      private boolean registerChipCallback() {
          synchronized (sLock) {
              if (mIWifiChip == null) return boolResult(false);
              try {
                  WifiStatus status;
                  android.hardware.wifi.V1_2.IWifiChip iWifiChipV12 = getWifiChipForV1_2Mockable();
                  if (iWifiChipV12 != null) {
                      status = iWifiChipV12.registerEventCallback_1_2(mIWifiChipEventCallbackV12);
                  } else {
                      status = mIWifiChip.registerEventCallback(mIWifiChipEventCallback);
                  }
                  return ok(status);
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Stops the HAL
       */
      public void stopVendorHal() {
          synchronized (sLock) {
              mHalDeviceManager.stop();
              clearState();
              mLog.info("Vendor Hal stopped").flush();
          }
      }
  
      /**
       * Clears the state associated with a started Iface
       *
       * Caller should hold the lock.
       */
      private void clearState() {
          mIWifiChip = null;
          mIWifiStaIfaces.clear();
          mIWifiApIfaces.clear();
          mDriverDescription = null;
          mFirmwareDescription = null;
      }
  
      /**
       * Tests whether the HAL is started and atleast one iface is up.
       */
      public boolean isHalStarted() {
          // For external use only. Methods in this class should test for null directly.
          synchronized (sLock) {
              return (!mIWifiStaIfaces.isEmpty() || !mIWifiApIfaces.isEmpty());
          }
      }
  
      /**
       * Gets the scan capabilities
       *
       * @param ifaceName Name of the interface.
       * @param capabilities object to be filled in
       * @return true for success, false for failure
       */
      public boolean getBgScanCapabilities(
              @NonNull String ifaceName, WifiNative.ScanCapabilities capabilities) {
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return boolResult(false);
              try {
                  MutableBoolean ans = new MutableBoolean(false);
                  WifiNative.ScanCapabilities out = capabilities;
                  iface.getBackgroundScanCapabilities((status, cap) -> {
                              if (!ok(status)) return;
                              mVerboseLog.info("scan capabilities %").c(cap.toString()).flush();
                              out.max_scan_cache_size = cap.maxCacheSize;
                              out.max_ap_cache_per_scan = cap.maxApCachePerScan;
                              out.max_scan_buckets = cap.maxBuckets;
                              out.max_rssi_sample_size = 0;
                              out.max_scan_reporting_threshold = cap.maxReportingThreshold;
                              ans.value = true;
                          }
                  );
                  return ans.value;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Holds the current background scan state, to implement pause and restart
       */
      @VisibleForTesting
      class CurrentBackgroundScan {
          public int cmdId;
          public StaBackgroundScanParameters param;
          public WifiNative.ScanEventHandler eventHandler = null;
          public boolean paused = false;
          public WifiScanner.ScanData[] latestScanResults = null;
  
          CurrentBackgroundScan(int id, WifiNative.ScanSettings settings) {
              cmdId = id;
              param = new StaBackgroundScanParameters();
              param.basePeriodInMs = settings.base_period_ms;
              param.maxApPerScan = settings.max_ap_per_scan;
              param.reportThresholdPercent = settings.report_threshold_percent;
              param.reportThresholdNumScans = settings.report_threshold_num_scans;
              if (settings.buckets != null) {
                  for (WifiNative.BucketSettings bs : settings.buckets) {
                      param.buckets.add(makeStaBackgroundScanBucketParametersFromBucketSettings(bs));
                  }
              }
          }
      }
  
      /**
       * Makes the Hal flavor of WifiNative.BucketSettings
       *
       * @param bs WifiNative.BucketSettings
       * @return Hal flavor of bs
       * @throws IllegalArgumentException if band value is not recognized
       */
      private StaBackgroundScanBucketParameters
              makeStaBackgroundScanBucketParametersFromBucketSettings(WifiNative.BucketSettings bs) {
          StaBackgroundScanBucketParameters pa = new StaBackgroundScanBucketParameters();
          pa.bucketIdx = bs.bucket;
          pa.band = makeWifiBandFromFrameworkBand(bs.band);
          if (bs.channels != null) {
              for (WifiNative.ChannelSettings cs : bs.channels) {
                  pa.frequencies.add(cs.frequency);
              }
          }
          pa.periodInMs = bs.period_ms;
          pa.eventReportScheme = makeReportSchemeFromBucketSettingsReportEvents(bs.report_events);
          pa.exponentialMaxPeriodInMs = bs.max_period_ms;
          // Although HAL API allows configurable base value for the truncated
          // exponential back off scan. Native API and above support only
          // truncated binary exponential back off scan.
          // Hard code value of base to 2 here.
          pa.exponentialBase = 2;
          pa.exponentialStepCount = bs.step_count;
          return pa;
      }
  
      /**
       * Makes the Hal flavor of WifiScanner's band indication
       *
       * @param frameworkBand one of WifiScanner.WIFI_BAND_*
       * @return A WifiBand value
       * @throws IllegalArgumentException if frameworkBand is not recognized
       */
      private int makeWifiBandFromFrameworkBand(int frameworkBand) {
          switch (frameworkBand) {
              case WifiScanner.WIFI_BAND_UNSPECIFIED:
                  return WifiBand.BAND_UNSPECIFIED;
              case WifiScanner.WIFI_BAND_24_GHZ:
                  return WifiBand.BAND_24GHZ;
              case WifiScanner.WIFI_BAND_5_GHZ:
                  return WifiBand.BAND_5GHZ;
              case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY:
                  return WifiBand.BAND_5GHZ_DFS;
              case WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS:
                  return WifiBand.BAND_5GHZ_WITH_DFS;
              case WifiScanner.WIFI_BAND_BOTH:
                  return WifiBand.BAND_24GHZ_5GHZ;
              case WifiScanner.WIFI_BAND_BOTH_WITH_DFS:
                  return WifiBand.BAND_24GHZ_5GHZ_WITH_DFS;
              default:
                  throw new IllegalArgumentException("bad band " + frameworkBand);
          }
      }
  
      /**
       * Makes the Hal flavor of WifiScanner's report event mask
       *
       * @param reportUnderscoreEvents is logical OR of WifiScanner.REPORT_EVENT_* values
       * @return Corresponding StaBackgroundScanBucketEventReportSchemeMask value
       * @throws IllegalArgumentException if a mask bit is not recognized
       */
      private int makeReportSchemeFromBucketSettingsReportEvents(int reportUnderscoreEvents) {
          int ans = 0;
          BitMask in = new BitMask(reportUnderscoreEvents);
          if (in.testAndClear(WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN)) {
              ans |= StaBackgroundScanBucketEventReportSchemeMask.EACH_SCAN;
          }
          if (in.testAndClear(WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT)) {
              ans |= StaBackgroundScanBucketEventReportSchemeMask.FULL_RESULTS;
          }
          if (in.testAndClear(WifiScanner.REPORT_EVENT_NO_BATCH)) {
              ans |= StaBackgroundScanBucketEventReportSchemeMask.NO_BATCH;
          }
          if (in.value != 0) throw new IllegalArgumentException("bad " + reportUnderscoreEvents);
          return ans;
      }
  
      private int mLastScanCmdId; // For assigning cmdIds to scans
  
      @VisibleForTesting
      CurrentBackgroundScan mScan = null;
  
      /**
       * Starts a background scan
       *
       * Any ongoing scan will be stopped first
       *
       * @param ifaceName    Name of the interface.
       * @param settings     to control the scan
       * @param eventHandler to call with the results
       * @return true for success
       */
      public boolean startBgScan(@NonNull String ifaceName,
                                 WifiNative.ScanSettings settings,
                                 WifiNative.ScanEventHandler eventHandler) {
          WifiStatus status;
          if (eventHandler == null) return boolResult(false);
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return boolResult(false);
              try {
                  if (mScan != null && !mScan.paused) {
                      ok(iface.stopBackgroundScan(mScan.cmdId));
                      mScan = null;
                  }
                  mLastScanCmdId = (mLastScanCmdId % 9) + 1; // cycle through non-zero single digits
                  CurrentBackgroundScan scan = new CurrentBackgroundScan(mLastScanCmdId, settings);
                  status = iface.startBackgroundScan(scan.cmdId, scan.param);
                  if (!ok(status)) return false;
                  scan.eventHandler = eventHandler;
                  mScan = scan;
                  return true;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
  
      /**
       * Stops any ongoing backgound scan
       *
       * @param ifaceName Name of the interface.
       */
      public void stopBgScan(@NonNull String ifaceName) {
          WifiStatus status;
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return;
              try {
                  if (mScan != null) {
                      ok(iface.stopBackgroundScan(mScan.cmdId));
                      mScan = null;
                  }
              } catch (RemoteException e) {
                  handleRemoteException(e);
              }
          }
      }
  
      /**
       * Pauses an ongoing backgound scan
       *
       * @param ifaceName Name of the interface.
       */
      public void pauseBgScan(@NonNull String ifaceName) {
          WifiStatus status;
          synchronized (sLock) {
              try {
                  IWifiStaIface iface = getStaIface(ifaceName);
                  if (iface == null) return;
                  if (mScan != null && !mScan.paused) {
                      status = iface.stopBackgroundScan(mScan.cmdId);
                      if (!ok(status)) return;
                      mScan.paused = true;
                  }
              } catch (RemoteException e) {
                  handleRemoteException(e);
              }
          }
      }
  
      /**
       * Restarts a paused background scan
       *
       * @param ifaceName Name of the interface.
       */
      public void restartBgScan(@NonNull String ifaceName) {
          WifiStatus status;
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return;
              try {
                  if (mScan != null && mScan.paused) {
                      status = iface.startBackgroundScan(mScan.cmdId, mScan.param);
                      if (!ok(status)) return;
                      mScan.paused = false;
                  }
              } catch (RemoteException e) {
                  handleRemoteException(e);
              }
          }
      }
  
      /**
       * Gets the latest scan results received from the HIDL interface callback.
       * TODO(b/35754840): This hop to fetch scan results after callback is unnecessary. Refactor
       * WifiScanner to use the scan results from the callback.
       *
       * @param ifaceName Name of the interface.
       */
      public WifiScanner.ScanData[] getBgScanResults(@NonNull String ifaceName) {
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return null;
              if (mScan == null) return null;
              return mScan.latestScanResults;
          }
      }
  
      /**
       * Get the link layer statistics
       *
       * Note - we always enable link layer stats on a STA interface.
       *
       * @param ifaceName Name of the interface.
       * @return the statistics, or null if unable to do so
       */
      public WifiLinkLayerStats getWifiLinkLayerStats(@NonNull String ifaceName) {
          if (getWifiStaIfaceForV1_3Mockable(ifaceName) != null) {
              return getWifiLinkLayerStats_1_3_Internal(ifaceName);
          }
          return getWifiLinkLayerStats_internal(ifaceName);
      }
  
      private WifiLinkLayerStats getWifiLinkLayerStats_internal(@NonNull String ifaceName) {
          class AnswerBox {
              public StaLinkLayerStats value = null;
          }
          AnswerBox answer = new AnswerBox();
          synchronized (sLock) {
              try {
                  IWifiStaIface iface = getStaIface(ifaceName);
                  if (iface == null) return null;
                  iface.getLinkLayerStats((status, stats) -> {
                      if (!ok(status)) return;
                      answer.value = stats;
                  });
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return null;
              }
          }
          WifiLinkLayerStats stats = frameworkFromHalLinkLayerStats(answer.value);
          return stats;
      }
  
      private WifiLinkLayerStats getWifiLinkLayerStats_1_3_Internal(@NonNull String ifaceName) {
          class AnswerBox {
              public android.hardware.wifi.V1_3.StaLinkLayerStats value = null;
          }
          AnswerBox answer = new AnswerBox();
          synchronized (sLock) {
              try {
                  android.hardware.wifi.V1_3.IWifiStaIface iface =
                          getWifiStaIfaceForV1_3Mockable(ifaceName);
                  if (iface == null) return null;
                  iface.getLinkLayerStats_1_3((status, stats) -> {
                      if (!ok(status)) return;
                      answer.value = stats;
                  });
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return null;
              }
          }
          WifiLinkLayerStats stats = frameworkFromHalLinkLayerStats_1_3(answer.value);
          return stats;
      }
  
  
      /**
       * Makes the framework version of link layer stats from the hal version.
       */
      @VisibleForTesting
      static WifiLinkLayerStats frameworkFromHalLinkLayerStats(StaLinkLayerStats stats) {
          if (stats == null) return null;
          WifiLinkLayerStats out = new WifiLinkLayerStats();
          setIfaceStats(out, stats.iface);
          setRadioStats(out, stats.radios);
          setTimeStamp(out, stats.timeStampInMs);
          out.version = WifiLinkLayerStats.V1_0;
          return out;
      }
  
      /**
       * Makes the framework version of link layer stats from the hal version.
       */
      @VisibleForTesting
      static WifiLinkLayerStats frameworkFromHalLinkLayerStats_1_3(
              android.hardware.wifi.V1_3.StaLinkLayerStats stats) {
          if (stats == null) return null;
          WifiLinkLayerStats out = new WifiLinkLayerStats();
          setIfaceStats(out, stats.iface);
          setRadioStats_1_3(out, stats.radios);
          setTimeStamp(out, stats.timeStampInMs);
          out.version = WifiLinkLayerStats.V1_3;
          return out;
      }
  
      private static void setIfaceStats(WifiLinkLayerStats stats, StaLinkLayerIfaceStats iface) {
          if (iface == null) return;
          stats.beacon_rx = iface.beaconRx;
          stats.rssi_mgmt = iface.avgRssiMgmt;
          // Statistics are broken out by Wireless Multimedia Extensions categories
          // WME Best Effort Access Category
          stats.rxmpdu_be = iface.wmeBePktStats.rxMpdu;
          stats.txmpdu_be = iface.wmeBePktStats.txMpdu;
          stats.lostmpdu_be = iface.wmeBePktStats.lostMpdu;
          stats.retries_be = iface.wmeBePktStats.retries;
          // WME Background Access Category
          stats.rxmpdu_bk = iface.wmeBkPktStats.rxMpdu;
          stats.txmpdu_bk = iface.wmeBkPktStats.txMpdu;
          stats.lostmpdu_bk = iface.wmeBkPktStats.lostMpdu;
          stats.retries_bk = iface.wmeBkPktStats.retries;
          // WME Video Access Category
          stats.rxmpdu_vi = iface.wmeViPktStats.rxMpdu;
          stats.txmpdu_vi = iface.wmeViPktStats.txMpdu;
          stats.lostmpdu_vi = iface.wmeViPktStats.lostMpdu;
          stats.retries_vi = iface.wmeViPktStats.retries;
          // WME Voice Access Category
          stats.rxmpdu_vo = iface.wmeVoPktStats.rxMpdu;
          stats.txmpdu_vo = iface.wmeVoPktStats.txMpdu;
          stats.lostmpdu_vo = iface.wmeVoPktStats.lostMpdu;
          stats.retries_vo = iface.wmeVoPktStats.retries;
      }
  
      private static void setRadioStats(WifiLinkLayerStats stats,
              List<StaLinkLayerRadioStats> radios) {
          if (radios == null) return;
          // NOTE(b/36176141): Figure out how to coalesce this info for multi radio devices.
          if (radios.size() > 0) {
              StaLinkLayerRadioStats radioStats = radios.get(0);
              stats.on_time = radioStats.onTimeInMs;
              stats.tx_time = radioStats.txTimeInMs;
              stats.tx_time_per_level = new int[radioStats.txTimeInMsPerLevel.size()];
              for (int i = 0; i < stats.tx_time_per_level.length; i++) {
                  stats.tx_time_per_level[i] = radioStats.txTimeInMsPerLevel.get(i);
              }
              stats.rx_time = radioStats.rxTimeInMs;
              stats.on_time_scan = radioStats.onTimeInMsForScan;
          }
      }
  
      private static void setRadioStats_1_3(WifiLinkLayerStats stats,
              List<android.hardware.wifi.V1_3.StaLinkLayerRadioStats> radios) {
          if (radios == null) return;
          // NOTE(b/36176141): Figure out how to coalesce this info for multi radio devices.
          if (radios.size() > 0) {
              android.hardware.wifi.V1_3.StaLinkLayerRadioStats radioStats = radios.get(0);
              stats.on_time = radioStats.V1_0.onTimeInMs;
              stats.tx_time = radioStats.V1_0.txTimeInMs;
              stats.tx_time_per_level = new int[radioStats.V1_0.txTimeInMsPerLevel.size()];
              for (int i = 0; i < stats.tx_time_per_level.length; i++) {
                  stats.tx_time_per_level[i] = radioStats.V1_0.txTimeInMsPerLevel.get(i);
              }
              stats.rx_time = radioStats.V1_0.rxTimeInMs;
              stats.on_time_scan = radioStats.V1_0.onTimeInMsForScan;
              stats.on_time_nan_scan = radioStats.onTimeInMsForNanScan;
              stats.on_time_background_scan = radioStats.onTimeInMsForBgScan;
              stats.on_time_roam_scan = radioStats.onTimeInMsForRoamScan;
              stats.on_time_pno_scan = radioStats.onTimeInMsForPnoScan;
              stats.on_time_hs20_scan = radioStats.onTimeInMsForHs20Scan;
              /* Copy list of channel stats */
              for (int i = 0; i < radioStats.channelStats.size(); i++) {
                  android.hardware.wifi.V1_3.WifiChannelStats channelStats =
                          radioStats.channelStats.get(i);
                  ChannelStats channelStatsEntry = new ChannelStats();
                  channelStatsEntry.frequency = channelStats.channel.centerFreq;
                  channelStatsEntry.radioOnTimeMs = channelStats.onTimeInMs;
                  channelStatsEntry.ccaBusyTimeMs = channelStats.ccaBusyTimeInMs;
                  stats.channelStatsMap.put(channelStats.channel.centerFreq, channelStatsEntry);
              }
          }
      }
  
      private static void setTimeStamp(WifiLinkLayerStats stats, long timeStampInMs) {
          stats.timeStampInMs = timeStampInMs;
      }
  
      @VisibleForTesting
      boolean mLinkLayerStatsDebug = false;  // Passed to Hal
  
      /**
       * Enables the linkLayerStats in the Hal.
       *
       * This is called unconditionally whenever we create a STA interface.
       *
       * @param iface Iface object.
       */
      private void enableLinkLayerStats(IWifiStaIface iface) {
          synchronized (sLock) {
              try {
                  WifiStatus status;
                  status = iface.enableLinkLayerStatsCollection(mLinkLayerStatsDebug);
                  if (!ok(status)) {
                      mLog.err("unable to enable link layer stats collection").flush();
                  }
              } catch (RemoteException e) {
                  handleRemoteException(e);
              }
          }
      }
  
      /**
       * Translation table used by getSupportedFeatureSet for translating IWifiChip caps for V1.1
       */
      private static final long[][] sChipFeatureCapabilityTranslation = {
              {WifiManager.WIFI_FEATURE_TX_POWER_LIMIT,
                      android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.SET_TX_POWER_LIMIT
              },
              {WifiManager.WIFI_FEATURE_D2D_RTT,
                      android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2D_RTT
              },
              {WifiManager.WIFI_FEATURE_D2AP_RTT,
                      android.hardware.wifi.V1_1.IWifiChip.ChipCapabilityMask.D2AP_RTT
              }
      };
  
      /**
       * Translation table used by getSupportedFeatureSet for translating IWifiChip caps for
       * additional capabilities introduced in V1.3
       */
      private static final long[][] sChipFeatureCapabilityTranslation13 = {
              {WifiManager.WIFI_FEATURE_LOW_LATENCY,
                      android.hardware.wifi.V1_3.IWifiChip.ChipCapabilityMask.SET_LATENCY_MODE
              },
              {WifiManager.WIFI_FEATURE_P2P_RAND_MAC,
                      android.hardware.wifi.V1_3.IWifiChip.ChipCapabilityMask.P2P_RAND_MAC
              }
  
      };
  
      /**
       * Feature bit mask translation for Chip V1.1
       *
       * @param capabilities bitmask defined IWifiChip.ChipCapabilityMask
       * @return bitmask defined by WifiManager.WIFI_FEATURE_*
       */
      @VisibleForTesting
      int wifiFeatureMaskFromChipCapabilities(int capabilities) {
          int features = 0;
          for (int i = 0; i < sChipFeatureCapabilityTranslation.length; i++) {
              if ((capabilities & sChipFeatureCapabilityTranslation[i][1]) != 0) {
                  features |= sChipFeatureCapabilityTranslation[i][0];
              }
          }
          return features;
      }
  
      /**
       * Feature bit mask translation for Chip V1.3
       *
       * @param capabilities bitmask defined IWifiChip.ChipCapabilityMask
       * @return bitmask defined by WifiManager.WIFI_FEATURE_*
       */
      @VisibleForTesting
      long wifiFeatureMaskFromChipCapabilities_1_3(int capabilities) {
          // First collect features from previous versions
          long features = wifiFeatureMaskFromChipCapabilities(capabilities);
  
          // Next collect features for V1_3 version
          for (int i = 0; i < sChipFeatureCapabilityTranslation13.length; i++) {
              if ((capabilities & sChipFeatureCapabilityTranslation13[i][1]) != 0) {
                  features |= sChipFeatureCapabilityTranslation13[i][0];
              }
          }
  
          return features;
      }
  
      /**
       * Translation table used by getSupportedFeatureSet for translating IWifiStaIface caps
       */
      private static final long[][] sStaFeatureCapabilityTranslation = {
              {WifiManager.WIFI_FEATURE_INFRA_5G,
                      IWifiStaIface.StaIfaceCapabilityMask.STA_5G
              },
              {WifiManager.WIFI_FEATURE_PASSPOINT,
                      IWifiStaIface.StaIfaceCapabilityMask.HOTSPOT
              },
              {WifiManager.WIFI_FEATURE_SCANNER,
                      IWifiStaIface.StaIfaceCapabilityMask.BACKGROUND_SCAN,
              },
              {WifiManager.WIFI_FEATURE_PNO,
                      IWifiStaIface.StaIfaceCapabilityMask.PNO
              },
              {WifiManager.WIFI_FEATURE_TDLS,
                      IWifiStaIface.StaIfaceCapabilityMask.TDLS
              },
              {WifiManager.WIFI_FEATURE_TDLS_OFFCHANNEL,
                      IWifiStaIface.StaIfaceCapabilityMask.TDLS_OFFCHANNEL
              },
              {WifiManager.WIFI_FEATURE_LINK_LAYER_STATS,
                      IWifiStaIface.StaIfaceCapabilityMask.LINK_LAYER_STATS
              },
              {WifiManager.WIFI_FEATURE_RSSI_MONITOR,
                      IWifiStaIface.StaIfaceCapabilityMask.RSSI_MONITOR
              },
              {WifiManager.WIFI_FEATURE_MKEEP_ALIVE,
                      IWifiStaIface.StaIfaceCapabilityMask.KEEP_ALIVE
              },
              {WifiManager.WIFI_FEATURE_CONFIG_NDO,
                      IWifiStaIface.StaIfaceCapabilityMask.ND_OFFLOAD
              },
              {WifiManager.WIFI_FEATURE_CONTROL_ROAMING,
                      IWifiStaIface.StaIfaceCapabilityMask.CONTROL_ROAMING
              },
              {WifiManager.WIFI_FEATURE_IE_WHITELIST,
                      IWifiStaIface.StaIfaceCapabilityMask.PROBE_IE_WHITELIST
              },
              {WifiManager.WIFI_FEATURE_SCAN_RAND,
                      IWifiStaIface.StaIfaceCapabilityMask.SCAN_RAND
              },
      };
  
      /**
       * Feature bit mask translation for STAs
       *
       * @param capabilities bitmask defined IWifiStaIface.StaIfaceCapabilityMask
       * @return bitmask defined by WifiManager.WIFI_FEATURE_*
       */
      @VisibleForTesting
      long wifiFeatureMaskFromStaCapabilities(int capabilities) {
          long features = 0;
          for (int i = 0; i < sStaFeatureCapabilityTranslation.length; i++) {
              if ((capabilities & sStaFeatureCapabilityTranslation[i][1]) != 0) {
                  features |= sStaFeatureCapabilityTranslation[i][0];
              }
          }
          return features;
      }
  
      /**
       * Get the supported features
       *
       * The result may differ depending on the mode (STA or AP)
       *
       * @param ifaceName Name of the interface.
       * @return bitmask defined by WifiManager.WIFI_FEATURE_*
       */
      public long getSupportedFeatureSet(@NonNull String ifaceName) {
          long featureSet = 0;
          if (!mHalDeviceManager.isStarted()) {
              return featureSet; // TODO: can't get capabilities with Wi-Fi down
          }
          try {
              final MutableLong feat = new MutableLong(0);
              synchronized (sLock) {
                  android.hardware.wifi.V1_3.IWifiChip iWifiChipV13 = getWifiChipForV1_3Mockable();
                  if (iWifiChipV13 != null) {
                      iWifiChipV13.getCapabilities_1_3((status, capabilities) -> {
                          if (!ok(status)) return;
                          feat.value = wifiFeatureMaskFromChipCapabilities_1_3(capabilities);
                      });
                  } else if (mIWifiChip != null) {
                      mIWifiChip.getCapabilities((status, capabilities) -> {
                          if (!ok(status)) return;
                          feat.value = wifiFeatureMaskFromChipCapabilities(capabilities);
                      });
                  }
                  IWifiStaIface iface = getStaIface(ifaceName);
                  if (iface != null) {
                      iface.getCapabilities((status, capabilities) -> {
                          if (!ok(status)) return;
                          feat.value |= wifiFeatureMaskFromStaCapabilities(capabilities);
                      });
                  }
              }
              featureSet = feat.value;
          } catch (RemoteException e) {
              handleRemoteException(e);
              return 0;
          }
  
          Set<Integer> supportedIfaceTypes = mHalDeviceManager.getSupportedIfaceTypes();
          if (supportedIfaceTypes.contains(IfaceType.STA)) {
              featureSet |= WifiManager.WIFI_FEATURE_INFRA;
          }
          if (supportedIfaceTypes.contains(IfaceType.AP)) {
              featureSet |= WifiManager.WIFI_FEATURE_MOBILE_HOTSPOT;
          }
          if (supportedIfaceTypes.contains(IfaceType.P2P)) {
              featureSet |= WifiManager.WIFI_FEATURE_P2P;
          }
          if (supportedIfaceTypes.contains(IfaceType.NAN)) {
              featureSet |= WifiManager.WIFI_FEATURE_AWARE;
          }
  
          return featureSet;
      }
  
      /**
       * Set the MAC OUI during scanning.
       * <p>
       * An OUI {Organizationally Unique Identifier} is a 24-bit number that
       * uniquely identifies a vendor or manufacturer.
       *
       * @param ifaceName Name of the interface.
       * @param oui
       * @return true for success
       */
      public boolean setScanningMacOui(@NonNull String ifaceName, byte[] oui) {
          if (oui == null) return boolResult(false);
          if (oui.length != 3) return boolResult(false);
          synchronized (sLock) {
              try {
                  IWifiStaIface iface = getStaIface(ifaceName);
                  if (iface == null) return boolResult(false);
                  WifiStatus status = iface.setScanningMacOui(oui);
                  if (!ok(status)) return false;
                  return true;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Set Mac address on the given interface
       *
       * @param ifaceName Name of the interface
       * @param mac MAC address to change into
       * @return true for success
       */
      public boolean setMacAddress(@NonNull String ifaceName, @NonNull MacAddress mac) {
          byte[] macByteArray = mac.toByteArray();
          synchronized (sLock) {
              try {
                  android.hardware.wifi.V1_2.IWifiStaIface ifaceV12 =
                          getWifiStaIfaceForV1_2Mockable(ifaceName);
                  if (ifaceV12 == null) return boolResult(false);
                  WifiStatus status = ifaceV12.setMacAddress(macByteArray);
                  if (!ok(status)) return false;
                  return true;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Get factory MAC address of the given interface
       *
       * @param ifaceName Name of the interface
       * @return factory MAC address of the interface or null.
       */
      public MacAddress getFactoryMacAddress(@NonNull String ifaceName) {
          class AnswerBox {
              public MacAddress mac = null;
          }
          synchronized (sLock) {
              try {
                  android.hardware.wifi.V1_3.IWifiStaIface ifaceV13 =
                          getWifiStaIfaceForV1_3Mockable(ifaceName);
                  if (ifaceV13 == null) return null;
                  AnswerBox box = new AnswerBox();
                  ifaceV13.getFactoryMacAddress((status, macBytes) -> {
                      if (!ok(status)) return;
                      box.mac = MacAddress.fromBytes(macBytes);
                  });
                  return box.mac;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return null;
              }
          }
      }
  
      /**
       * Get the APF (Android Packet Filter) capabilities of the device
       *
       * @param ifaceName Name of the interface.
       * @return APF capabilities object.
       */
      public ApfCapabilities getApfCapabilities(@NonNull String ifaceName) {
          class AnswerBox {
              public ApfCapabilities value = sNoApfCapabilities;
          }
          synchronized (sLock) {
              try {
                  IWifiStaIface iface = getStaIface(ifaceName);
                  if (iface == null) return sNoApfCapabilities;
                  AnswerBox box = new AnswerBox();
                  iface.getApfPacketFilterCapabilities((status, capabilities) -> {
                      if (!ok(status)) return;
                      box.value = new ApfCapabilities(
                          /* apfVersionSupported */   capabilities.version,
                          /* maximumApfProgramSize */ capabilities.maxLength,
                          /* apfPacketFormat */       android.system.OsConstants.ARPHRD_ETHER);
                  });
                  return box.value;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return sNoApfCapabilities;
              }
          }
      }
  
      private static final ApfCapabilities sNoApfCapabilities = new ApfCapabilities(0, 0, 0);
  
      /**
       * Installs an APF program on this iface, replacing any existing program.
       *
       * @param ifaceName Name of the interface.
       * @param filter is the android packet filter program
       * @return true for success
       */
      public boolean installPacketFilter(@NonNull String ifaceName, byte[] filter) {
          int cmdId = 0; // We only aspire to support one program at a time
          if (filter == null) return boolResult(false);
          // Copy the program before taking the lock.
          ArrayList<Byte> program = NativeUtil.byteArrayToArrayList(filter);
          enter("filter length %").c(filter.length).flush();
          synchronized (sLock) {
              try {
                  IWifiStaIface iface = getStaIface(ifaceName);
                  if (iface == null) return boolResult(false);
                  WifiStatus status = iface.installApfPacketFilter(cmdId, program);
                  if (!ok(status)) return false;
                  return true;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Reads the APF program and data buffer on this iface.
       *
       * @param ifaceName Name of the interface
       * @return the buffer returned by the driver, or null in case of an error
       */
      public byte[] readPacketFilter(@NonNull String ifaceName) {
          class AnswerBox {
              public byte[] data = null;
          }
          AnswerBox answer = new AnswerBox();
          enter("").flush();
          // TODO: Must also take the wakelock here to prevent going to sleep with APF disabled.
          synchronized (sLock) {
              try {
                  android.hardware.wifi.V1_2.IWifiStaIface ifaceV12 =
                          getWifiStaIfaceForV1_2Mockable(ifaceName);
                  if (ifaceV12 == null) return byteArrayResult(null);
                  ifaceV12.readApfPacketFilterData((status, dataByteArray) -> {
                      if (!ok(status)) return;
                      answer.data = NativeUtil.byteArrayFromArrayList(dataByteArray);
                  });
                  return byteArrayResult(answer.data);
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return byteArrayResult(null);
              }
          }
      }
  
      /**
       * Set country code for this AP iface.
       *
       * @param ifaceName Name of the interface.
       * @param countryCode - two-letter country code (as ISO 3166)
       * @return true for success
       */
      public boolean setCountryCodeHal(@NonNull String ifaceName, String countryCode) {
          if (countryCode == null) return boolResult(false);
          if (countryCode.length() != 2) return boolResult(false);
          byte[] code;
          try {
              code = NativeUtil.stringToByteArray(countryCode);
          } catch (IllegalArgumentException e) {
              return boolResult(false);
          }
          synchronized (sLock) {
              try {
                  IWifiApIface iface = getApIface(ifaceName);
                  if (iface == null) return boolResult(false);
                  WifiStatus status = iface.setCountryCode(code);
                  if (!ok(status)) return false;
                  return true;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      private WifiNative.WifiLoggerEventHandler mLogEventHandler = null;
  
      /**
       * Registers the logger callback and enables alerts.
       * Ring buffer data collection is only triggered when |startLoggingRingBuffer| is invoked.
       */
      public boolean setLoggingEventHandler(WifiNative.WifiLoggerEventHandler handler) {
          if (handler == null) return boolResult(false);
          synchronized (sLock) {
              if (mIWifiChip == null) return boolResult(false);
              if (mLogEventHandler != null) return boolResult(false);
              try {
                  WifiStatus status = mIWifiChip.enableDebugErrorAlerts(true);
                  if (!ok(status)) return false;
                  mLogEventHandler = handler;
                  return true;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Stops all logging and resets the logger callback.
       * This stops both the alerts and ring buffer data collection.
       * Existing log handler is cleared.
       */
      public boolean resetLogHandler() {
          synchronized (sLock) {
              mLogEventHandler = null;
              if (mIWifiChip == null) return boolResult(false);
              try {
                  WifiStatus status = mIWifiChip.enableDebugErrorAlerts(false);
                  if (!ok(status)) return false;
                  status = mIWifiChip.stopLoggingToDebugRingBuffer();
                  if (!ok(status)) return false;
                  return true;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Control debug data collection
       *
       * @param verboseLevel       0 to 3, inclusive. 0 stops logging.
       * @param flags              Ignored.
       * @param maxIntervalInSec   Maximum interval between reports; ignore if 0.
       * @param minDataSizeInBytes Minimum data size in buffer for report; ignore if 0.
       * @param ringName           Name of the ring for which data collection is to start.
       * @return true for success
       */
      public boolean startLoggingRingBuffer(int verboseLevel, int flags, int maxIntervalInSec,
                                            int minDataSizeInBytes, String ringName) {
          enter("verboseLevel=%, flags=%, maxIntervalInSec=%, minDataSizeInBytes=%, ringName=%")
                  .c(verboseLevel).c(flags).c(maxIntervalInSec).c(minDataSizeInBytes).c(ringName)
                  .flush();
          synchronized (sLock) {
              if (mIWifiChip == null) return boolResult(false);
              try {
                  // note - flags are not used
                  WifiStatus status = mIWifiChip.startLoggingToDebugRingBuffer(
                          ringName,
                          verboseLevel,
                          maxIntervalInSec,
                          minDataSizeInBytes
                  );
                  return ok(status);
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Pointlessly fail
       *
       * @return -1
       */
      public int getSupportedLoggerFeatureSet() {
          return -1;
      }
  
      private String mDriverDescription; // Cached value filled by requestChipDebugInfo()
  
      /**
       * Vendor-provided wifi driver version string
       */
      public String getDriverVersion() {
          synchronized (sLock) {
              if (mDriverDescription == null) requestChipDebugInfo();
              return mDriverDescription;
          }
      }
  
      private String mFirmwareDescription; // Cached value filled by requestChipDebugInfo()
  
      /**
       * Vendor-provided wifi firmware version string
       */
      public String getFirmwareVersion() {
          synchronized (sLock) {
              if (mFirmwareDescription == null) requestChipDebugInfo();
              return mFirmwareDescription;
          }
      }
  
      /**
       * Refreshes our idea of the driver and firmware versions
       */
      private void requestChipDebugInfo() {
          mDriverDescription = null;
          mFirmwareDescription = null;
          try {
              if (mIWifiChip == null) return;
              mIWifiChip.requestChipDebugInfo((status, chipDebugInfo) -> {
                  if (!ok(status)) return;
                  mDriverDescription = chipDebugInfo.driverDescription;
                  mFirmwareDescription = chipDebugInfo.firmwareDescription;
              });
          } catch (RemoteException e) {
              handleRemoteException(e);
              return;
          }
          mLog.info("Driver: % Firmware: %")
                  .c(mDriverDescription)
                  .c(mFirmwareDescription)
                  .flush();
      }
  
      /**
       * Creates RingBufferStatus from the Hal version
       */
      private static WifiNative.RingBufferStatus ringBufferStatus(WifiDebugRingBufferStatus h) {
          WifiNative.RingBufferStatus ans = new WifiNative.RingBufferStatus();
          ans.name = h.ringName;
          ans.flag = frameworkRingBufferFlagsFromHal(h.flags);
          ans.ringBufferId = h.ringId;
          ans.ringBufferByteSize = h.sizeInBytes;
          ans.verboseLevel = h.verboseLevel;
          // Remaining fields are unavailable
          //  writtenBytes;
          //  readBytes;
          //  writtenRecords;
          return ans;
      }
  
      /**
       * Translates a hal wifiDebugRingBufferFlag to the WifiNative version
       */
      private static int frameworkRingBufferFlagsFromHal(int wifiDebugRingBufferFlag) {
          BitMask checkoff = new BitMask(wifiDebugRingBufferFlag);
          int flags = 0;
          if (checkoff.testAndClear(WifiDebugRingBufferFlags.HAS_BINARY_ENTRIES)) {
              flags |= WifiNative.RingBufferStatus.HAS_BINARY_ENTRIES;
          }
          if (checkoff.testAndClear(WifiDebugRingBufferFlags.HAS_ASCII_ENTRIES)) {
              flags |= WifiNative.RingBufferStatus.HAS_ASCII_ENTRIES;
          }
          if (checkoff.testAndClear(WifiDebugRingBufferFlags.HAS_PER_PACKET_ENTRIES)) {
              flags |= WifiNative.RingBufferStatus.HAS_PER_PACKET_ENTRIES;
          }
          if (checkoff.value != 0) {
              throw new IllegalArgumentException("Unknown WifiDebugRingBufferFlag " + checkoff.value);
          }
          return flags;
      }
  
      /**
       * Creates array of RingBufferStatus from the Hal version
       */
      private static WifiNative.RingBufferStatus[] makeRingBufferStatusArray(
              ArrayList<WifiDebugRingBufferStatus> ringBuffers) {
          WifiNative.RingBufferStatus[] ans = new WifiNative.RingBufferStatus[ringBuffers.size()];
          int i = 0;
          for (WifiDebugRingBufferStatus b : ringBuffers) {
              ans[i++] = ringBufferStatus(b);
          }
          return ans;
      }
  
      /**
       * API to get the status of all ring buffers supported by driver
       */
      public WifiNative.RingBufferStatus[] getRingBufferStatus() {
          class AnswerBox {
              public WifiNative.RingBufferStatus[] value = null;
          }
          AnswerBox ans = new AnswerBox();
          synchronized (sLock) {
              if (mIWifiChip == null) return null;
              try {
                  mIWifiChip.getDebugRingBuffersStatus((status, ringBuffers) -> {
                      if (!ok(status)) return;
                      ans.value = makeRingBufferStatusArray(ringBuffers);
                  });
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return null;
              }
          }
          return ans.value;
      }
  
      /**
       * Indicates to driver that all the data has to be uploaded urgently
       */
      public boolean getRingBufferData(String ringName) {
          enter("ringName %").c(ringName).flush();
          synchronized (sLock) {
              if (mIWifiChip == null) return boolResult(false);
              try {
                  WifiStatus status = mIWifiChip.forceDumpToDebugRingBuffer(ringName);
                  return ok(status);
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * request hal to flush ring buffers to files
       */
      public boolean flushRingBufferData() {
          synchronized (sLock) {
              if (mIWifiChip == null) return boolResult(false);
              android.hardware.wifi.V1_3.IWifiChip iWifiChipV13 = getWifiChipForV1_3Mockable();
              if (iWifiChipV13 != null) {
                  try {
                      WifiStatus status = iWifiChipV13.flushRingBufferToFile();
                      return ok(status);
                  } catch (RemoteException e) {
                      handleRemoteException(e);
                      return false;
                  }
              }
              return false;
          }
      }
  
      /**
       * Request vendor debug info from the firmware
       */
      public byte[] getFwMemoryDump() {
          class AnswerBox {
              public byte[] value;
          }
          AnswerBox ans = new AnswerBox();
          synchronized (sLock) {
              if (mIWifiChip == null) return (null);
              try {
                  mIWifiChip.requestFirmwareDebugDump((status, blob) -> {
                      if (!ok(status)) return;
                      ans.value = NativeUtil.byteArrayFromArrayList(blob);
                  });
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return null;
              }
          }
          return ans.value;
      }
  
      /**
       * Request vendor debug info from the driver
       */
      public byte[] getDriverStateDump() {
          class AnswerBox {
              public byte[] value;
          }
          AnswerBox ans = new AnswerBox();
          synchronized (sLock) {
              if (mIWifiChip == null) return (null);
              try {
                  mIWifiChip.requestDriverDebugDump((status, blob) -> {
                      if (!ok(status)) return;
                      ans.value = NativeUtil.byteArrayFromArrayList(blob);
                  });
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return null;
              }
          }
          return ans.value;
      }
  
      /**
       * Start packet fate monitoring
       * <p>
       * Once started, monitoring remains active until HAL is unloaded.
       *
       * @param ifaceName Name of the interface.
       * @return true for success
       */
      public boolean startPktFateMonitoring(@NonNull String ifaceName) {
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return boolResult(false);
              try {
                  WifiStatus status = iface.startDebugPacketFateMonitoring();
                  return ok(status);
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      private byte halToFrameworkPktFateFrameType(int type) {
          switch (type) {
              case WifiDebugPacketFateFrameType.UNKNOWN:
                  return WifiLoggerHal.FRAME_TYPE_UNKNOWN;
              case WifiDebugPacketFateFrameType.ETHERNET_II:
                  return WifiLoggerHal.FRAME_TYPE_ETHERNET_II;
              case WifiDebugPacketFateFrameType.MGMT_80211:
                  return WifiLoggerHal.FRAME_TYPE_80211_MGMT;
              default:
                  throw new IllegalArgumentException("bad " + type);
          }
      }
  
      private byte halToFrameworkRxPktFate(int type) {
          switch (type) {
              case WifiDebugRxPacketFate.SUCCESS:
                  return WifiLoggerHal.RX_PKT_FATE_SUCCESS;
              case WifiDebugRxPacketFate.FW_QUEUED:
                  return WifiLoggerHal.RX_PKT_FATE_FW_QUEUED;
              case WifiDebugRxPacketFate.FW_DROP_FILTER:
                  return WifiLoggerHal.RX_PKT_FATE_FW_DROP_FILTER;
              case WifiDebugRxPacketFate.FW_DROP_INVALID:
                  return WifiLoggerHal.RX_PKT_FATE_FW_DROP_INVALID;
              case WifiDebugRxPacketFate.FW_DROP_NOBUFS:
                  return WifiLoggerHal.RX_PKT_FATE_FW_DROP_NOBUFS;
              case WifiDebugRxPacketFate.FW_DROP_OTHER:
                  return WifiLoggerHal.RX_PKT_FATE_FW_DROP_OTHER;
              case WifiDebugRxPacketFate.DRV_QUEUED:
                  return WifiLoggerHal.RX_PKT_FATE_DRV_QUEUED;
              case WifiDebugRxPacketFate.DRV_DROP_FILTER:
                  return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_FILTER;
              case WifiDebugRxPacketFate.DRV_DROP_INVALID:
                  return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_INVALID;
              case WifiDebugRxPacketFate.DRV_DROP_NOBUFS:
                  return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_NOBUFS;
              case WifiDebugRxPacketFate.DRV_DROP_OTHER:
                  return WifiLoggerHal.RX_PKT_FATE_DRV_DROP_OTHER;
              default:
                  throw new IllegalArgumentException("bad " + type);
          }
      }
  
      private byte halToFrameworkTxPktFate(int type) {
          switch (type) {
              case WifiDebugTxPacketFate.ACKED:
                  return WifiLoggerHal.TX_PKT_FATE_ACKED;
              case WifiDebugTxPacketFate.SENT:
                  return WifiLoggerHal.TX_PKT_FATE_SENT;
              case WifiDebugTxPacketFate.FW_QUEUED:
                  return WifiLoggerHal.TX_PKT_FATE_FW_QUEUED;
              case WifiDebugTxPacketFate.FW_DROP_INVALID:
                  return WifiLoggerHal.TX_PKT_FATE_FW_DROP_INVALID;
              case WifiDebugTxPacketFate.FW_DROP_NOBUFS:
                  return WifiLoggerHal.TX_PKT_FATE_FW_DROP_NOBUFS;
              case WifiDebugTxPacketFate.FW_DROP_OTHER:
                  return WifiLoggerHal.TX_PKT_FATE_FW_DROP_OTHER;
              case WifiDebugTxPacketFate.DRV_QUEUED:
                  return WifiLoggerHal.TX_PKT_FATE_DRV_QUEUED;
              case WifiDebugTxPacketFate.DRV_DROP_INVALID:
                  return WifiLoggerHal.TX_PKT_FATE_DRV_DROP_INVALID;
              case WifiDebugTxPacketFate.DRV_DROP_NOBUFS:
                  return WifiLoggerHal.TX_PKT_FATE_DRV_DROP_NOBUFS;
              case WifiDebugTxPacketFate.DRV_DROP_OTHER:
                  return WifiLoggerHal.TX_PKT_FATE_DRV_DROP_OTHER;
              default:
                  throw new IllegalArgumentException("bad " + type);
          }
      }
  
      /**
       * Retrieve fates of outbound packets
       * <p>
       * Reports the outbound frames for the most recent association (space allowing).
       *
       * @param ifaceName Name of the interface.
       * @param reportBufs
       * @return true for success
       */
      public boolean getTxPktFates(@NonNull String ifaceName, WifiNative.TxFateReport[] reportBufs) {
          if (ArrayUtils.isEmpty(reportBufs)) return boolResult(false);
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return boolResult(false);
              try {
                  MutableBoolean ok = new MutableBoolean(false);
                  iface.getDebugTxPacketFates((status, fates) -> {
                              if (!ok(status)) return;
                              int i = 0;
                              for (WifiDebugTxPacketFateReport fate : fates) {
                                  if (i >= reportBufs.length) break;
                                  byte code = halToFrameworkTxPktFate(fate.fate);
                                  long us = fate.frameInfo.driverTimestampUsec;
                                  byte type =
                                          halToFrameworkPktFateFrameType(fate.frameInfo.frameType);
                                  byte[] frame =
                                          NativeUtil.byteArrayFromArrayList(
                                                  fate.frameInfo.frameContent);
                                  reportBufs[i++] =
                                          new WifiNative.TxFateReport(code, us, type, frame);
                              }
                              ok.value = true;
                          }
                  );
                  return ok.value;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Retrieve fates of inbound packets
       * <p>
       * Reports the inbound frames for the most recent association (space allowing).
       *
       * @param ifaceName Name of the interface.
       * @param reportBufs
       * @return true for success
       */
      public boolean getRxPktFates(@NonNull String ifaceName, WifiNative.RxFateReport[] reportBufs) {
          if (ArrayUtils.isEmpty(reportBufs)) return boolResult(false);
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return boolResult(false);
              try {
                  MutableBoolean ok = new MutableBoolean(false);
                  iface.getDebugRxPacketFates((status, fates) -> {
                              if (!ok(status)) return;
                              int i = 0;
                              for (WifiDebugRxPacketFateReport fate : fates) {
                                  if (i >= reportBufs.length) break;
                                  byte code = halToFrameworkRxPktFate(fate.fate);
                                  long us = fate.frameInfo.driverTimestampUsec;
                                  byte type =
                                          halToFrameworkPktFateFrameType(fate.frameInfo.frameType);
                                  byte[] frame =
                                          NativeUtil.byteArrayFromArrayList(
                                                  fate.frameInfo.frameContent);
                                  reportBufs[i++] =
                                          new WifiNative.RxFateReport(code, us, type, frame);
                              }
                              ok.value = true;
                          }
                  );
                  return ok.value;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Start sending the specified keep alive packets periodically.
       *
       * @param ifaceName Name of the interface.
       * @param slot
       * @param srcMac
       * @param dstMac
       * @param keepAlivePacket
       * @param protocol
       * @param periodInMs
       * @return 0 for success, -1 for error
       */
      public int startSendingOffloadedPacket(
              @NonNull String ifaceName, int slot, byte[] srcMac, byte[] dstMac,
              byte[] packet, int protocol, int periodInMs) {
          enter("slot=% periodInMs=%").c(slot).c(periodInMs).flush();
  
          ArrayList<Byte> data = NativeUtil.byteArrayToArrayList(packet);
  
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return -1;
              try {
                  WifiStatus status = iface.startSendingKeepAlivePackets(
                          slot,
                          data,
                          (short) protocol,
                          srcMac,
                          dstMac,
                          periodInMs);
                  if (!ok(status)) return -1;
                  return 0;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return -1;
              }
          }
      }
  
      /**
       * Stop sending the specified keep alive packets.
       *
       * @param ifaceName Name of the interface.
       * @param slot id - same as startSendingOffloadedPacket call.
       * @return 0 for success, -1 for error
       */
      public int stopSendingOffloadedPacket(@NonNull String ifaceName, int slot) {
          enter("slot=%").c(slot).flush();
  
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return -1;
              try {
                  WifiStatus status = iface.stopSendingKeepAlivePackets(slot);
                  if (!ok(status)) return -1;
                  return 0;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return -1;
              }
          }
      }
  
      /**
       * A fixed cmdId for our RssiMonitoring (we only do one at a time)
       */
      @VisibleForTesting
      static final int sRssiMonCmdId = 7551;
  
      /**
       * Our client's handler
       */
      private WifiNative.WifiRssiEventHandler mWifiRssiEventHandler;
  
      /**
       * Start RSSI monitoring on the currently connected access point.
       *
       * @param ifaceName        Name of the interface.
       * @param maxRssi          Maximum RSSI threshold.
       * @param minRssi          Minimum RSSI threshold.
       * @param rssiEventHandler Called when RSSI goes above maxRssi or below minRssi
       * @return 0 for success, -1 for failure
       */
      public int startRssiMonitoring(@NonNull String ifaceName, byte maxRssi, byte minRssi,
                                     WifiNative.WifiRssiEventHandler rssiEventHandler) {
          enter("maxRssi=% minRssi=%").c(maxRssi).c(minRssi).flush();
          if (maxRssi <= minRssi) return -1;
          if (rssiEventHandler == null) return -1;
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return -1;
              try {
                  iface.stopRssiMonitoring(sRssiMonCmdId);
                  WifiStatus status;
                  status = iface.startRssiMonitoring(sRssiMonCmdId, maxRssi, minRssi);
                  if (!ok(status)) return -1;
                  mWifiRssiEventHandler = rssiEventHandler;
                  return 0;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return -1;
              }
          }
      }
  
      /**
       * Stop RSSI monitoring
       *
       * @param ifaceName Name of the interface.
       * @return 0 for success, -1 for failure
       */
      public int stopRssiMonitoring(@NonNull String ifaceName) {
          synchronized (sLock) {
              mWifiRssiEventHandler = null;
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return -1;
              try {
                  WifiStatus status = iface.stopRssiMonitoring(sRssiMonCmdId);
                  if (!ok(status)) return -1;
                  return 0;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return -1;
              }
          }
      }
  
      //TODO - belongs in NativeUtil
      private static int[] intsFromArrayList(ArrayList<Integer> a) {
          if (a == null) return null;
          int[] b = new int[a.size()];
          int i = 0;
          for (Integer e : a) b[i++] = e;
          return b;
      }
  
      /**
       * Translates from Hal version of wake reason stats to the framework version of same
       *
       * @param h - Hal version of wake reason stats
       * @return framework version of same
       */
      private static WlanWakeReasonAndCounts halToFrameworkWakeReasons(
              WifiDebugHostWakeReasonStats h) {
          if (h == null) return null;
          WlanWakeReasonAndCounts ans = new WlanWakeReasonAndCounts();
          ans.totalCmdEventWake = h.totalCmdEventWakeCnt;
          ans.totalDriverFwLocalWake = h.totalDriverFwLocalWakeCnt;
          ans.totalRxDataWake = h.totalRxPacketWakeCnt;
          ans.rxUnicast = h.rxPktWakeDetails.rxUnicastCnt;
          ans.rxMulticast = h.rxPktWakeDetails.rxMulticastCnt;
          ans.rxBroadcast = h.rxPktWakeDetails.rxBroadcastCnt;
          ans.icmp = h.rxIcmpPkWakeDetails.icmpPkt;
          ans.icmp6 = h.rxIcmpPkWakeDetails.icmp6Pkt;
          ans.icmp6Ra = h.rxIcmpPkWakeDetails.icmp6Ra;
          ans.icmp6Na = h.rxIcmpPkWakeDetails.icmp6Na;
          ans.icmp6Ns = h.rxIcmpPkWakeDetails.icmp6Ns;
          ans.ipv4RxMulticast = h.rxMulticastPkWakeDetails.ipv4RxMulticastAddrCnt;
          ans.ipv6Multicast = h.rxMulticastPkWakeDetails.ipv6RxMulticastAddrCnt;
          ans.otherRxMulticast = h.rxMulticastPkWakeDetails.otherRxMulticastAddrCnt;
          ans.cmdEventWakeCntArray = intsFromArrayList(h.cmdEventWakeCntPerType);
          ans.driverFWLocalWakeCntArray = intsFromArrayList(h.driverFwLocalWakeCntPerType);
          return ans;
      }
  
      /**
       * Fetch the host wakeup reasons stats from wlan driver.
       *
       * @return the |WlanWakeReasonAndCounts| from the wlan driver, or null on failure.
       */
      public WlanWakeReasonAndCounts getWlanWakeReasonCount() {
          class AnswerBox {
              public WifiDebugHostWakeReasonStats value = null;
          }
          AnswerBox ans = new AnswerBox();
          synchronized (sLock) {
              if (mIWifiChip == null) return null;
              try {
                  mIWifiChip.getDebugHostWakeReasonStats((status, stats) -> {
                      if (ok(status)) {
                          ans.value = stats;
                      }
                  });
                  return halToFrameworkWakeReasons(ans.value);
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return null;
              }
          }
      }
  
      /**
       * Enable/Disable Neighbour discovery offload functionality in the firmware.
       *
       * @param ifaceName Name of the interface.
       * @param enabled true to enable, false to disable.
       * @return true for success, false for failure
       */
      public boolean configureNeighborDiscoveryOffload(@NonNull String ifaceName, boolean enabled) {
          enter("enabled=%").c(enabled).flush();
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return boolResult(false);
              try {
                  WifiStatus status = iface.enableNdOffload(enabled);
                  if (!ok(status)) return false;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
          return true;
      }
  
      // Firmware roaming control.
  
      /**
       * Query the firmware roaming capabilities.
       *
       * @param ifaceName Name of the interface.
       * @param capabilities object to be filled in
       * @return true for success; false for failure
       */
      public boolean getRoamingCapabilities(@NonNull String ifaceName,
                                            WifiNative.RoamingCapabilities capabilities) {
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return boolResult(false);
              try {
                  MutableBoolean ok = new MutableBoolean(false);
                  WifiNative.RoamingCapabilities out = capabilities;
                  iface.getRoamingCapabilities((status, cap) -> {
                      if (!ok(status)) return;
                      out.maxBlacklistSize = cap.maxBlacklistSize;
                      out.maxWhitelistSize = cap.maxWhitelistSize;
                      ok.value = true;
                  });
                  return ok.value;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              }
          }
      }
  
      /**
       * Enable/disable firmware roaming.
       *
       * @param ifaceName Name of the interface.
       * @param state the intended roaming state
       * @return SET_FIRMWARE_ROAMING_SUCCESS, SET_FIRMWARE_ROAMING_FAILURE,
       *         or SET_FIRMWARE_ROAMING_BUSY
       */
      public int enableFirmwareRoaming(@NonNull String ifaceName, int state) {
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return WifiNative.SET_FIRMWARE_ROAMING_FAILURE;
              try {
                  byte val;
                  switch (state) {
                      case WifiNative.DISABLE_FIRMWARE_ROAMING:
                          val = StaRoamingState.DISABLED;
                          break;
                      case WifiNative.ENABLE_FIRMWARE_ROAMING:
                          val = StaRoamingState.ENABLED;
                          break;
                      default:
                          mLog.err("enableFirmwareRoaming invalid argument %").c(state).flush();
                          return WifiNative.SET_FIRMWARE_ROAMING_FAILURE;
                  }
                  WifiStatus status = iface.setRoamingState(val);
                  if (ok(status)) {
                      return WifiNative.SET_FIRMWARE_ROAMING_SUCCESS;
                  } else if (status.code == WifiStatusCode.ERROR_BUSY) {
                      return WifiNative.SET_FIRMWARE_ROAMING_BUSY;
                  } else {
                      return WifiNative.SET_FIRMWARE_ROAMING_FAILURE;
                  }
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return WifiNative.SET_FIRMWARE_ROAMING_FAILURE;
              }
          }
      }
  
      /**
       * Set firmware roaming configurations.
       *
       * @param ifaceName Name of the interface.
       * @param config new roaming configuration object
       * @return true for success; false for failure
       */
      public boolean configureRoaming(@NonNull String ifaceName, WifiNative.RoamingConfig config) {
          synchronized (sLock) {
              IWifiStaIface iface = getStaIface(ifaceName);
              if (iface == null) return boolResult(false);
              try {
                  StaRoamingConfig roamingConfig = new StaRoamingConfig();
  
                  // parse the blacklist BSSIDs if any
                  if (config.blacklistBssids != null) {
                      for (String bssid : config.blacklistBssids) {
                          byte[] mac = NativeUtil.macAddressToByteArray(bssid);
                          roamingConfig.bssidBlacklist.add(mac);
                      }
                  }
  
                  // parse the whitelist SSIDs if any
                  if (config.whitelistSsids != null) {
                      for (String ssidStr : config.whitelistSsids) {
                          byte[] ssid = NativeUtil.byteArrayFromArrayList(
                                  NativeUtil.decodeSsid(ssidStr));
                          roamingConfig.ssidWhitelist.add(ssid);
                      }
                  }
  
                  WifiStatus status = iface.configureRoaming(roamingConfig);
                  if (!ok(status)) return false;
              } catch (RemoteException e) {
                  handleRemoteException(e);
                  return false;
              } catch (IllegalArgumentException e) {
                  mLog.err("Illegal argument for roaming configuration").c(e.toString()).flush();
                  return false;
              }
              return true;
          }
      }
  
      /**
       * Method to mock out the V1_1 IWifiChip retrieval in unit tests.
       *
       * @return 1.1 IWifiChip object if the device is running the 1.1 wifi hal service, null
       * otherwise.
       */
      protected android.hardware.wifi.V1_1.IWifiChip getWifiChipForV1_1Mockable() {
          if (mIWifiChip == null) return null;
          return android.hardware.wifi.V1_1.IWifiChip.castFrom(mIWifiChip);
      }
  
      /**
       * Method to mock out the V1_2 IWifiChip retrieval in unit tests.
       *
       * @return 1.2 IWifiChip object if the device is running the 1.2 wifi hal service, null
       * otherwise.
       */
      protected android.hardware.wifi.V1_2.IWifiChip getWifiChipForV1_2Mockable() {
          if (mIWifiChip == null) return null;
          return android.hardware.wifi.V1_2.IWifiChip.castFrom(mIWifiChip);
      }
  
      /**
       * Method to mock out the V1_3 IWifiChip retrieval in unit tests.
       *
       * @return 1.3 IWifiChip object if the device is running the 1.3 wifi hal service, null
       * otherwise.
       */
      protected android.hardware.wifi.V1_3.IWifiChip getWifiChipForV1_3Mockable() {
          if (mIWifiChip == null) return null;
          return android.hardware.wifi.V1_3.IWifiChip.castFrom(mIWifiChip);
      }
  
      /**
       * Method to mock out the V1_2 IWifiStaIface retrieval in unit tests.
       *
       * @param ifaceName Name of the interface
       * @return 1.2 IWifiStaIface object if the device is running the 1.2 wifi hal service, null
       * otherwise.
       */
      protected android.hardware.wifi.V1_2.IWifiStaIface getWifiStaIfaceForV1_2Mockable(
              @NonNull String ifaceName) {
          IWifiStaIface iface = getStaIface(ifaceName);
          if (iface == null) return null;
          return android.hardware.wifi.V1_2.IWifiStaIface.castFrom(iface);
      }
  
      /**
       * Method to mock out the V1_3 IWifiStaIface retrieval in unit tests.
       *
       * @param ifaceName Name of the interface
       * @return 1.3 IWifiStaIface object if the device is running the 1.3 wifi hal service, null
       * otherwise.
       */
      protected android.hardware.wifi.V1_3.IWifiStaIface getWifiStaIfaceForV1_3Mockable(
              @NonNull String ifaceName) {
          IWifiStaIface iface = getStaIface(ifaceName);
          if (iface == null) return null;
          return android.hardware.wifi.V1_3.IWifiStaIface.castFrom(iface);
      }
  
      /**
       * sarPowerBackoffRequired_1_1()
       * This method checks if we need to backoff wifi Tx power due to SAR requirements.
       * It handles the case when the device is running the V1_1 version of WifiChip HAL
       * In that HAL version, it is required to perform wifi Tx power backoff only if
       * a voice call is ongoing.
       */
      private boolean sarPowerBackoffRequired_1_1(SarInfo sarInfo) {
          /* As long as no voice call is active (in case voice call is supported),
           * no backoff is needed */
          if (sarInfo.sarVoiceCallSupported) {
              return (sarInfo.isVoiceCall || sarInfo.isEarPieceActive);
          } else {
              return false;
          }
      }
  
      /**
       * frameworkToHalTxPowerScenario_1_1()
       * This method maps the information inside the SarInfo instance into a SAR scenario
       * when device is running the V1_1 version of WifiChip HAL.
       * In this HAL version, only one scenario is defined which is for VOICE_CALL (if voice call is
       * supported).
       * Otherwise, an exception is thrown.
       */
      private int frameworkToHalTxPowerScenario_1_1(SarInfo sarInfo) {
          if (sarInfo.sarVoiceCallSupported && (sarInfo.isVoiceCall || sarInfo.isEarPieceActive)) {
              return android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL;
          } else {
              throw new IllegalArgumentException("bad scenario: voice call not active/supported");
          }
      }
  
      /**
       * sarPowerBackoffRequired_1_2()
       * This method checks if we need to backoff wifi Tx power due to SAR requirements.
       * It handles the case when the device is running the V1_2 version of WifiChip HAL
       * In that HAL version, behavior depends on if SAR sensor input is considered in this device.
       * If it is, then whenever the device is near the user body/hand/head, back-off is required.
       * Otherwise, we should revert to the V1_1 HAL behavior which is only to perform backoff when
       * a voice call is ongoing.
       */
      private boolean sarPowerBackoffRequired_1_2(SarInfo sarInfo) {
          /* If SAR sensor is supported, output only dependent on device proximity */
          if (sarInfo.sarSensorSupported) {
              return (sarInfo.sensorState != SarInfo.SAR_SENSOR_FREE_SPACE);
          }
          if (sarInfo.sarSapSupported && sarInfo.isWifiSapEnabled) {
              return true;
          }
          if (sarInfo.sarVoiceCallSupported && (sarInfo.isVoiceCall || sarInfo.isEarPieceActive)) {
              return true;
          }
          return false;
      }
  
      /**
       * frameworkToHalTxPowerScenario_1_2()
       * This method maps the information inside the SarInfo instance into a SAR scenario
       * when device is running the V1_2 version of WifiChip HAL.
       * In this HAL version, behavior depends on if SAR sensor input is considered in this device.
       * If it is, then based on regulatory compliance requirements,
       *   - There is no need to treat NEAR_HAND different from NEAR_BODY, both can be considered
       *     near the user body.
       *   - Running in softAP mode can be treated the same way as running a voice call from tx power
       *     backoff perspective.
       * If SAR sensor input is not supported in this device, but SoftAP is,
       * we make these assumptions:
       *   - All voice calls are treated as if device is near the head.
       *   - SoftAP scenario is treated as if device is near the body.
       * In case neither SAR sensor, nor SoftAP is supported, then we should revert to the V1_1 HAL
       * behavior, and the only valid scenario would be when a voice call is ongoing.
       */
      private int frameworkToHalTxPowerScenario_1_2(SarInfo sarInfo) {
          if (sarInfo.sarSensorSupported) {
              switch(sarInfo.sensorState) {
                  case SarInfo.SAR_SENSOR_NEAR_BODY:
                  case SarInfo.SAR_SENSOR_NEAR_HAND:
                      if (sarInfo.isVoiceCall || sarInfo.isWifiSapEnabled) {
                          return android.hardware.wifi.V1_2.IWifiChip
                                  .TxPowerScenario.ON_BODY_CELL_ON;
                      } else {
                          return android.hardware.wifi.V1_2.IWifiChip
                                  .TxPowerScenario.ON_BODY_CELL_OFF;
                      }
  
                  case SarInfo.SAR_SENSOR_NEAR_HEAD:
                      if (sarInfo.isVoiceCall || sarInfo.isWifiSapEnabled) {
                          return android.hardware.wifi.V1_2.IWifiChip
                                  .TxPowerScenario.ON_HEAD_CELL_ON;
                      } else {
                          return android.hardware.wifi.V1_2.IWifiChip
                                  .TxPowerScenario.ON_HEAD_CELL_OFF;
                      }
  
                  default:
                      throw new IllegalArgumentException("bad scenario: Invalid sensor state");
              }
          } else if (sarInfo.sarSapSupported && sarInfo.sarVoiceCallSupported) {
              if (sarInfo.isVoiceCall || sarInfo.isEarPieceActive) {
                  return android.hardware.wifi.V1_2.IWifiChip
                          .TxPowerScenario.ON_HEAD_CELL_ON;
              } else if (sarInfo.isWifiSapEnabled) {
                  return android.hardware.wifi.V1_2.IWifiChip
                          .TxPowerScenario.ON_BODY_CELL_ON;
              } else {
                  throw new IllegalArgumentException("bad scenario: no voice call/softAP active");
              }
          } else if (sarInfo.sarVoiceCallSupported) {
              /* SAR Sensors and SoftAP not supported, act like V1_1 */
              if (sarInfo.isVoiceCall || sarInfo.isEarPieceActive) {
                  return android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL;
              } else {
                  throw new IllegalArgumentException("bad scenario: voice call not active");
              }
          } else {
              throw new IllegalArgumentException("Invalid case: voice call not supported");
          }
      }
  
      /**
       * Select one of the pre-configured TX power level scenarios or reset it back to normal.
       * Primarily used for meeting SAR requirements during voice calls.
       *
       * Note: If it was found out that the scenario to be reported is the same as last reported one,
       *       then exit with success.
       *       This is to handle the case when some HAL versions deal with different inputs equally,
       *       in that case, we should not call the hal unless there is a change in scenario.
       * Note: It is assumed that this method is only called if SAR is enabled. The logic of whether
       *       to call it or not resides in SarManager class.
       * Note: This method is called whether SAR sensor is supported or not. The passed SarInfo object
       *       contains a flag to indicate the SAR sensor support.
       *
       * @param sarInfo The collection of inputs to select the SAR scenario.
       * @return true for success; false for failure or if the HAL version does not support this API.
       */
      public boolean selectTxPowerScenario(SarInfo sarInfo) {
          synchronized (sLock) {
              // First attempt to get a V_1_2 instance of the Wifi HAL.
              android.hardware.wifi.V1_2.IWifiChip iWifiChipV12 = getWifiChipForV1_2Mockable();
              if (iWifiChipV12 != null) {
                  return selectTxPowerScenario_1_2(iWifiChipV12, sarInfo);
              }
  
              // Now attempt to get a V_1_1 instance of the Wifi HAL.
              android.hardware.wifi.V1_1.IWifiChip iWifiChipV11 = getWifiChipForV1_1Mockable();
              if (iWifiChipV11 != null) {
                  return selectTxPowerScenario_1_1(iWifiChipV11, sarInfo);
              }
  
              // HAL version does not support SAR
              return false;
          }
      }
  
      private boolean selectTxPowerScenario_1_1(
              android.hardware.wifi.V1_1.IWifiChip iWifiChip, SarInfo sarInfo) {
          WifiStatus status;
          try {
              if (sarPowerBackoffRequired_1_1(sarInfo)) {
                  // Power backoff is needed, so calculate the required scenario,
                  // and attempt to set it.
                  int halScenario = frameworkToHalTxPowerScenario_1_1(sarInfo);
                  if (sarInfo.setSarScenarioNeeded(halScenario)) {
                      status = iWifiChip.selectTxPowerScenario(halScenario);
                      if (ok(status)) {
                          mLog.d("Setting SAR scenario to " + halScenario);
                          return true;
                      } else {
                          mLog.e("Failed to set SAR scenario to " + halScenario);
                          return false;
                      }
                  }
  
                  // Reaching here means setting SAR scenario would be redundant,
                  // do nothing and return with success.
                  return true;
              }
  
              // We don't need to perform power backoff, so attempt to reset SAR scenario.
              if (sarInfo.resetSarScenarioNeeded()) {
                  status = iWifiChip.resetTxPowerScenario();
                  if (ok(status)) {
                      mLog.d("Resetting SAR scenario");
                      return true;
                  } else {
                      mLog.e("Failed to reset SAR scenario");
                      return false;
                  }
              }
  
              // Resetting SAR scenario would be redundant,
              // do nothing and return with success.
              return true;
          } catch (RemoteException e) {
              handleRemoteException(e);
              return false;
          } catch (IllegalArgumentException e) {
              mLog.err("Illegal argument for selectTxPowerScenario_1_1()").c(e.toString()).flush();
              return false;
          }
      }
  
      private boolean selectTxPowerScenario_1_2(
              android.hardware.wifi.V1_2.IWifiChip iWifiChip, SarInfo sarInfo) {
          WifiStatus status;
          try {
              if (sarPowerBackoffRequired_1_2(sarInfo)) {
                  // Power backoff is needed, so calculate the required scenario,
                  // and attempt to set it.
                  int halScenario = frameworkToHalTxPowerScenario_1_2(sarInfo);
                  if (sarInfo.setSarScenarioNeeded(halScenario)) {
                      status = iWifiChip.selectTxPowerScenario_1_2(halScenario);
                      if (ok(status)) {
                          mLog.d("Setting SAR scenario to " + halScenario);
                          return true;
                      } else {
                          mLog.e("Failed to set SAR scenario to " + halScenario);
                          return false;
                      }
                  }
  
                  // Reaching here means setting SAR scenario would be redundant,
                  // do nothing and return with success.
                  return true;
              }
  
              // We don't need to perform power backoff, so attempt to reset SAR scenario.
              if (sarInfo.resetSarScenarioNeeded()) {
                  status = iWifiChip.resetTxPowerScenario();
                  if (ok(status)) {
                      mLog.d("Resetting SAR scenario");
                      return true;
                  } else {
                      mLog.e("Failed to reset SAR scenario");
                      return false;
                  }
              }
  
              // Resetting SAR scenario would be redundant,
              // do nothing and return with success.
              return true;
          } catch (RemoteException e) {
              handleRemoteException(e);
              return false;
          } catch (IllegalArgumentException e) {
              mLog.err("Illegal argument for selectTxPowerScenario_1_2()").c(e.toString()).flush();
              return false;
          }
      }
  
      /**
       * Enable/Disable low-latency mode
       *
       * @param enabled true to enable low-latency mode, false to disable it
       */
      public boolean setLowLatencyMode(boolean enabled) {
          synchronized (sLock) {
              android.hardware.wifi.V1_3.IWifiChip iWifiChipV13 = getWifiChipForV1_3Mockable();
              if (iWifiChipV13 != null) {
                  try {
                      int mode;
                      if (enabled) {
                          mode = android.hardware.wifi.V1_3.IWifiChip.LatencyMode.LOW;
                      } else {
                          mode = android.hardware.wifi.V1_3.IWifiChip.LatencyMode.NORMAL;
                      }
  
                      WifiStatus status = iWifiChipV13.setLatencyMode(mode);
                      if (ok(status)) {
                          mVerboseLog.d("Setting low-latency mode to " + enabled);
                          return true;
                      } else {
                          mLog.e("Failed to set low-latency mode to " + enabled);
                          return false;
                      }
                  } catch (RemoteException e) {
                      handleRemoteException(e);
                      return false;
                  }
              }
  
              // HAL version does not support this api
              return false;
          }
      }
  
      // This creates a blob of IE elements from the array received.
      // TODO: This ugly conversion can be removed if we put IE elements in ScanResult.
      private static byte[] hidlIeArrayToFrameworkIeBlob(ArrayList<WifiInformationElement> ies) {
          if (ies == null || ies.isEmpty()) return new byte[0];
          ArrayList<Byte> ieBlob = new ArrayList<>();
          for (WifiInformationElement ie : ies) {
              ieBlob.add(ie.id);
              ieBlob.addAll(ie.data);
          }
          return NativeUtil.byteArrayFromArrayList(ieBlob);
      }
  
      // This is only filling up the fields of Scan Result used by Gscan clients.
      private static ScanResult hidlToFrameworkScanResult(StaScanResult scanResult) {
          if (scanResult == null) return null;
          ScanResult frameworkScanResult = new ScanResult();
          frameworkScanResult.SSID = NativeUtil.encodeSsid(scanResult.ssid);
          frameworkScanResult.wifiSsid =
                  WifiSsid.createFromByteArray(NativeUtil.byteArrayFromArrayList(scanResult.ssid));
          frameworkScanResult.BSSID = NativeUtil.macAddressFromByteArray(scanResult.bssid);
          frameworkScanResult.level = scanResult.rssi;
          frameworkScanResult.frequency = scanResult.frequency;
          frameworkScanResult.timestamp = scanResult.timeStampInUs;
          return frameworkScanResult;
      }
  
      private static ScanResult[] hidlToFrameworkScanResults(ArrayList<StaScanResult> scanResults) {
          if (scanResults == null || scanResults.isEmpty()) return new ScanResult[0];
          ScanResult[] frameworkScanResults = new ScanResult[scanResults.size()];
          int i = 0;
          for (StaScanResult scanResult : scanResults) {
              frameworkScanResults[i++] = hidlToFrameworkScanResult(scanResult);
          }
          return frameworkScanResults;
      }
  
      /**
       * This just returns whether the scan was interrupted or not.
       */
      private static int hidlToFrameworkScanDataFlags(int flag) {
          if (flag == StaScanDataFlagMask.INTERRUPTED) {
              return 1;
          } else {
              return 0;
          }
      }
  
      private static WifiScanner.ScanData[] hidlToFrameworkScanDatas(
              int cmdId, ArrayList<StaScanData> scanDatas) {
          if (scanDatas == null || scanDatas.isEmpty()) return new WifiScanner.ScanData[0];
          WifiScanner.ScanData[] frameworkScanDatas = new WifiScanner.ScanData[scanDatas.size()];
          int i = 0;
          for (StaScanData scanData : scanDatas) {
              int flags = hidlToFrameworkScanDataFlags(scanData.flags);
              ScanResult[] frameworkScanResults = hidlToFrameworkScanResults(scanData.results);
              frameworkScanDatas[i++] =
                      new WifiScanner.ScanData(cmdId, flags, scanData.bucketsScanned,
                              WifiScanner.WIFI_BAND_UNSPECIFIED, frameworkScanResults);
          }
          return frameworkScanDatas;
      }
  
      /**
       * Callback for events on the STA interface.
       */
      private class StaIfaceEventCallback extends IWifiStaIfaceEventCallback.Stub {
          @Override
          public void onBackgroundScanFailure(int cmdId) {
              mVerboseLog.d("onBackgroundScanFailure " + cmdId);
              WifiNative.ScanEventHandler eventHandler;
              synchronized (sLock) {
                  if (mScan == null || cmdId != mScan.cmdId) return;
                  eventHandler = mScan.eventHandler;
              }
              eventHandler.onScanStatus(WifiNative.WIFI_SCAN_FAILED);
          }
  
          @Override
          public void onBackgroundFullScanResult(
                  int cmdId, int bucketsScanned, StaScanResult result) {
              mVerboseLog.d("onBackgroundFullScanResult " + cmdId);
              WifiNative.ScanEventHandler eventHandler;
              synchronized (sLock) {
                  if (mScan == null || cmdId != mScan.cmdId) return;
                  eventHandler = mScan.eventHandler;
              }
              eventHandler.onFullScanResult(hidlToFrameworkScanResult(result), bucketsScanned);
          }
  
          @Override
          public void onBackgroundScanResults(int cmdId, ArrayList<StaScanData> scanDatas) {
              mVerboseLog.d("onBackgroundScanResults " + cmdId);
              WifiNative.ScanEventHandler eventHandler;
              // WifiScanner currently uses the results callback to fetch the scan results.
              // So, simulate that by sending out the notification and then caching the results
              // locally. This will then be returned to WifiScanner via getScanResults.
              synchronized (sLock) {
                  if (mScan == null || cmdId != mScan.cmdId) return;
                  eventHandler = mScan.eventHandler;
                  mScan.latestScanResults = hidlToFrameworkScanDatas(cmdId, scanDatas);
              }
              eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
          }
  
          @Override
          public void onRssiThresholdBreached(int cmdId, byte[/* 6 */] currBssid, int currRssi) {
              mVerboseLog.d("onRssiThresholdBreached " + cmdId + "currRssi " + currRssi);
              WifiNative.WifiRssiEventHandler eventHandler;
              synchronized (sLock) {
                  if (mWifiRssiEventHandler == null || cmdId != sRssiMonCmdId) return;
                  eventHandler = mWifiRssiEventHandler;
              }
              eventHandler.onRssiThresholdBreached((byte) currRssi);
          }
      }
  
      /**
       * Callback for events on the chip.
       */
      private class ChipEventCallback extends IWifiChipEventCallback.Stub {
          @Override
          public void onChipReconfigured(int modeId) {
              mVerboseLog.d("onChipReconfigured " + modeId);
          }
  
          @Override
          public void onChipReconfigureFailure(WifiStatus status) {
              mVerboseLog.d("onChipReconfigureFailure " + status);
          }
  
          public void onIfaceAdded(int type, String name) {
              mVerboseLog.d("onIfaceAdded " + type + ", name: " + name);
          }
  
          @Override
          public void onIfaceRemoved(int type, String name) {
              mVerboseLog.d("onIfaceRemoved " + type + ", name: " + name);
          }
  
          @Override
          public void onDebugRingBufferDataAvailable(
                  WifiDebugRingBufferStatus status, java.util.ArrayList<Byte> data) {
              //TODO(b/35875078) Reinstate logging when execessive callbacks are fixed
              // mVerboseLog.d("onDebugRingBufferDataAvailable " + status);
              mHalEventHandler.post(() -> {
                  WifiNative.WifiLoggerEventHandler eventHandler;
                  synchronized (sLock) {
                      if (mLogEventHandler == null || status == null || data == null) return;
                      eventHandler = mLogEventHandler;
                  }
                  // Because |sLock| has been released, there is a chance that we'll execute
                  // a spurious callback (after someone has called resetLogHandler()).
                  //
                  // However, the alternative risks deadlock. Consider:
                  // [T1.1] WifiDiagnostics.captureBugReport()
                  // [T1.2] -- acquire WifiDiagnostics object's intrinsic lock
                  // [T1.3]    -> WifiVendorHal.getRingBufferData()
                  // [T1.4]       -- acquire WifiVendorHal.sLock
                  // [T2.1] <lambda>()
                  // [T2.2] -- acquire WifiVendorHal.sLock
                  // [T2.3]    -> WifiDiagnostics.onRingBufferData()
                  // [T2.4]       -- acquire WifiDiagnostics object's intrinsic lock
                  //
                  // The problem here is that the two threads acquire the locks in opposite order.
                  // If, for example, T2.2 executes between T1.2 and 1.4, then T1 and T2
                  // will be deadlocked.
                  int sizeBefore = data.size();
                  boolean conversionFailure = false;
                  try {
                      eventHandler.onRingBufferData(
                              ringBufferStatus(status), NativeUtil.byteArrayFromArrayList(data));
                      int sizeAfter = data.size();
                      if (sizeAfter != sizeBefore) {
                          conversionFailure = true;
                      }
                  } catch (ArrayIndexOutOfBoundsException e) {
                      conversionFailure = true;
                  }
                  if (conversionFailure) {
                      Log.wtf("WifiVendorHal", "Conversion failure detected in "
                              + "onDebugRingBufferDataAvailable. "
                              + "The input ArrayList |data| is potentially corrupted. "
                              + "Starting size=" + sizeBefore + ", "
                              + "final size=" + data.size());
                  }
              });
          }
  
          @Override
          public void onDebugErrorAlert(int errorCode, java.util.ArrayList<Byte> debugData) {
              mLog.w("onDebugErrorAlert " + errorCode);
              mHalEventHandler.post(() -> {
                  WifiNative.WifiLoggerEventHandler eventHandler;
                  synchronized (sLock) {
                      if (mLogEventHandler == null || debugData == null) return;
                      eventHandler = mLogEventHandler;
                  }
                  // See comment in onDebugRingBufferDataAvailable(), for an explanation
                  // of why this callback is invoked without |sLock| held.
                  eventHandler.onWifiAlert(
                          errorCode, NativeUtil.byteArrayFromArrayList(debugData));
              });
          }
      }
  
      /**
       * Callback for events on the 1.2 chip.
       */
      private class ChipEventCallbackV12 extends
              android.hardware.wifi.V1_2.IWifiChipEventCallback.Stub {
          @Override
          public void onChipReconfigured(int modeId) {
              mIWifiChipEventCallback.onChipReconfigured(modeId);
          }
  
          @Override
          public void onChipReconfigureFailure(WifiStatus status) {
              mIWifiChipEventCallback.onChipReconfigureFailure(status);
          }
  
          public void onIfaceAdded(int type, String name) {
              mIWifiChipEventCallback.onIfaceAdded(type, name);
          }
  
          @Override
          public void onIfaceRemoved(int type, String name) {
              mIWifiChipEventCallback.onIfaceRemoved(type, name);
          }
  
          @Override
          public void onDebugRingBufferDataAvailable(
                  WifiDebugRingBufferStatus status, java.util.ArrayList<Byte> data) {
              mIWifiChipEventCallback.onDebugRingBufferDataAvailable(status, data);
          }
  
          @Override
          public void onDebugErrorAlert(int errorCode, java.util.ArrayList<Byte> debugData) {
              mIWifiChipEventCallback.onDebugErrorAlert(errorCode, debugData);
          }
  
          private boolean areSameIfaceNames(List<IfaceInfo> ifaceList1, List<IfaceInfo> ifaceList2) {
              List<String> ifaceNamesList1 = ifaceList1
                      .stream()
                      .map(i -> i.name)
                      .collect(Collectors.toList());
              List<String> ifaceNamesList2 = ifaceList2
                      .stream()
                      .map(i -> i.name)
                      .collect(Collectors.toList());
              return ifaceNamesList1.containsAll(ifaceNamesList2);
          }
  
          private boolean areSameIfaces(List<IfaceInfo> ifaceList1, List<IfaceInfo> ifaceList2) {
              return ifaceList1.containsAll(ifaceList2);
          }
  
          @Override
          public void onRadioModeChange(ArrayList<RadioModeInfo> radioModeInfoList) {
              mVerboseLog.d("onRadioModeChange " + radioModeInfoList);
              WifiNative.VendorHalRadioModeChangeEventHandler handler;
              synchronized (sLock) {
                  if (mRadioModeChangeEventHandler == null || radioModeInfoList == null) return;
                  handler = mRadioModeChangeEventHandler;
              }
              // Should only contain 1 or 2 radio infos.
              if (radioModeInfoList.size() == 0 || radioModeInfoList.size() > 2) {
                  mLog.e("Unexpected number of radio info in list " + radioModeInfoList.size());
                  return;
              }
              RadioModeInfo radioModeInfo0 = radioModeInfoList.get(0);
              RadioModeInfo radioModeInfo1 =
                      radioModeInfoList.size() == 2 ? radioModeInfoList.get(1) : null;
              // Number of ifaces on each radio should be equal.
              if (radioModeInfo1 != null
                      && radioModeInfo0.ifaceInfos.size() != radioModeInfo1.ifaceInfos.size()) {
                  mLog.e("Unexpected number of iface info in list "
                          + radioModeInfo0.ifaceInfos.size() + ", "
                          + radioModeInfo1.ifaceInfos.size());
                  return;
              }
              int numIfacesOnEachRadio = radioModeInfo0.ifaceInfos.size();
              // Only 1 or 2 ifaces should be present on each radio.
              if (numIfacesOnEachRadio == 0 || numIfacesOnEachRadio > 2) {
                  mLog.e("Unexpected number of iface info in list " + numIfacesOnEachRadio);
                  return;
              }
              Runnable runnable = null;
              // 2 ifaces simultaneous on 2 radios.
              if (radioModeInfoList.size() == 2 && numIfacesOnEachRadio == 1) {
                  // Iface on radio0 should be different from the iface on radio1 for DBS & SBS.
                  if (areSameIfaceNames(radioModeInfo0.ifaceInfos, radioModeInfo1.ifaceInfos)) {
                      mLog.e("Unexpected for both radio infos to have same iface");
                      return;
                  }
                  if (radioModeInfo0.bandInfo != radioModeInfo1.bandInfo) {
                      runnable = () -> {
                          handler.onDbs();
                      };
                  } else {
                      runnable = () -> {
                          handler.onSbs(radioModeInfo0.bandInfo);
                      };
                  }
              // 2 ifaces time sharing on 1 radio.
              } else if (radioModeInfoList.size() == 1 && numIfacesOnEachRadio == 2) {
                  IfaceInfo ifaceInfo0 = radioModeInfo0.ifaceInfos.get(0);
                  IfaceInfo ifaceInfo1 = radioModeInfo0.ifaceInfos.get(1);
                  if (ifaceInfo0.channel != ifaceInfo1.channel) {
                      runnable = () -> {
                          handler.onMcc(radioModeInfo0.bandInfo);
                      };
                  } else {
                      runnable = () -> {
                          handler.onScc(radioModeInfo0.bandInfo);
                      };
                  }
              } else {
                  // Not concurrency scenario, uninteresting...
              }
              if (runnable != null) mHalEventHandler.post(runnable);
          }
      }
  
      /**
       * Hal Device Manager callbacks.
       */
      public class HalDeviceManagerStatusListener implements HalDeviceManager.ManagerStatusListener {
          @Override
          public void onStatusChanged() {
              boolean isReady = mHalDeviceManager.isReady();
              boolean isStarted = mHalDeviceManager.isStarted();
  
              mVerboseLog.i("Device Manager onStatusChanged. isReady(): " + isReady
                      + ", isStarted(): " + isStarted);
              if (!isReady) {
                  // Probably something unpleasant, e.g. the server died
                  WifiNative.VendorHalDeathEventHandler handler;
                  synchronized (sLock) {
                      clearState();
                      handler = mDeathEventHandler;
                  }
                  if (handler != null) {
                      handler.onDeath();
                  }
              }
          }
      }
  }
  