[AWARE] Fix message length limit comparison am: 8685e078c9 am: af95ae8575
am: f5c1120540  -s ours

Change-Id: I324e42a2d0726d01999447b9603ade45b0b19dac
diff --git a/libwifi_hal/wifi_hal_common.cpp b/libwifi_hal/wifi_hal_common.cpp
index 04e5925..413daf7 100644
--- a/libwifi_hal/wifi_hal_common.cpp
+++ b/libwifi_hal/wifi_hal_common.cpp
@@ -23,6 +23,7 @@
 #include <android-base/logging.h>
 #include <cutils/misc.h>
 #include <cutils/properties.h>
+#include <sys/syscall.h>
 
 extern "C" int init_module(void *, unsigned long, const char *);
 extern "C" int delete_module(const char *, unsigned int);
@@ -51,16 +52,21 @@
 #endif
 
 static int insmod(const char *filename, const char *args) {
-  void *module;
-  unsigned int size;
   int ret;
+  int fd;
 
-  module = load_file(filename, &size);
-  if (!module) return -1;
+  fd = TEMP_FAILURE_RETRY(open(filename, O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
+  if (fd < 0) {
+    PLOG(ERROR) << "Failed to open " << filename;
+    return -1;
+  }
 
-  ret = init_module(module, size, args);
+  ret = syscall(__NR_finit_module, fd, args, 0);
 
-  free(module);
+  close(fd);
+  if (ret < 0) {
+    PLOG(ERROR) << "finit_module return: " << ret;
+  }
 
   return ret;
 }
diff --git a/service/Android.mk b/service/Android.mk
index 5b539e5..7b3b115 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -65,7 +65,9 @@
 	android.hardware.wifi-V1.0-java \
 	android.hardware.wifi-V1.1-java \
 	android.hardware.wifi.supplicant-V1.0-java
-LOCAL_REQUIRED_MODULES := services
+LOCAL_REQUIRED_MODULES := \
+	services \
+	libwifi-service
 LOCAL_MODULE_TAGS :=
 LOCAL_MODULE := wifi-service
 LOCAL_INIT_RC := wifi-events.rc
diff --git a/service/java/com/android/server/wifi/ExtendedWifiInfo.java b/service/java/com/android/server/wifi/ExtendedWifiInfo.java
new file mode 100644
index 0000000..5edbd34
--- /dev/null
+++ b/service/java/com/android/server/wifi/ExtendedWifiInfo.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 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.net.wifi.WifiInfo;
+
+/**
+ * Extends WifiInfo with the methods for computing the averaged packet rates
+ */
+public class ExtendedWifiInfo extends WifiInfo {
+    private static final long RESET_TIME_STAMP = Long.MIN_VALUE;
+    private static final double FILTER_TIME_CONSTANT = 3000.0;
+
+    private long mLastPacketCountUpdateTimeStamp = RESET_TIME_STAMP;
+
+    @Override
+    public void reset() {
+        super.reset();
+        mLastPacketCountUpdateTimeStamp = RESET_TIME_STAMP;
+    }
+
+    /**
+     * Updates the packet rates using link layer stats
+     *
+     * @param stats WifiLinkLayerStats
+     * @param timeStamp time in milliseconds
+     */
+    public void updatePacketRates(WifiLinkLayerStats stats, long timeStamp) {
+        if (stats != null) {
+            long txgood = stats.txmpdu_be + stats.txmpdu_bk + stats.txmpdu_vi + stats.txmpdu_vo;
+            long txretries = stats.retries_be + stats.retries_bk
+                    + stats.retries_vi + stats.retries_vo;
+            long rxgood = stats.rxmpdu_be + stats.rxmpdu_bk + stats.rxmpdu_vi + stats.rxmpdu_vo;
+            long txbad = stats.lostmpdu_be + stats.lostmpdu_bk
+                    + stats.lostmpdu_vi + stats.lostmpdu_vo;
+
+            if (mLastPacketCountUpdateTimeStamp != RESET_TIME_STAMP
+                    && mLastPacketCountUpdateTimeStamp < timeStamp
+                    && txBad <= txbad
+                    && txSuccess <= txgood
+                    && rxSuccess <= rxgood
+                    && txRetries <= txretries) {
+                long timeDelta = timeStamp - mLastPacketCountUpdateTimeStamp;
+                double lastSampleWeight = Math.exp(-1.0 * timeDelta / FILTER_TIME_CONSTANT);
+                double currentSampleWeight = 1.0 - lastSampleWeight;
+
+                txBadRate = txBadRate * lastSampleWeight
+                        + (txbad - txBad) * 1000.0 / timeDelta
+                        * currentSampleWeight;
+                txSuccessRate = txSuccessRate * lastSampleWeight
+                        + (txgood - txSuccess) * 1000.0 / timeDelta
+                        * currentSampleWeight;
+                rxSuccessRate = rxSuccessRate * lastSampleWeight
+                        + (rxgood - rxSuccess) * 1000.0 / timeDelta
+                        * currentSampleWeight;
+                txRetriesRate = txRetriesRate * lastSampleWeight
+                        + (txretries - txRetries) * 1000.0 / timeDelta
+                        * currentSampleWeight;
+            } else {
+                txBadRate = 0;
+                txSuccessRate = 0;
+                rxSuccessRate = 0;
+                txRetriesRate = 0;
+            }
+            txBad = txbad;
+            txSuccess = txgood;
+            rxSuccess = rxgood;
+            txRetries = txretries;
+            mLastPacketCountUpdateTimeStamp = timeStamp;
+        } else {
+            txBad = 0;
+            txSuccess = 0;
+            rxSuccess = 0;
+            txRetries = 0;
+            txBadRate = 0;
+            txSuccessRate = 0;
+            rxSuccessRate = 0;
+            txRetriesRate = 0;
+            mLastPacketCountUpdateTimeStamp = RESET_TIME_STAMP;
+        }
+    }
+
+    /**
+     * This function is less powerful and used if the WifiLinkLayerStats API is not implemented
+     * at the Wifi HAL
+     *
+     * @hide
+     */
+    public void updatePacketRates(long txPackets, long rxPackets) {
+        //paranoia
+        txBad = 0;
+        txRetries = 0;
+        txBadRate = 0;
+        txRetriesRate = 0;
+        if (txSuccess <= txPackets && rxSuccess <= rxPackets) {
+            txSuccessRate = (txSuccessRate * 0.5)
+                    + ((double) (txPackets - txSuccess) * 0.5);
+            rxSuccessRate = (rxSuccessRate * 0.5)
+                    + ((double) (rxPackets - rxSuccess) * 0.5);
+        } else {
+            txBadRate = 0;
+            txRetriesRate = 0;
+        }
+        txSuccess = txPackets;
+        rxSuccess = rxPackets;
+    }
+
+
+}
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index 2c16444..31124d5 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -79,6 +79,17 @@
                 contentObserver);
     }
 
+    /**
+     * Helper method for classes to unregister a ContentObserver
+     * {@see ContentResolver#unregisterContentObserver(ContentObserver)}.
+     *
+     * @param context
+     * @param contentObserver
+     */
+    public void unregisterContentObserver(Context context, ContentObserver contentObserver) {
+        context.getContentResolver().unregisterContentObserver(contentObserver);
+    }
+
     public IBinder getService(String serviceName) {
         return ServiceManager.getService(serviceName);
     }
diff --git a/service/java/com/android/server/wifi/HalDeviceManager.java b/service/java/com/android/server/wifi/HalDeviceManager.java
index d6009c7..d65f31f 100644
--- a/service/java/com/android/server/wifi/HalDeviceManager.java
+++ b/service/java/com/android/server/wifi/HalDeviceManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.wifi.V1_0.IWifi;
 import android.hardware.wifi.V1_0.IWifiApIface;
 import android.hardware.wifi.V1_0.IWifiChip;
@@ -34,10 +36,9 @@
 import android.hidl.manager.V1_0.IServiceNotification;
 import android.os.Handler;
 import android.os.HwRemoteBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.LongSparseArray;
 import android.util.MutableBoolean;
 import android.util.MutableInt;
 import android.util.SparseArray;
@@ -70,8 +71,12 @@
     @VisibleForTesting
     public static final String HAL_INSTANCE_NAME = "default";
 
+    private final Clock mClock;
+
     // public API
-    public HalDeviceManager() {
+    public HalDeviceManager(Clock clock) {
+        mClock = clock;
+
         mInterfaceAvailableForRequestListeners.put(IfaceType.STA, new HashSet<>());
         mInterfaceAvailableForRequestListeners.put(IfaceType.AP, new HashSet<>());
         mInterfaceAvailableForRequestListeners.put(IfaceType.P2P, new HashSet<>());
@@ -98,12 +103,14 @@
      * single copy kept.
      *
      * @param listener ManagerStatusListener listener object.
-     * @param looper Looper on which to dispatch listener. Null implies current looper.
+     * @param handler Handler on which to dispatch listener. Null implies the listener will be
+     *                invoked synchronously from the context of the client which triggered the
+     *                state change.
      */
-    public void registerStatusListener(ManagerStatusListener listener, Looper looper) {
+    public void registerStatusListener(@NonNull ManagerStatusListener listener,
+            @Nullable Handler handler) {
         synchronized (mLock) {
-            if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener,
-                    looper == null ? Looper.myLooper() : looper))) {
+            if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener, handler))) {
                 Log.w(TAG, "registerStatusListener: duplicate registration ignored");
             }
         }
@@ -192,37 +199,38 @@
      * @param destroyedListener Optional (nullable) listener to call when the allocated interface
      *                          is removed. Will only be registered and used if an interface is
      *                          created successfully.
-     * @param looper The looper on which to dispatch the listener. A null value indicates the
-     *               current thread.
+     * @param handler Handler on which to dispatch listener. Null implies the listener will be
+     *                invoked synchronously from the context of the client which triggered the
+     *                iface destruction.
      * @return A newly created interface - or null if the interface could not be created.
      */
-    public IWifiStaIface createStaIface(InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
-        return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, looper);
+    public IWifiStaIface createStaIface(@Nullable InterfaceDestroyedListener destroyedListener,
+            @Nullable Handler handler) {
+        return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, handler);
     }
 
     /**
      * Create AP interface if possible (see createStaIface doc).
      */
-    public IWifiApIface createApIface(InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
-        return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, looper);
+    public IWifiApIface createApIface(@Nullable InterfaceDestroyedListener destroyedListener,
+            @Nullable Handler handler) {
+        return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, handler);
     }
 
     /**
      * Create P2P interface if possible (see createStaIface doc).
      */
-    public IWifiP2pIface createP2pIface(InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
-        return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, looper);
+    public IWifiP2pIface createP2pIface(@Nullable InterfaceDestroyedListener destroyedListener,
+            @Nullable Handler handler) {
+        return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, handler);
     }
 
     /**
      * Create NAN interface if possible (see createStaIface doc).
      */
-    public IWifiNanIface createNanIface(InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
-        return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, looper);
+    public IWifiNanIface createNanIface(@Nullable InterfaceDestroyedListener destroyedListener,
+            @Nullable Handler handler) {
+        return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, handler);
     }
 
     /**
@@ -263,11 +271,16 @@
      * and false on failure. This listener is in addition to the one registered when the interface
      * was created - allowing non-creators to monitor interface status.
      *
-     * Listener called-back on the specified looper - or on the current looper if a null is passed.
+     * @param destroyedListener Listener to call when the allocated interface is removed.
+     *                          Will only be registered and used if an interface is created
+     *                          successfully.
+     * @param handler Handler on which to dispatch listener. Null implies the listener will be
+     *                invoked synchronously from the context of the client which triggered the
+     *                iface destruction.
      */
     public boolean registerDestroyedListener(IWifiIface iface,
-            InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
+            @NonNull InterfaceDestroyedListener destroyedListener,
+            @Nullable Handler handler) {
         String name = getName(iface);
         if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name);
 
@@ -279,8 +292,7 @@
             }
 
             return cacheEntry.destroyedListeners.add(
-                    new InterfaceDestroyedListenerProxy(destroyedListener,
-                            looper == null ? Looper.myLooper() : looper));
+                    new InterfaceDestroyedListenerProxy(name, destroyedListener, handler));
         }
     }
 
@@ -299,17 +311,17 @@
      * @param ifaceType The interface type (IfaceType) to be monitored.
      * @param listener Listener to call when an interface of the requested
      *                 type could be created
-     * @param looper The looper on which to dispatch the listener. A null value indicates the
-     *               current thread.
+     * @param handler Handler on which to dispatch listener. Null implies the listener will be
+     *                invoked synchronously from the context of the client which triggered the
+     *                mode change.
      */
     public void registerInterfaceAvailableForRequestListener(int ifaceType,
-            InterfaceAvailableForRequestListener listener, Looper looper) {
+            @NonNull InterfaceAvailableForRequestListener listener, @Nullable Handler handler) {
         if (DBG) Log.d(TAG, "registerInterfaceAvailableForRequestListener: ifaceType=" + ifaceType);
 
         synchronized (mLock) {
             mInterfaceAvailableForRequestListeners.get(ifaceType).add(
-                    new InterfaceAvailableForRequestListenerProxy(listener,
-                            looper == null ? Looper.myLooper() : looper));
+                    new InterfaceAvailableForRequestListenerProxy(listener, handler));
         }
 
         WifiChipInfo[] chipInfos = getAllChipInfo();
@@ -379,8 +391,10 @@
          *
          * Can be registered when the interface is requested with createXxxIface() - will
          * only be valid if the interface creation was successful - i.e. a non-null was returned.
+         *
+         * @param ifaceName Name of the interface that was destroyed.
          */
-        void onDestroyed();
+        void onDestroyed(@NonNull String ifaceName);
     }
 
     /**
@@ -396,43 +410,48 @@
     }
 
     /**
-     * Creates a IWifiRttController corresponding to the input interface. A direct match to the
-     * IWifiChip.createRttController() method.
+     * Creates a IWifiRttController. A direct match to the IWifiChip.createRttController() method.
      *
      * Returns the created IWifiRttController or a null on error.
      */
-    public IWifiRttController createRttController(IWifiIface boundIface) {
-        if (DBG) Log.d(TAG, "createRttController: boundIface(name)=" + getName(boundIface));
+    public IWifiRttController createRttController() {
+        if (DBG) Log.d(TAG, "createRttController");
         synchronized (mLock) {
             if (mWifi == null) {
-                Log.e(TAG, "createRttController: null IWifi -- boundIface(name)="
-                        + getName(boundIface));
+                Log.e(TAG, "createRttController: null IWifi");
                 return null;
             }
 
-            IWifiChip chip = getChip(boundIface);
-            if (chip == null) {
-                Log.e(TAG, "createRttController: null IWifiChip -- boundIface(name)="
-                        + getName(boundIface));
+            WifiChipInfo[] chipInfos = getAllChipInfo();
+            if (chipInfos == null) {
+                Log.e(TAG, "createRttController: no chip info found");
+                stopWifi(); // major error: shutting down
                 return null;
             }
 
-            Mutable<IWifiRttController> rttResp = new Mutable<>();
-            try {
-                chip.createRttController(boundIface,
-                        (WifiStatus status, IWifiRttController rtt) -> {
-                            if (status.code == WifiStatusCode.SUCCESS) {
-                                rttResp.value = rtt;
-                            } else {
-                                Log.e(TAG, "IWifiChip.createRttController failed: " + statusString(
-                                        status));
-                            }
-                        });
-            } catch (RemoteException e) {
-                Log.e(TAG, "IWifiChip.createRttController exception: " + e);
+            for (WifiChipInfo chipInfo : chipInfos) {
+                Mutable<IWifiRttController> rttResp = new Mutable<>();
+                try {
+                    chipInfo.chip.createRttController(null,
+                            (WifiStatus status, IWifiRttController rtt) -> {
+                                if (status.code == WifiStatusCode.SUCCESS) {
+                                    rttResp.value = rtt;
+                                } else {
+                                    Log.e(TAG,
+                                            "IWifiChip.createRttController failed: " + statusString(
+                                                    status));
+                                }
+                            });
+                } catch (RemoteException e) {
+                    Log.e(TAG, "IWifiChip.createRttController exception: " + e);
+                }
+                if (rttResp.value != null) {
+                    return rttResp.value;
+                }
             }
 
-            return rttResp.value;
+            Log.e(TAG, "createRttController: not available from any of the chips");
+            return null;
         }
     }
 
@@ -469,12 +488,14 @@
         public String name;
         public int type;
         public Set<InterfaceDestroyedListenerProxy> destroyedListeners = new HashSet<>();
+        public long creationTime;
 
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder();
             sb.append("{name=").append(name).append(", type=").append(type)
                     .append(", destroyedListeners.size()=").append(destroyedListeners.size())
+                    .append(", creationTime=").append(creationTime)
                     .append("}");
             return sb.toString();
         }
@@ -1202,9 +1223,8 @@
 
     private class ManagerStatusListenerProxy  extends
             ListenerProxy<ManagerStatusListener> {
-        ManagerStatusListenerProxy(ManagerStatusListener statusListener,
-                Looper looper) {
-            super(statusListener, looper, "ManagerStatusListenerProxy");
+        ManagerStatusListenerProxy(ManagerStatusListener statusListener, Handler handler) {
+            super(statusListener, handler, "ManagerStatusListenerProxy");
         }
 
         @Override
@@ -1265,7 +1285,7 @@
     }
 
     private IWifiIface createIface(int ifaceType, InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
+            Handler handler) {
         if (DBG) Log.d(TAG, "createIface: ifaceType=" + ifaceType);
 
         synchronized (mLock) {
@@ -1283,7 +1303,7 @@
             }
 
             IWifiIface iface = createIfaceIfPossible(chipInfos, ifaceType, destroyedListener,
-                    looper);
+                    handler);
             if (iface != null) { // means that some configuration has changed
                 if (!dispatchAvailableForRequestListeners()) {
                     return null; // catastrophic failure - shut down
@@ -1295,7 +1315,7 @@
     }
 
     private IWifiIface createIfaceIfPossible(WifiChipInfo[] chipInfos, int ifaceType,
-            InterfaceDestroyedListener destroyedListener, Looper looper) {
+            InterfaceDestroyedListener destroyedListener, Handler handler) {
         if (DBG) {
             Log.d(TAG, "createIfaceIfPossible: chipInfos=" + Arrays.deepToString(chipInfos)
                     + ", ifaceType=" + ifaceType);
@@ -1336,9 +1356,10 @@
                     cacheEntry.type = ifaceType;
                     if (destroyedListener != null) {
                         cacheEntry.destroyedListeners.add(
-                                new InterfaceDestroyedListenerProxy(destroyedListener,
-                                        looper == null ? Looper.myLooper() : looper));
+                                new InterfaceDestroyedListenerProxy(
+                                        cacheEntry.name, destroyedListener, handler));
                     }
+                    cacheEntry.creationTime = mClock.getUptimeSinceBootMillis();
 
                     if (DBG) Log.d(TAG, "createIfaceIfPossible: added cacheEntry=" + cacheEntry);
                     mInterfaceInfoCache.put(cacheEntry.name, cacheEntry);
@@ -1463,7 +1484,8 @@
         if (isChipModeChangeProposed) {
             for (int type: IFACE_TYPES_BY_PRIORITY) {
                 if (chipInfo.ifaces[type].length != 0) {
-                    if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType)) {
+                    if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType,
+                            chipInfo.ifaces)) {
                         if (DBG) {
                             Log.d(TAG, "Couldn't delete existing type " + type
                                     + " interfaces for requested type");
@@ -1493,17 +1515,16 @@
             }
 
             if (tooManyInterfaces > 0) { // may need to delete some
-                if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType)) {
+                if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType, chipInfo.ifaces)) {
                     if (DBG) {
                         Log.d(TAG, "Would need to delete some higher priority interfaces");
                     }
                     return null;
                 }
 
-                // arbitrarily pick the first interfaces to delete
-                for (int i = 0; i < tooManyInterfaces; ++i) {
-                    interfacesToBeRemovedFirst.add(chipInfo.ifaces[type][i]);
-                }
+                // delete the most recently created interfaces
+                interfacesToBeRemovedFirst = selectInterfacesToDelete(tooManyInterfaces,
+                        chipInfo.ifaces[type]);
             }
         }
 
@@ -1570,35 +1591,92 @@
      * Returns true if we're allowed to delete the existing interface type for the requested
      * interface type.
      *
-     * Rules:
-     * 1. Request for AP or STA will destroy any other interface (except see #4)
-     * 2. Request for P2P will destroy NAN-only
-     * 3. Request for NAN will not destroy any interface
-     * --
-     * 4. No interface will be destroyed for a requested interface of the same type
+     * Rules - applies in order:
+     *
+     * General rules:
+     * 1. No interface will be destroyed for a requested interface of the same type
+     * 2. No interface will be destroyed if one of the requested interfaces already exists
+     * 3. If there are >1 interface of an existing type, then it is ok to destroy that type
+     *    interface
+     *
+     * Type-specific rules (but note that the general rules are appied first):
+     * 4. Request for AP or STA will destroy any other interface
+     * 5. Request for P2P will destroy NAN-only (but will destroy a second STA per #3)
+     * 6. Request for NAN will not destroy any interface (but will destroy a second STA per #3)
+
      */
     private boolean allowedToDeleteIfaceTypeForRequestedType(int existingIfaceType,
-            int requestedIfaceType) {
-        // rule 4
+            int requestedIfaceType, WifiIfaceInfo[][] currentIfaces) {
+        // rule 1
         if (existingIfaceType == requestedIfaceType) {
             return false;
         }
 
+        // rule 2
+        if (currentIfaces[requestedIfaceType].length != 0) {
+            return false;
+        }
+
         // rule 3
+        if (currentIfaces[existingIfaceType].length > 1) {
+            return true;
+        }
+
+        // rule 6
         if (requestedIfaceType == IfaceType.NAN) {
             return false;
         }
 
-        // rule 2
+        // rule 5
         if (requestedIfaceType == IfaceType.P2P) {
             return existingIfaceType == IfaceType.NAN;
         }
 
-        // rule 1, the requestIfaceType is either AP or STA
+        // rule 4, the requestIfaceType is either AP or STA
         return true;
     }
 
     /**
+     * Selects the interfaces to delete.
+     *
+     * Rule: select the most recently created interfaces in order.
+     *
+     * @param excessInterfaces Number of interfaces which need to be selected.
+     * @param interfaces Array of interfaces.
+     */
+    private List<WifiIfaceInfo> selectInterfacesToDelete(int excessInterfaces,
+            WifiIfaceInfo[] interfaces) {
+        if (DBG) {
+            Log.d(TAG, "selectInterfacesToDelete: excessInterfaces=" + excessInterfaces
+                    + ", interfaces=" + Arrays.toString(interfaces));
+        }
+
+        boolean lookupError = false;
+        LongSparseArray<WifiIfaceInfo> orderedList = new LongSparseArray(interfaces.length);
+        for (WifiIfaceInfo info : interfaces) {
+            InterfaceCacheEntry cacheEntry = mInterfaceInfoCache.get(info.name);
+            if (cacheEntry == null) {
+                Log.e(TAG,
+                        "selectInterfacesToDelete: can't find cache entry with name=" + info.name);
+                lookupError = true;
+                break;
+            }
+            orderedList.append(cacheEntry.creationTime, info);
+        }
+
+        if (lookupError) {
+            Log.e(TAG, "selectInterfacesToDelete: falling back to arbitary selection");
+            return Arrays.asList(Arrays.copyOf(interfaces, excessInterfaces));
+        } else {
+            List<WifiIfaceInfo> result = new ArrayList<>(excessInterfaces);
+            for (int i = 0; i < excessInterfaces; ++i) {
+                result.add(orderedList.valueAt(orderedList.size() - i - 1));
+            }
+            return result;
+        }
+    }
+
+    /**
      * Performs chip reconfiguration per the input:
      * - Removes the specified interfaces
      * - Reconfigures the chip to the new chip mode (if necessary)
@@ -1841,8 +1919,6 @@
     }
 
     private abstract class ListenerProxy<LISTENER>  {
-        private static final int LISTENER_TRIGGERED = 0;
-
         protected LISTENER mListener;
         private Handler mHandler;
 
@@ -1859,50 +1935,44 @@
         }
 
         void trigger() {
-            mHandler.sendMessage(mHandler.obtainMessage(LISTENER_TRIGGERED));
+            if (mHandler != null) {
+                mHandler.post(() -> {
+                    action();
+                });
+            } else {
+                action();
+            }
         }
 
         protected abstract void action();
 
-        ListenerProxy(LISTENER listener, Looper looper, String tag) {
+        ListenerProxy(LISTENER listener, Handler handler, String tag) {
             mListener = listener;
-            mHandler = new Handler(looper) {
-                @Override
-                public void handleMessage(Message msg) {
-                    if (DBG) {
-                        Log.d(tag, "ListenerProxy.handleMessage: what=" + msg.what);
-                    }
-                    switch (msg.what) {
-                        case LISTENER_TRIGGERED:
-                            action();
-                            break;
-                        default:
-                            Log.e(tag, "ListenerProxy.handleMessage: unknown message what="
-                                    + msg.what);
-                    }
-                }
-            };
+            mHandler = handler;
         }
     }
 
     private class InterfaceDestroyedListenerProxy extends
             ListenerProxy<InterfaceDestroyedListener> {
-        InterfaceDestroyedListenerProxy(InterfaceDestroyedListener destroyedListener,
-                Looper looper) {
-            super(destroyedListener, looper, "InterfaceDestroyedListenerProxy");
+        private final String mIfaceName;
+        InterfaceDestroyedListenerProxy(@NonNull String ifaceName,
+                                        InterfaceDestroyedListener destroyedListener,
+                                        Handler handler) {
+            super(destroyedListener, handler, "InterfaceDestroyedListenerProxy");
+            mIfaceName = ifaceName;
         }
 
         @Override
         protected void action() {
-            mListener.onDestroyed();
+            mListener.onDestroyed(mIfaceName);
         }
     }
 
     private class InterfaceAvailableForRequestListenerProxy extends
             ListenerProxy<InterfaceAvailableForRequestListener> {
         InterfaceAvailableForRequestListenerProxy(
-                InterfaceAvailableForRequestListener destroyedListener, Looper looper) {
-            super(destroyedListener, looper, "InterfaceAvailableForRequestListenerProxy");
+                InterfaceAvailableForRequestListener destroyedListener, Handler handler) {
+            super(destroyedListener, handler, "InterfaceAvailableForRequestListenerProxy");
         }
 
         @Override
diff --git a/service/java/com/android/server/wifi/LegacyConnectedScore.java b/service/java/com/android/server/wifi/LegacyConnectedScore.java
index facab0a..3027696 100644
--- a/service/java/com/android/server/wifi/LegacyConnectedScore.java
+++ b/service/java/com/android/server/wifi/LegacyConnectedScore.java
@@ -63,6 +63,10 @@
     private boolean mMultiBandScanResults;
     private boolean mIsHomeNetwork;
     private int mScore = 0;
+    private int mBadRssiCount;
+    private int mLinkStuckCount;
+    private int mLowRssiCount;
+
 
     LegacyConnectedScore(Context context, WifiConfigManager wifiConfigManager, Clock clock) {
         super(clock);
@@ -109,32 +113,32 @@
             rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST;
         }
 
-        if ((wifiInfo.txBadRate >= 1)
-                && (wifiInfo.txSuccessRate < MAX_SUCCESS_RATE_OF_STUCK_LINK)
+        if ((wifiInfo.txBadRate * 5 >= 1)
+                && (wifiInfo.txSuccessRate * 5 < MAX_SUCCESS_RATE_OF_STUCK_LINK)
                 && rssi < rssiThreshLow) {
             // Link is stuck
-            if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) {
-                wifiInfo.linkStuckCount += 1;
+            if (mLinkStuckCount < MAX_STUCK_LINK_COUNT) {
+                mLinkStuckCount += 1;
             }
-        } else if (wifiInfo.txBadRate < MIN_TX_FAILURE_RATE_FOR_WORKING_LINK) {
-            if (wifiInfo.linkStuckCount > 0) {
-                wifiInfo.linkStuckCount -= 1;
+        } else if (wifiInfo.txBadRate * 5 < MIN_TX_FAILURE_RATE_FOR_WORKING_LINK) {
+            if (mLinkStuckCount > 0) {
+                mLinkStuckCount -= 1;
             }
         }
 
         if (rssi < rssiThreshBad) {
-            if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) {
-                wifiInfo.badRssiCount += 1;
+            if (mBadRssiCount < MAX_BAD_RSSI_COUNT) {
+                mBadRssiCount += 1;
             }
         } else if (rssi < rssiThreshLow) {
-            wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
-            if (wifiInfo.badRssiCount > 0) {
+            mLowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1
+            if (mBadRssiCount > 0) {
                 // Decrement bad Rssi count
-                wifiInfo.badRssiCount -= 1;
+                mBadRssiCount -= 1;
             }
         } else {
-            wifiInfo.badRssiCount = 0;
-            wifiInfo.lowRssiCount = 0;
+            mBadRssiCount = 0;
+            mLowRssiCount = 0;
         }
 
         // Ugh, we need to finish the score calculation while we have wifiInfo
@@ -155,6 +159,9 @@
     @Override
     public void reset() {
         mScore = 0;
+        mBadRssiCount = 0;
+        mLinkStuckCount = 0;
+        mLowRssiCount = 0;
     }
 
     /**
@@ -182,18 +189,19 @@
 
         int linkSpeed = wifiInfo.getLinkSpeed();
 
-        if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
+        if (mLinkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) {
             // Once link gets stuck for more than 3 seconds, start reducing the score
-            score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1);
+            score = score - LINK_STUCK_PENALTY * (mLinkStuckCount - 1);
         }
 
         if (linkSpeed < linkspeedThreshBad) {
             score -= BAD_LINKSPEED_PENALTY;
-        } else if ((linkSpeed >= linkspeedThreshGood) && (wifiInfo.txSuccessRate > 5)) {
+        } else if ((linkSpeed >= linkspeedThreshGood)
+                    && (wifiInfo.txSuccessRate > 1)) {
             score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone doesn't kill us
         }
 
-        score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
+        score -= mBadRssiCount * BAD_RSSI_COUNT_PENALTY + mLowRssiCount;
 
         if (rssi >= rssiThreshSaturated) score += 5;
 
diff --git a/service/java/com/android/server/wifi/SavedNetworkEvaluator.java b/service/java/com/android/server/wifi/SavedNetworkEvaluator.java
index 59d4ab2..97f6b6f 100644
--- a/service/java/com/android/server/wifi/SavedNetworkEvaluator.java
+++ b/service/java/com/android/server/wifi/SavedNetworkEvaluator.java
@@ -25,8 +25,6 @@
 import com.android.internal.R;
 import com.android.server.wifi.util.TelephonyUtil;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -242,96 +240,83 @@
 
         for (ScanDetail scanDetail : scanDetails) {
             ScanResult scanResult = scanDetail.getScanResult();
-            int highestScoreOfScanResult = Integer.MIN_VALUE;
-            int candidateIdOfScanResult = WifiConfiguration.INVALID_NETWORK_ID;
 
             // One ScanResult can be associated with more than one networks, hence we calculate all
             // the scores and use the highest one as the ScanResult's score.
-            List<WifiConfiguration> associatedConfigurations = null;
-            WifiConfiguration associatedConfiguration =
+            WifiConfiguration network =
                     mWifiConfigManager.getConfiguredNetworkForScanDetailAndCache(scanDetail);
 
-            if (associatedConfiguration == null) {
+            if (network == null) {
                 continue;
-            } else {
-                associatedConfigurations =
-                    new ArrayList<>(Arrays.asList(associatedConfiguration));
             }
 
-            for (WifiConfiguration network : associatedConfigurations) {
-                /**
-                 * Ignore Passpoint and Ephemeral networks. They are configured networks,
-                 * but without being persisted to the storage. They are evaluated by
-                 * {@link PasspointNetworkEvaluator} and {@link ScoredNetworkEvaluator}
-                 * respectively.
-                 */
-                if (network.isPasspoint() || network.isEphemeral()) {
-                    continue;
-                }
+            /**
+             * Ignore Passpoint and Ephemeral networks. They are configured networks,
+             * but without being persisted to the storage. They are evaluated by
+             * {@link PasspointNetworkEvaluator} and {@link ScoredNetworkEvaluator}
+             * respectively.
+             */
+            if (network.isPasspoint() || network.isEphemeral()) {
+                continue;
+            }
 
-                WifiConfiguration.NetworkSelectionStatus status =
-                        network.getNetworkSelectionStatus();
-                status.setSeenInLastQualifiedNetworkSelection(true);
+            WifiConfiguration.NetworkSelectionStatus status =
+                    network.getNetworkSelectionStatus();
+            status.setSeenInLastQualifiedNetworkSelection(true);
 
-                if (!status.isNetworkEnabled()) {
-                    continue;
-                } else if (network.BSSID != null &&  !network.BSSID.equals("any")
-                        && !network.BSSID.equals(scanResult.BSSID)) {
-                    // App has specified the only BSSID to connect for this
-                    // configuration. So only the matching ScanResult can be a candidate.
-                    localLog("Network " + WifiNetworkSelector.toNetworkString(network)
-                            + " has specified BSSID " + network.BSSID + ". Skip "
-                            + scanResult.BSSID);
-                    continue;
-                } else if (TelephonyUtil.isSimConfig(network)
-                        && !mWifiConfigManager.isSimPresent()) {
-                    // Don't select if security type is EAP SIM/AKA/AKA' when SIM is not present.
-                    continue;
-                }
+            if (!status.isNetworkEnabled()) {
+                continue;
+            } else if (network.BSSID != null &&  !network.BSSID.equals("any")
+                    && !network.BSSID.equals(scanResult.BSSID)) {
+                // App has specified the only BSSID to connect for this
+                // configuration. So only the matching ScanResult can be a candidate.
+                localLog("Network " + WifiNetworkSelector.toNetworkString(network)
+                        + " has specified BSSID " + network.BSSID + ". Skip "
+                        + scanResult.BSSID);
+                continue;
+            } else if (TelephonyUtil.isSimConfig(network)
+                    && !mWifiConfigManager.isSimPresent()) {
+                // Don't select if security type is EAP SIM/AKA/AKA' when SIM is not present.
+                continue;
+            }
 
-                int score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid,
-                        scoreHistory);
+            int score = calculateBssidScore(scanResult, network, currentNetwork, currentBssid,
+                    scoreHistory);
 
-                // Set candidate ScanResult for all saved networks to ensure that users can
-                // override network selection. See WifiNetworkSelector#setUserConnectChoice.
-                // TODO(b/36067705): consider alternative designs to push filtering/selecting of
-                // user connect choice networks to RecommendedNetworkEvaluator.
-                if (score > status.getCandidateScore() || (score == status.getCandidateScore()
-                        && status.getCandidate() != null
-                        && scanResult.level > status.getCandidate().level)) {
-                    mWifiConfigManager.setNetworkCandidateScanResult(
-                            network.networkId, scanResult, score);
-                }
+            // Set candidate ScanResult for all saved networks to ensure that users can
+            // override network selection. See WifiNetworkSelector#setUserConnectChoice.
+            // TODO(b/36067705): consider alternative designs to push filtering/selecting of
+            // user connect choice networks to RecommendedNetworkEvaluator.
+            if (score > status.getCandidateScore() || (score == status.getCandidateScore()
+                    && status.getCandidate() != null
+                    && scanResult.level > status.getCandidate().level)) {
+                mWifiConfigManager.setNetworkCandidateScanResult(
+                        network.networkId, scanResult, score);
+            }
 
-                // If the network is marked to use external scores, or is an open network with
-                // curate saved open networks enabled, do not consider it for network selection.
-                if (network.useExternalScores) {
-                    localLog("Network " + WifiNetworkSelector.toNetworkString(network)
-                            + " has external score.");
-                    continue;
-                }
-
-                if (score > highestScoreOfScanResult) {
-                    highestScoreOfScanResult = score;
-                    candidateIdOfScanResult = network.networkId;
-                }
+            // If the network is marked to use external scores, or is an open network with
+            // curate saved open networks enabled, do not consider it for network selection.
+            if (network.useExternalScores) {
+                localLog("Network " + WifiNetworkSelector.toNetworkString(network)
+                        + " has external score.");
+                continue;
             }
 
             if (connectableNetworks != null) {
                 connectableNetworks.add(Pair.create(scanDetail,
-                        mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult)));
+                        mWifiConfigManager.getConfiguredNetwork(network.networkId)));
             }
 
-            if (highestScoreOfScanResult > highestScore
-                    || (highestScoreOfScanResult == highestScore
+            if (score > highestScore
+                    || (score == highestScore
                     && scanResultCandidate != null
                     && scanResult.level > scanResultCandidate.level)) {
-                highestScore = highestScoreOfScanResult;
+                highestScore = score;
                 scanResultCandidate = scanResult;
                 mWifiConfigManager.setNetworkCandidateScanResult(
-                        candidateIdOfScanResult, scanResultCandidate, highestScore);
+                        network.networkId, scanResultCandidate, highestScore);
                 // Reload the network config with the updated info.
-                candidate = mWifiConfigManager.getConfiguredNetwork(candidateIdOfScanResult);
+                candidate = mWifiConfigManager.getConfiguredNetwork(network.networkId);
             }
         }
 
diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java
index d4a1ea5..2200618 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -20,23 +20,34 @@
 import static com.android.server.wifi.util.ApConfigUtil.ERROR_NO_CHANNEL;
 import static com.android.server.wifi.util.ApConfigUtil.SUCCESS;
 
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
 import android.net.InterfaceConfiguration;
 import android.net.wifi.IApInterface;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiManager;
+import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.net.BaseNetworkObserver;
+import com.android.server.wifi.WifiNative.SoftApListener;
 import com.android.server.wifi.util.ApConfigUtil;
 
-import java.nio.charset.StandardCharsets;
 import java.util.Locale;
 
 /**
@@ -46,6 +57,15 @@
 public class SoftApManager implements ActiveModeManager {
     private static final String TAG = "SoftApManager";
 
+    // Minimum limit to use for timeout delay if the value from overlay setting is too small.
+    private static final int MIN_SOFT_AP_TIMEOUT_DELAY_MS = 600_000;  // 10 minutes
+
+    @VisibleForTesting
+    public static final String SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG = TAG
+            + " Soft AP Send Message Timeout";
+
+    private final Context mContext;
+    private final FrameworkFacade mFrameworkFacade;
     private final WifiNative mWifiNative;
 
     private final String mCountryCode;
@@ -55,15 +75,28 @@
     private final Listener mListener;
 
     private final IApInterface mApInterface;
+    private final String mApInterfaceName;
 
     private final INetworkManagementService mNwService;
     private final WifiApConfigStore mWifiApConfigStore;
 
     private final WifiMetrics mWifiMetrics;
 
+    private final int mMode;
     private WifiConfiguration mApConfig;
 
     /**
+     * Listener for soft AP events.
+     */
+    private final SoftApListener mSoftApListener = new SoftApListener() {
+        @Override
+        public void onNumAssociatedStationsChanged(int numStations) {
+            mStateMachine.sendMessage(
+                    SoftApStateMachine.CMD_NUM_ASSOCIATED_STATIONS_CHANGED, numStations);
+        }
+    };
+
+    /**
      * Listener for soft AP state changes.
      */
     public interface Listener {
@@ -75,29 +108,36 @@
         void onStateChanged(int state, int failureReason);
     }
 
-    public SoftApManager(Looper looper,
+    public SoftApManager(Context context,
+                         Looper looper,
+                         FrameworkFacade framework,
                          WifiNative wifiNative,
                          String countryCode,
                          Listener listener,
-                         IApInterface apInterface,
+                         @NonNull IApInterface apInterface,
+                         @NonNull String ifaceName,
                          INetworkManagementService nms,
                          WifiApConfigStore wifiApConfigStore,
-                         WifiConfiguration config,
+                         @NonNull SoftApModeConfiguration apConfig,
                          WifiMetrics wifiMetrics) {
-        mStateMachine = new SoftApStateMachine(looper);
-
+        mContext = context;
+        mFrameworkFacade = framework;
         mWifiNative = wifiNative;
         mCountryCode = countryCode;
         mListener = listener;
         mApInterface = apInterface;
+        mApInterfaceName = ifaceName;
         mNwService = nms;
         mWifiApConfigStore = wifiApConfigStore;
+        mMode = apConfig.getTargetMode();
+        WifiConfiguration config = apConfig.getWifiConfiguration();
         if (config == null) {
             mApConfig = mWifiApConfigStore.getApConfiguration();
         } else {
             mApConfig = config;
         }
         mWifiMetrics = wifiMetrics;
+        mStateMachine = new SoftApStateMachine(looper);
     }
 
     /**
@@ -116,13 +156,28 @@
 
     /**
      * Update AP state.
-     * @param state new AP state
+     * @param newState new AP state
+     * @param currentState current AP state
      * @param reason Failure reason if the new AP state is in failure state
      */
-    private void updateApState(int state, int reason) {
+    private void updateApState(int newState, int currentState, int reason) {
         if (mListener != null) {
-            mListener.onStateChanged(state, reason);
+            mListener.onStateChanged(newState, reason);
         }
+
+        //send the AP state change broadcast
+        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, newState);
+        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, currentState);
+        if (newState == WifiManager.WIFI_AP_STATE_FAILED) {
+            //only set reason number when softAP start failed
+            intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason);
+        }
+
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, mApInterfaceName);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mMode);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     /**
@@ -142,6 +197,7 @@
         int result = ApConfigUtil.updateApChannelConfig(
                 mWifiNative, mCountryCode,
                 mWifiApConfigStore.getAllowed2GChannel(), localConfig);
+
         if (result != SUCCESS) {
             Log.e(TAG, "Failed to update AP band and channel");
             return result;
@@ -158,71 +214,24 @@
                 return ERROR_GENERIC;
             }
         }
-
-        int encryptionType = getIApInterfaceEncryptionType(localConfig);
-
         if (localConfig.hiddenSSID) {
             Log.d(TAG, "SoftAP is a hidden network");
         }
-
-        try {
-            // Note that localConfig.SSID is intended to be either a hex string or "double quoted".
-            // However, it seems that whatever is handing us these configurations does not obey
-            // this convention.
-            boolean success = mApInterface.writeHostapdConfig(
-                    localConfig.SSID.getBytes(StandardCharsets.UTF_8), localConfig.hiddenSSID,
-                    localConfig.apChannel, encryptionType,
-                    (localConfig.preSharedKey != null)
-                            ? localConfig.preSharedKey.getBytes(StandardCharsets.UTF_8)
-                            : new byte[0]);
-            if (!success) {
-                Log.e(TAG, "Failed to write hostapd configuration");
-                return ERROR_GENERIC;
-            }
-
-            success = mApInterface.startHostapd();
-            if (!success) {
-                Log.e(TAG, "Failed to start hostapd.");
-                return ERROR_GENERIC;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Exception in starting soft AP: " + e);
+        if (!mWifiNative.startSoftAp(localConfig, mSoftApListener)) {
+            Log.e(TAG, "Soft AP start failed");
+            return ERROR_GENERIC;
         }
-
         Log.d(TAG, "Soft AP is started");
 
         return SUCCESS;
     }
 
-    private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) {
-        int encryptionType;
-        switch (localConfig.getAuthType()) {
-            case KeyMgmt.NONE:
-                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
-                break;
-            case KeyMgmt.WPA_PSK:
-                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA;
-                break;
-            case KeyMgmt.WPA2_PSK:
-                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2;
-                break;
-            default:
-                // We really shouldn't default to None, but this was how NetworkManagementService
-                // used to do this.
-                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
-                break;
-        }
-        return encryptionType;
-    }
-
     /**
      * Teardown soft AP.
      */
     private void stopSoftAp() {
-        try {
-            mApInterface.stopHostapd();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Exception in stopping soft AP: " + e);
+        if (!mWifiNative.stopSoftAp()) {
+            Log.d(TAG, "Soft AP stop failed");
             return;
         }
         Log.d(TAG, "Soft AP is stopped");
@@ -232,14 +241,18 @@
         // Commands for the state machine.
         public static final int CMD_START = 0;
         public static final int CMD_STOP = 1;
-        public static final int CMD_AP_INTERFACE_BINDER_DEATH = 2;
+        public static final int CMD_WIFICOND_BINDER_DEATH = 2;
         public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
+        public static final int CMD_NUM_ASSOCIATED_STATIONS_CHANGED = 4;
+        public static final int CMD_NO_ASSOCIATED_STATIONS_TIMEOUT = 5;
+        public static final int CMD_TIMEOUT_TOGGLE_CHANGED = 6;
 
         private final State mIdleState = new IdleState();
         private final State mStartedState = new StartedState();
 
-        private final StateMachineDeathRecipient mDeathRecipient =
-                new StateMachineDeathRecipient(this, CMD_AP_INTERFACE_BINDER_DEATH);
+        private final WifiNative.WificondDeathEventHandler mWificondDeathRecipient = () -> {
+            sendMessage(CMD_WIFICOND_BINDER_DEATH);
+        };
 
         private NetworkObserver mNetworkObserver;
 
@@ -272,7 +285,7 @@
         private class IdleState extends State {
             @Override
             public void enter() {
-                mDeathRecipient.unlinkToDeath();
+                mWifiNative.deregisterWificondDeathHandler();
                 unregisterObserver();
             }
 
@@ -280,23 +293,35 @@
             public boolean processMessage(Message message) {
                 switch (message.what) {
                     case CMD_START:
-                        updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 0);
-                        if (!mDeathRecipient.linkToDeath(mApInterface.asBinder())) {
-                            mDeathRecipient.unlinkToDeath();
+                        // first a sanity check on the interface name.  If we failed to retrieve it,
+                        // we are going to have a hard time setting up routing.
+                        if (TextUtils.isEmpty(mApInterfaceName)) {
+                            Log.e(TAG, "Not starting softap mode without an interface name.");
                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                          WifiManager.WIFI_AP_STATE_DISABLED,
+                                          WifiManager.SAP_START_FAILURE_GENERAL);
+                            mWifiMetrics.incrementSoftApStartResult(
+                                    false, WifiManager.SAP_START_FAILURE_GENERAL);
+                            break;
+                        }
+                        updateApState(WifiManager.WIFI_AP_STATE_ENABLING,
+                                WifiManager.WIFI_AP_STATE_DISABLED, 0);
+                        if (!mWifiNative.registerWificondDeathHandler(mWificondDeathRecipient)) {
+                            updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                    WifiManager.WIFI_AP_STATE_ENABLING,
                                     WifiManager.SAP_START_FAILURE_GENERAL);
                             mWifiMetrics.incrementSoftApStartResult(
                                     false, WifiManager.SAP_START_FAILURE_GENERAL);
                             break;
                         }
-
                         try {
-                            mNetworkObserver = new NetworkObserver(mApInterface.getInterfaceName());
+                            mNetworkObserver = new NetworkObserver(mApInterfaceName);
                             mNwService.registerObserver(mNetworkObserver);
                         } catch (RemoteException e) {
-                            mDeathRecipient.unlinkToDeath();
+                            mWifiNative.deregisterWificondDeathHandler();
                             unregisterObserver();
                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                          WifiManager.WIFI_AP_STATE_ENABLING,
                                           WifiManager.SAP_START_FAILURE_GENERAL);
                             mWifiMetrics.incrementSoftApStartResult(
                                     false, WifiManager.SAP_START_FAILURE_GENERAL);
@@ -309,9 +334,11 @@
                             if (result == ERROR_NO_CHANNEL) {
                                 failureReason = WifiManager.SAP_START_FAILURE_NO_CHANNEL;
                             }
-                            mDeathRecipient.unlinkToDeath();
+                            mWifiNative.deregisterWificondDeathHandler();
                             unregisterObserver();
-                            updateApState(WifiManager.WIFI_AP_STATE_FAILED, failureReason);
+                            updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                          WifiManager.WIFI_AP_STATE_ENABLING,
+                                          failureReason);
                             mWifiMetrics.incrementSoftApStartResult(false, failureReason);
                             break;
                         }
@@ -340,6 +367,92 @@
         private class StartedState extends State {
             private boolean mIfaceIsUp;
 
+            private int mNumAssociatedStations;
+
+            private boolean mTimeoutEnabled;
+            private int mTimeoutDelay;
+            private WakeupMessage mSoftApTimeoutMessage;
+            private SoftApTimeoutEnabledSettingObserver mSettingObserver;
+
+            /**
+            * Observer for timeout settings changes.
+            */
+            private class SoftApTimeoutEnabledSettingObserver extends ContentObserver {
+                SoftApTimeoutEnabledSettingObserver(Handler handler) {
+                    super(handler);
+                }
+
+                public void register() {
+                    mFrameworkFacade.registerContentObserver(mContext,
+                            Settings.Global.getUriFor(Settings.Global.SOFT_AP_TIMEOUT_ENABLED),
+                            true, this);
+                    mTimeoutEnabled = getValue();
+                }
+
+                public void unregister() {
+                    mFrameworkFacade.unregisterContentObserver(mContext, this);
+                }
+
+                @Override
+                public void onChange(boolean selfChange) {
+                    super.onChange(selfChange);
+                    mStateMachine.sendMessage(SoftApStateMachine.CMD_TIMEOUT_TOGGLE_CHANGED,
+                            getValue() ? 1 : 0);
+                }
+
+                private boolean getValue() {
+                    boolean enabled = mFrameworkFacade.getIntegerSetting(mContext,
+                            Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1) == 1;
+                    return enabled;
+                }
+            }
+
+            private int getConfigSoftApTimeoutDelay() {
+                int delay = mContext.getResources().getInteger(
+                        R.integer.config_wifi_framework_soft_ap_timeout_delay);
+                if (delay < MIN_SOFT_AP_TIMEOUT_DELAY_MS) {
+                    delay = MIN_SOFT_AP_TIMEOUT_DELAY_MS;
+                    Log.w(TAG, "Overriding timeout delay with minimum limit value");
+                }
+                Log.d(TAG, "Timeout delay: " + delay);
+                return delay;
+            }
+
+            private void scheduleTimeoutMessage() {
+                if (!mTimeoutEnabled) {
+                    return;
+                }
+                mSoftApTimeoutMessage.schedule(SystemClock.elapsedRealtime() + mTimeoutDelay);
+                Log.d(TAG, "Timeout message scheduled");
+            }
+
+            private void cancelTimeoutMessage() {
+                mSoftApTimeoutMessage.cancel();
+                Log.d(TAG, "Timeout message canceled");
+            }
+
+            /**
+             * Set number of stations associated with this soft AP
+             * @param numStations Number of connected stations
+             */
+            private void setNumAssociatedStations(int numStations) {
+                if (mNumAssociatedStations == numStations) {
+                    return;
+                }
+                mNumAssociatedStations = numStations;
+                Log.d(TAG, "Number of associated stations changed: " + mNumAssociatedStations);
+
+                // TODO:(b/63906412) send it up to settings.
+                mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(mNumAssociatedStations,
+                        mMode);
+
+                if (mNumAssociatedStations == 0) {
+                    scheduleTimeoutMessage();
+                } else {
+                    cancelTimeoutMessage();
+                }
+            }
+
             private void onUpChanged(boolean isUp) {
                 if (isUp == mIfaceIsUp) {
                     return;  // no change
@@ -347,11 +460,13 @@
                 mIfaceIsUp = isUp;
                 if (isUp) {
                     Log.d(TAG, "SoftAp is ready for use");
-                    updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 0);
+                    updateApState(WifiManager.WIFI_AP_STATE_ENABLED,
+                            WifiManager.WIFI_AP_STATE_ENABLING, 0);
                     mWifiMetrics.incrementSoftApStartResult(true, 0);
                 } else {
                     // TODO: handle the case where the interface was up, but goes down
                 }
+                mWifiMetrics.addSoftApUpChangedEvent(isUp, mMode);
             }
 
             @Override
@@ -359,17 +474,62 @@
                 mIfaceIsUp = false;
                 InterfaceConfiguration config = null;
                 try {
-                    config = mNwService.getInterfaceConfig(mApInterface.getInterfaceName());
+                    config = mNwService.getInterfaceConfig(mApInterfaceName);
                 } catch (RemoteException e) {
                 }
                 if (config != null) {
                     onUpChanged(config.isUp());
                 }
+
+                mTimeoutDelay = getConfigSoftApTimeoutDelay();
+                Handler handler = mStateMachine.getHandler();
+                mSoftApTimeoutMessage = new WakeupMessage(mContext, handler,
+                        SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG,
+                        SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT);
+                mSettingObserver = new SoftApTimeoutEnabledSettingObserver(handler);
+
+                if (mSettingObserver != null) {
+                    mSettingObserver.register();
+                }
+                Log.d(TAG, "Resetting num stations on start");
+                mNumAssociatedStations = 0;
+                scheduleTimeoutMessage();
+            }
+
+            @Override
+            public void exit() {
+                if (mSettingObserver != null) {
+                    mSettingObserver.unregister();
+                }
+                Log.d(TAG, "Resetting num stations on stop");
+                mNumAssociatedStations = 0;
+                cancelTimeoutMessage();
             }
 
             @Override
             public boolean processMessage(Message message) {
                 switch (message.what) {
+                    case CMD_NUM_ASSOCIATED_STATIONS_CHANGED:
+                        if (message.arg1 < 0) {
+                            Log.e(TAG, "Invalid number of associated stations: " + message.arg1);
+                            break;
+                        }
+                        Log.d(TAG, "Setting num stations on CMD_NUM_ASSOCIATED_STATIONS_CHANGED");
+                        setNumAssociatedStations(message.arg1);
+                        break;
+                    case CMD_TIMEOUT_TOGGLE_CHANGED:
+                        boolean isEnabled = (message.arg1 == 1);
+                        if (mTimeoutEnabled == isEnabled) {
+                            break;
+                        }
+                        mTimeoutEnabled = isEnabled;
+                        if (!mTimeoutEnabled) {
+                            cancelTimeoutMessage();
+                        }
+                        if (mTimeoutEnabled && mNumAssociatedStations == 0) {
+                            scheduleTimeoutMessage();
+                        }
+                        break;
                     case CMD_INTERFACE_STATUS_CHANGED:
                         if (message.obj != mNetworkObserver) {
                             // This is from some time before the most recent configuration.
@@ -381,17 +541,35 @@
                     case CMD_START:
                         // Already started, ignore this command.
                         break;
-                    case CMD_AP_INTERFACE_BINDER_DEATH:
+                    case CMD_NO_ASSOCIATED_STATIONS_TIMEOUT:
+                        if (!mTimeoutEnabled) {
+                            Log.wtf(TAG, "Timeout message received while timeout is disabled."
+                                    + " Dropping.");
+                            break;
+                        }
+                        if (mNumAssociatedStations != 0) {
+                            Log.wtf(TAG, "Timeout message received but has clients. Dropping.");
+                            break;
+                        }
+                        Log.i(TAG, "Timeout message received. Stopping soft AP.");
+                    case CMD_WIFICOND_BINDER_DEATH:
                     case CMD_STOP:
-                        updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 0);
+                        updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
+                                WifiManager.WIFI_AP_STATE_ENABLED, 0);
                         stopSoftAp();
-                        if (message.what == CMD_AP_INTERFACE_BINDER_DEATH) {
+                        if (message.what == CMD_WIFICOND_BINDER_DEATH) {
                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
-                                    WifiManager.SAP_START_FAILURE_GENERAL);
+                                          WifiManager.WIFI_AP_STATE_DISABLING,
+                                          WifiManager.SAP_START_FAILURE_GENERAL);
                         } else {
-                            updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 0);
+                            updateApState(WifiManager.WIFI_AP_STATE_DISABLED,
+                                    WifiManager.WIFI_AP_STATE_DISABLING, 0);
                         }
                         transitionTo(mIdleState);
+
+                        // Need this here since we are exiting |Started| state and won't handle any
+                        // future CMD_INTERFACE_STATUS_CHANGED events after this point
+                        mWifiMetrics.addSoftApUpChangedEvent(false, mMode);
                         break;
                     default:
                         return NOT_HANDLED;
@@ -399,6 +577,5 @@
                 return HANDLED;
             }
         }
-
     }
 }
diff --git a/service/java/com/android/server/wifi/StateMachineDeathRecipient.java b/service/java/com/android/server/wifi/StateMachineDeathRecipient.java
deleted file mode 100644
index 64c4bee..0000000
--- a/service/java/com/android/server/wifi/StateMachineDeathRecipient.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 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.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.RemoteException;
-import com.android.internal.util.StateMachine;
-
-/**
- * Allows StateMachine instances to subscribe to binder death.
- *
- * @hide
- */
-public class StateMachineDeathRecipient implements DeathRecipient {
-
-    private final StateMachine mStateMachine;
-    private final int mDeathCommand;
-    private IBinder mLinkedBinder;
-
-    /**
-     * Construct a StateMachineDeathRecipient.
-     *
-     * @param sm StateMachine instance to receive a message upon Binder death.
-     * @param command message to send the state machine.
-     */
-    public StateMachineDeathRecipient(StateMachine sm, int command) {
-        mStateMachine = sm;
-        mDeathCommand = command;
-    }
-
-    /**
-     * Listen for the death of a binder.
-     *
-     * This method will unlink from death notifications from any
-     * previously linked IBinder instance.
-     *
-     * @param binder remote object to listen for death.
-     * @return true iff we have successfully subscribed to death notifications of a live
-     *         IBinder instance.
-     */
-    public boolean linkToDeath(IBinder binder) {
-        unlinkToDeath();
-        try {
-            binder.linkToDeath(this, 0);
-        } catch (RemoteException e) {
-            // The remote has already died.
-            return false;
-        }
-        mLinkedBinder = binder;
-        return true;
-    }
-
-    /**
-     * Unlink from notifications from the last linked IBinder instance.
-     */
-    public void unlinkToDeath() {
-        if (mLinkedBinder == null) {
-            return;
-        }
-        mLinkedBinder.unlinkToDeath(this, 0);
-        mLinkedBinder = null;
-    }
-
-    /**
-     * Called by the binder subsystem upon remote object death.
-     */
-    @Override
-    public void binderDied() {
-        mStateMachine.sendMessage(mDeathCommand);
-    }
-}
\ No newline at end of file
diff --git a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
index d2182fc..664b052 100644
--- a/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
+++ b/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
@@ -53,6 +53,7 @@
 import android.util.Pair;
 import android.util.SparseArray;
 
+import com.android.server.wifi.WifiNative.SupplicantDeathEventHandler;
 import com.android.server.wifi.hotspot2.AnqpEvent;
 import com.android.server.wifi.hotspot2.IconEvent;
 import com.android.server.wifi.hotspot2.WnmData;
@@ -69,6 +70,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -96,8 +98,15 @@
     // Supplicant HAL interface objects
     private IServiceManager mIServiceManager = null;
     private ISupplicant mISupplicant;
-    private ISupplicantStaIface mISupplicantStaIface;
-    private ISupplicantStaIfaceCallback mISupplicantStaIfaceCallback;
+    private HashMap<String, ISupplicantStaIface> mISupplicantStaIfaces = new HashMap<>();
+    private HashMap<String, ISupplicantStaIfaceCallback> mISupplicantStaIfaceCallbacks =
+            new HashMap<>();
+    private HashMap<String, SupplicantStaNetworkHal> mCurrentNetworkRemoteHandles = new HashMap<>();
+    private HashMap<String, WifiConfiguration> mCurrentNetworkLocalConfigs = new HashMap<>();
+    private SupplicantDeathEventHandler mDeathEventHandler;
+    private final Context mContext;
+    private final WifiMonitor mWifiMonitor;
+
     private final IServiceNotification mServiceNotificationCallback =
             new IServiceNotification.Stub() {
         public void onRegistration(String fqName, String name, boolean preexisting) {
@@ -106,11 +115,11 @@
                     Log.i(TAG, "IServiceNotification.onRegistration for: " + fqName
                             + ", " + name + " preexisting=" + preexisting);
                 }
-                if (!initSupplicantService() || !initSupplicantStaIface()) {
-                    Log.e(TAG, "initalizing ISupplicantIfaces failed.");
+                if (!initSupplicantService()) {
+                    Log.e(TAG, "initalizing ISupplicant failed.");
                     supplicantServiceDiedHandler();
                 } else {
-                    Log.i(TAG, "Completed initialization of ISupplicant interfaces.");
+                    Log.i(TAG, "Completed initialization of ISupplicant.");
                 }
             }
         }
@@ -131,16 +140,10 @@
                 }
             };
 
-    private String mIfaceName;
-    private SupplicantStaNetworkHal mCurrentNetworkRemoteHandle;
-    private WifiConfiguration mCurrentNetworkLocalConfig;
-    private final Context mContext;
-    private final WifiMonitor mWifiMonitor;
 
     public SupplicantStaIfaceHal(Context context, WifiMonitor monitor) {
         mContext = context;
         mWifiMonitor = monitor;
-        mISupplicantStaIfaceCallback = new SupplicantStaIfaceHalCallback();
     }
 
     /**
@@ -183,7 +186,7 @@
                 Log.i(TAG, "Registering ISupplicant service ready callback.");
             }
             mISupplicant = null;
-            mISupplicantStaIface = null;
+            mISupplicantStaIfaces.clear();
             if (mIServiceManager != null) {
                 // Already have an IServiceManager and serviceNotification registered, don't
                 // don't register another.
@@ -252,11 +255,11 @@
         return true;
     }
 
-    private boolean linkToSupplicantStaIfaceDeath() {
+    private boolean linkToSupplicantStaIfaceDeath(ISupplicantStaIface iface) {
         synchronized (mLock) {
-            if (mISupplicantStaIface == null) return false;
+            if (iface == null) return false;
             try {
-                if (!mISupplicantStaIface.linkToDeath(mSupplicantDeathRecipient, 0)) {
+                if (!iface.linkToDeath(mSupplicantDeathRecipient, 0)) {
                     Log.wtf(TAG, "Error on linkToDeath on ISupplicantStaIface");
                     supplicantServiceDiedHandler();
                     return false;
@@ -269,16 +272,23 @@
         }
     }
 
-    private int getCurrentNetworkId() {
+    private int getCurrentNetworkId(@NonNull String ifaceName) {
         synchronized (mLock) {
-            if (mCurrentNetworkLocalConfig == null) {
+            WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName);
+            if (currentConfig == null) {
                 return WifiConfiguration.INVALID_NETWORK_ID;
             }
-            return mCurrentNetworkLocalConfig.networkId;
+            return currentConfig.networkId;
         }
     }
 
-    private boolean initSupplicantStaIface() {
+    /**
+     * Setup a STA interface for the specified iface name.
+     *
+     * @param ifaceName Name of the interface.
+     * @return true on success, false otherwise.
+     */
+    public boolean setupIface(@NonNull String ifaceName) {
         synchronized (mLock) {
             /** List all supplicant Ifaces */
             final ArrayList<ISupplicant.IfaceInfo> supplicantIfaces = new ArrayList<>();
@@ -293,6 +303,7 @@
                 });
             } catch (RemoteException e) {
                 Log.e(TAG, "ISupplicant.listInterfaces exception: " + e);
+                supplicantServiceDiedHandler(ifaceName);
                 return false;
             }
             if (supplicantIfaces.size() == 0) {
@@ -300,9 +311,8 @@
                 return false;
             }
             Mutable<ISupplicantIface> supplicantIface = new Mutable<>();
-            Mutable<String> ifaceName = new Mutable<>();
             for (ISupplicant.IfaceInfo ifaceInfo : supplicantIfaces) {
-                if (ifaceInfo.type == IfaceType.STA) {
+                if (ifaceInfo.type == IfaceType.STA && ifaceName.equals(ifaceInfo.name)) {
                     try {
                         mISupplicant.getInterface(ifaceInfo,
                                 (SupplicantStatus status, ISupplicantIface iface) -> {
@@ -314,33 +324,99 @@
                             });
                     } catch (RemoteException e) {
                         Log.e(TAG, "ISupplicant.getInterface exception: " + e);
+                        supplicantServiceDiedHandler(ifaceName);
                         return false;
                     }
-                    ifaceName.value = ifaceInfo.name;
                     break;
                 }
             }
             if (supplicantIface.value == null) {
-                Log.e(TAG, "initSupplicantStaIface got null iface");
+                Log.e(TAG, "setupSupplicantStaIface got null iface");
                 return false;
             }
-            mISupplicantStaIface = getStaIfaceMockable(supplicantIface.value);
-            mIfaceName = ifaceName.value;
-            if (!linkToSupplicantStaIfaceDeath()) {
+            ISupplicantStaIface iface = getStaIfaceMockable(supplicantIface.value);
+            if (!linkToSupplicantStaIfaceDeath(iface)) {
                 return false;
             }
-            if (!registerCallback(mISupplicantStaIfaceCallback)) {
+            ISupplicantStaIfaceCallback callback = new SupplicantStaIfaceHalCallback(ifaceName);
+            if (!registerCallback(iface, callback)) {
                 return false;
             }
+            mISupplicantStaIfaces.put(ifaceName, iface);
+            mISupplicantStaIfaceCallbacks.put(ifaceName, callback);
             return true;
         }
     }
 
+    /**
+     * Registers a death notification for supplicant.
+     * @return Returns true on success.
+     */
+    public boolean registerDeathHandler(@NonNull SupplicantDeathEventHandler handler) {
+        if (mDeathEventHandler != null) {
+            Log.e(TAG, "Death handler already present");
+            return false;
+        }
+        mDeathEventHandler = handler;
+        return true;
+    }
+
+    /**
+     * Deregisters a death notification for supplicant.
+     * @return Returns true on success.
+     */
+    public boolean deregisterDeathHandler() {
+        if (mDeathEventHandler == null) {
+            Log.e(TAG, "No Death handler present");
+            return false;
+        }
+        mDeathEventHandler = null;
+        return true;
+    }
+
+    /**
+     * Teardown a STA interface for the specified iface name.
+     *
+     * @param ifaceName Name of the interface.
+     * @return true on success, false otherwise.
+     */
+    public boolean teardownIface(@NonNull String ifaceName) {
+        synchronized (mLock) {
+            if (mISupplicantStaIfaces.remove(ifaceName) == null) {
+                return false;
+            }
+            mISupplicantStaIfaceCallbacks.remove(ifaceName);
+            // TODO(b/69162019): There are no HIDL calls to bring down interfaces
+            // from wpa_supplicant yet!
+            return true;
+        }
+    }
+
+    private void clearState() {
+        synchronized (mLock) {
+            mISupplicant = null;
+            mISupplicantStaIfaces.clear();
+            mCurrentNetworkLocalConfigs.clear();
+            mCurrentNetworkRemoteHandles.clear();
+        }
+    }
+
+    private void supplicantServiceDiedHandler(@NonNull String ifaceName) {
+        synchronized (mLock) {
+            mWifiMonitor.broadcastSupplicantDisconnectionEvent(ifaceName);
+            clearState();
+        }
+    }
+
     private void supplicantServiceDiedHandler() {
         synchronized (mLock) {
-            mISupplicant = null;
-            mISupplicantStaIface = null;
-            mWifiMonitor.broadcastSupplicantDisconnectionEvent(mIfaceName);
+            if (mDeathEventHandler != null) {
+                mDeathEventHandler.onDeath();
+            }
+            for (String ifaceName : mISupplicantStaIfaces.keySet()) {
+                mWifiMonitor.broadcastSupplicantDisconnectionEvent(ifaceName);
+            }
+            clearState();
         }
     }
 
@@ -358,7 +434,7 @@
      */
     public boolean isInitializationComplete() {
         synchronized (mLock) {
-            return mISupplicantStaIface != null;
+            return mISupplicant != null;
         }
     }
 
@@ -384,6 +460,27 @@
     }
 
     /**
+     * Helper method to look up the network object for the specified iface.
+     */
+    private ISupplicantStaIface getStaIface(@NonNull String ifaceName) {
+        return mISupplicantStaIfaces.get(ifaceName);
+    }
+
+    /**
+     * Helper method to look up the network object for the specified iface.
+     */
+    private SupplicantStaNetworkHal getCurrentNetworkRemoteHandle(@NonNull String ifaceName) {
+        return mCurrentNetworkRemoteHandles.get(ifaceName);
+    }
+
+    /**
+     * Helper method to look up the network config or the specified iface.
+     */
+    private WifiConfiguration getCurrentNetworkLocalConfig(@NonNull String ifaceName) {
+        return mCurrentNetworkLocalConfigs.get(ifaceName);
+    }
+
+    /**
      * Add a network configuration to wpa_supplicant.
      *
      * @param config Config corresponding to the network.
@@ -391,14 +488,14 @@
      * for the current network.
      */
     private Pair<SupplicantStaNetworkHal, WifiConfiguration>
-            addNetworkAndSaveConfig(WifiConfiguration config) {
+            addNetworkAndSaveConfig(@NonNull String ifaceName, WifiConfiguration config) {
         synchronized (mLock) {
             logi("addSupplicantStaNetwork via HIDL");
             if (config == null) {
                 loge("Cannot add NULL network!");
                 return null;
             }
-            SupplicantStaNetworkHal network = addNetwork();
+            SupplicantStaNetworkHal network = addNetwork(ifaceName);
             if (network == null) {
                 loge("Failed to add a network!");
                 return null;
@@ -411,7 +508,7 @@
             }
             if (!saveSuccess) {
                 loge("Failed to save variables for: " + config.configKey());
-                if (!removeAllNetworks()) {
+                if (!removeAllNetworks(ifaceName)) {
                     loge("Failed to remove all networks on failure.");
                 }
                 return null;
@@ -427,32 +524,50 @@
      * networks and saves |config|.
      * 2. Select the new network in wpa_supplicant.
      *
+     * @param ifaceName Name of the interface.
      * @param config WifiConfiguration parameters for the provided network.
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
-    public boolean connectToNetwork(@NonNull WifiConfiguration config) {
+    public boolean connectToNetwork(@NonNull String ifaceName, @NonNull WifiConfiguration config) {
         synchronized (mLock) {
             logd("connectToNetwork " + config.configKey());
-            if (WifiConfigurationUtil.isSameNetwork(config, mCurrentNetworkLocalConfig)) {
-                logd("Network is already saved, will not trigger remove and add operation.");
+            WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName);
+            if (WifiConfigurationUtil.isSameNetwork(config, currentConfig)) {
+                String networkSelectionBSSID = config.getNetworkSelectionStatus()
+                        .getNetworkSelectionBSSID();
+                String networkSelectionBSSIDCurrent =
+                        currentConfig.getNetworkSelectionStatus().getNetworkSelectionBSSID();
+                if (Objects.equals(networkSelectionBSSID, networkSelectionBSSIDCurrent)) {
+                    logd("Network is already saved, will not trigger remove and add operation.");
+                } else {
+                    logd("Network is already saved, but need to update BSSID.");
+                    if (!setCurrentNetworkBssid(
+                            ifaceName,
+                            config.getNetworkSelectionStatus().getNetworkSelectionBSSID())) {
+                        loge("Failed to set current network BSSID.");
+                        return false;
+                    }
+                    mCurrentNetworkLocalConfigs.put(ifaceName, new WifiConfiguration(config));
+                }
             } else {
-                mCurrentNetworkRemoteHandle = null;
-                mCurrentNetworkLocalConfig = null;
-                if (!removeAllNetworks()) {
+                mCurrentNetworkRemoteHandles.remove(ifaceName);
+                mCurrentNetworkLocalConfigs.remove(ifaceName);
+                if (!removeAllNetworks(ifaceName)) {
                     loge("Failed to remove existing networks");
                     return false;
                 }
                 Pair<SupplicantStaNetworkHal, WifiConfiguration> pair =
-                        addNetworkAndSaveConfig(config);
+                        addNetworkAndSaveConfig(ifaceName, config);
                 if (pair == null) {
                     loge("Failed to add/save network configuration: " + config.configKey());
                     return false;
                 }
-                mCurrentNetworkRemoteHandle = pair.first;
-                mCurrentNetworkLocalConfig = pair.second;
+                mCurrentNetworkRemoteHandles.put(ifaceName, pair.first);
+                mCurrentNetworkLocalConfigs.put(ifaceName, pair.second);
             }
-
-            if (!mCurrentNetworkRemoteHandle.select()) {
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(ifaceName, "connectToNetwork");
+            if (networkHandle == null || !networkHandle.select()) {
                 loge("Failed to select network configuration: " + config.configKey());
                 return false;
             }
@@ -469,23 +584,27 @@
      * 2. Set the new bssid for the network in wpa_supplicant.
      * 3. Trigger reassociate command to wpa_supplicant.
      *
+     * @param ifaceName Name of the interface.
      * @param config WifiConfiguration parameters for the provided network.
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
-    public boolean roamToNetwork(WifiConfiguration config) {
+    public boolean roamToNetwork(@NonNull String ifaceName, WifiConfiguration config) {
         synchronized (mLock) {
-            if (getCurrentNetworkId() != config.networkId) {
+            if (getCurrentNetworkId(ifaceName) != config.networkId) {
                 Log.w(TAG, "Cannot roam to a different network, initiate new connection. "
-                        + "Current network ID: " + getCurrentNetworkId());
-                return connectToNetwork(config);
+                        + "Current network ID: " + getCurrentNetworkId(ifaceName));
+                return connectToNetwork(ifaceName, config);
             }
             String bssid = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
             logd("roamToNetwork" + config.configKey() + " (bssid " + bssid + ")");
-            if (!mCurrentNetworkRemoteHandle.setBssid(bssid)) {
+
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(ifaceName, "roamToNetwork");
+            if (networkHandle == null || !networkHandle.setBssid(bssid)) {
                 loge("Failed to set new bssid on network: " + config.configKey());
                 return false;
             }
-            if (!reassociate()) {
+            if (!reassociate(ifaceName)) {
                 loge("Failed to trigger reassociate");
                 return false;
             }
@@ -496,21 +615,22 @@
     /**
      * Load all the configured networks from wpa_supplicant.
      *
+     * @param ifaceName     Name of the interface.
      * @param configs       Map of configuration key to configuration objects corresponding to all
      *                      the networks.
      * @param networkExtras Map of extra configuration parameters stored in wpa_supplicant.conf
      * @return true if succeeds, false otherwise.
      */
-    public boolean loadNetworks(Map<String, WifiConfiguration> configs,
+    public boolean loadNetworks(@NonNull String ifaceName, Map<String, WifiConfiguration> configs,
                                 SparseArray<Map<String, String>> networkExtras) {
         synchronized (mLock) {
-            List<Integer> networkIds = listNetworks();
+            List<Integer> networkIds = listNetworks(ifaceName);
             if (networkIds == null) {
                 Log.e(TAG, "Failed to list networks");
                 return false;
             }
             for (Integer networkId : networkIds) {
-                SupplicantStaNetworkHal network = getNetwork(networkId);
+                SupplicantStaNetworkHal network = getNetwork(ifaceName, networkId);
                 if (network == null) {
                     Log.e(TAG, "Failed to get network with ID: " + networkId);
                     return false;
@@ -539,7 +659,7 @@
                 if (duplicateConfig != null) {
                     // The network is already known. Overwrite the duplicate entry.
                     Log.i(TAG, "Replacing duplicate network: " + duplicateConfig.networkId);
-                    removeNetwork(duplicateConfig.networkId);
+                    removeNetwork(ifaceName, duplicateConfig.networkId);
                     networkExtras.remove(duplicateConfig.networkId);
                 }
             }
@@ -551,37 +671,40 @@
      * Remove the request |networkId| from supplicant if it's the current network,
      * if the current configured network matches |networkId|.
      *
+     * @param ifaceName Name of the interface.
      * @param networkId network id of the network to be removed from supplicant.
      */
-    public void removeNetworkIfCurrent(int networkId) {
+    public void removeNetworkIfCurrent(@NonNull String ifaceName, int networkId) {
         synchronized (mLock) {
-            if (getCurrentNetworkId() == networkId) {
+            if (getCurrentNetworkId(ifaceName) == networkId) {
                 // Currently we only save 1 network in supplicant.
-                removeAllNetworks();
+                removeAllNetworks(ifaceName);
             }
         }
     }
 
     /**
      * Remove all networks from supplicant
+     *
+     * @param ifaceName Name of the interface.
      */
-    public boolean removeAllNetworks() {
+    public boolean removeAllNetworks(@NonNull String ifaceName) {
         synchronized (mLock) {
-            ArrayList<Integer> networks = listNetworks();
+            ArrayList<Integer> networks = listNetworks(ifaceName);
             if (networks == null) {
                 Log.e(TAG, "removeAllNetworks failed, got null networks");
                 return false;
             }
             for (int id : networks) {
-                if (!removeNetwork(id)) {
+                if (!removeNetwork(ifaceName, id)) {
                     Log.e(TAG, "removeAllNetworks failed to remove network: " + id);
                     return false;
                 }
             }
             // Reset current network info.  Probably not needed once we add support to remove/reset
             // current network on receiving disconnection event from supplicant (b/32898136).
-            mCurrentNetworkLocalConfig = null;
-            mCurrentNetworkRemoteHandle = null;
+            mCurrentNetworkRemoteHandles.remove(ifaceName);
+            mCurrentNetworkLocalConfigs.remove(ifaceName);
             return true;
         }
     }
@@ -589,113 +712,152 @@
     /**
      * Set the currently configured network's bssid.
      *
+     * @param ifaceName Name of the interface.
      * @param bssidStr Bssid to set in the form of "XX:XX:XX:XX:XX:XX"
      * @return true if succeeds, false otherwise.
      */
-    public boolean setCurrentNetworkBssid(String bssidStr) {
+    public boolean setCurrentNetworkBssid(@NonNull String ifaceName, String bssidStr) {
         synchronized (mLock) {
-            if (mCurrentNetworkRemoteHandle == null) return false;
-            return mCurrentNetworkRemoteHandle.setBssid(bssidStr);
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(ifaceName, "setCurrentNetworkBssid");
+            if (networkHandle == null) return false;
+            return networkHandle.setBssid(bssidStr);
         }
     }
 
     /**
      * Get the currently configured network's WPS NFC token.
      *
+     * @param ifaceName Name of the interface.
      * @return Hex string corresponding to the WPS NFC token.
      */
-    public String getCurrentNetworkWpsNfcConfigurationToken() {
+    public String getCurrentNetworkWpsNfcConfigurationToken(@NonNull String ifaceName) {
         synchronized (mLock) {
-            if (mCurrentNetworkRemoteHandle == null) return null;
-            return mCurrentNetworkRemoteHandle.getWpsNfcConfigurationToken();
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(
+                            ifaceName, "getCurrentNetworkWpsNfcConfigurationToken");
+            if (networkHandle == null) return null;
+            return networkHandle.getWpsNfcConfigurationToken();
         }
     }
 
     /**
      * Get the eap anonymous identity for the currently configured network.
      *
+     * @param ifaceName Name of the interface.
      * @return anonymous identity string if succeeds, null otherwise.
      */
-    public String getCurrentNetworkEapAnonymousIdentity() {
+    public String getCurrentNetworkEapAnonymousIdentity(@NonNull String ifaceName) {
         synchronized (mLock) {
-            if (mCurrentNetworkRemoteHandle == null) return null;
-            return mCurrentNetworkRemoteHandle.fetchEapAnonymousIdentity();
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(
+                            ifaceName, "getCurrentNetworkEapAnonymousIdentity");
+            if (networkHandle == null) return null;
+            return networkHandle.fetchEapAnonymousIdentity();
         }
     }
 
     /**
      * Send the eap identity response for the currently configured network.
      *
+     * @param ifaceName Name of the interface.
      * @param identityStr String to send.
      * @return true if succeeds, false otherwise.
      */
-    public boolean sendCurrentNetworkEapIdentityResponse(String identityStr) {
+    public boolean sendCurrentNetworkEapIdentityResponse(
+            @NonNull String ifaceName, String identityStr) {
         synchronized (mLock) {
-            if (mCurrentNetworkRemoteHandle == null) return false;
-            return mCurrentNetworkRemoteHandle.sendNetworkEapIdentityResponse(identityStr);
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(
+                            ifaceName, "sendCurrentNetworkEapIdentityResponse");
+            if (networkHandle == null) return false;
+            return networkHandle.sendNetworkEapIdentityResponse(identityStr);
         }
     }
 
     /**
      * Send the eap sim gsm auth response for the currently configured network.
      *
+     * @param ifaceName Name of the interface.
      * @param paramsStr String to send.
      * @return true if succeeds, false otherwise.
      */
-    public boolean sendCurrentNetworkEapSimGsmAuthResponse(String paramsStr) {
+    public boolean sendCurrentNetworkEapSimGsmAuthResponse(
+            @NonNull String ifaceName, String paramsStr) {
         synchronized (mLock) {
-            if (mCurrentNetworkRemoteHandle == null) return false;
-            return mCurrentNetworkRemoteHandle.sendNetworkEapSimGsmAuthResponse(paramsStr);
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(
+                            ifaceName, "sendCurrentNetworkEapSimGsmAuthResponse");
+            if (networkHandle == null) return false;
+            return networkHandle.sendNetworkEapSimGsmAuthResponse(paramsStr);
         }
     }
 
     /**
      * Send the eap sim gsm auth failure for the currently configured network.
      *
+     * @param ifaceName Name of the interface.
      * @return true if succeeds, false otherwise.
      */
-    public boolean sendCurrentNetworkEapSimGsmAuthFailure() {
+    public boolean sendCurrentNetworkEapSimGsmAuthFailure(@NonNull String ifaceName) {
         synchronized (mLock) {
-            if (mCurrentNetworkRemoteHandle == null) return false;
-            return mCurrentNetworkRemoteHandle.sendNetworkEapSimGsmAuthFailure();
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(
+                            ifaceName, "sendCurrentNetworkEapSimGsmAuthFailure");
+            if (networkHandle == null) return false;
+            return networkHandle.sendNetworkEapSimGsmAuthFailure();
         }
     }
 
     /**
      * Send the eap sim umts auth response for the currently configured network.
      *
+     * @param ifaceName Name of the interface.
      * @param paramsStr String to send.
      * @return true if succeeds, false otherwise.
      */
-    public boolean sendCurrentNetworkEapSimUmtsAuthResponse(String paramsStr) {
+    public boolean sendCurrentNetworkEapSimUmtsAuthResponse(
+            @NonNull String ifaceName, String paramsStr) {
         synchronized (mLock) {
-            if (mCurrentNetworkRemoteHandle == null) return false;
-            return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAuthResponse(paramsStr);
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(
+                            ifaceName, "sendCurrentNetworkEapSimUmtsAuthResponse");
+            if (networkHandle == null) return false;
+            return networkHandle.sendNetworkEapSimUmtsAuthResponse(paramsStr);
         }
     }
 
     /**
      * Send the eap sim umts auts response for the currently configured network.
      *
+     * @param ifaceName Name of the interface.
      * @param paramsStr String to send.
      * @return true if succeeds, false otherwise.
      */
-    public boolean sendCurrentNetworkEapSimUmtsAutsResponse(String paramsStr) {
+    public boolean sendCurrentNetworkEapSimUmtsAutsResponse(
+            @NonNull String ifaceName, String paramsStr) {
         synchronized (mLock) {
-            if (mCurrentNetworkRemoteHandle == null) return false;
-            return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAutsResponse(paramsStr);
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(
+                            ifaceName, "sendCurrentNetworkEapSimUmtsAutsResponse");
+            if (networkHandle == null) return false;
+            return networkHandle.sendNetworkEapSimUmtsAutsResponse(paramsStr);
         }
     }
 
     /**
      * Send the eap sim umts auth failure for the currently configured network.
      *
+     * @param ifaceName Name of the interface.
      * @return true if succeeds, false otherwise.
      */
-    public boolean sendCurrentNetworkEapSimUmtsAuthFailure() {
+    public boolean sendCurrentNetworkEapSimUmtsAuthFailure(@NonNull String ifaceName) {
         synchronized (mLock) {
-            if (mCurrentNetworkRemoteHandle == null) return false;
-            return mCurrentNetworkRemoteHandle.sendNetworkEapSimUmtsAuthFailure();
+            SupplicantStaNetworkHal networkHandle =
+                    checkSupplicantStaNetworkAndLogFailure(
+                            ifaceName, "sendCurrentNetworkEapSimUmtsAuthFailure");
+            if (networkHandle == null) return false;
+            return networkHandle.sendNetworkEapSimUmtsAuthFailure();
         }
     }
 
@@ -704,13 +866,14 @@
      *
      * @return The ISupplicantNetwork object for the new network, or null if the call fails
      */
-    private SupplicantStaNetworkHal addNetwork() {
+    private SupplicantStaNetworkHal addNetwork(@NonNull String ifaceName) {
         synchronized (mLock) {
             final String methodStr = "addNetwork";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return null;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return null;
             Mutable<ISupplicantNetwork> newNetwork = new Mutable<>();
             try {
-                mISupplicantStaIface.addNetwork((SupplicantStatus status,
+                iface.addNetwork((SupplicantStatus status,
                         ISupplicantNetwork network) -> {
                     if (checkStatusAndLogFailure(status, methodStr)) {
                         newNetwork.value = network;
@@ -721,6 +884,7 @@
             }
             if (newNetwork.value != null) {
                 return getStaNetworkMockable(
+                        ifaceName,
                         ISupplicantStaNetwork.asInterface(newNetwork.value.asBinder()));
             } else {
                 return null;
@@ -733,12 +897,13 @@
      *
      * @return true if request is sent successfully, false otherwise.
      */
-    private boolean removeNetwork(int id) {
+    private boolean removeNetwork(@NonNull String ifaceName, int id) {
         synchronized (mLock) {
             final String methodStr = "removeNetwork";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.removeNetwork(id);
+                SupplicantStatus status = iface.removeNetwork(id);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -750,15 +915,16 @@
     /**
      * Use this to mock the creation of SupplicantStaNetworkHal instance.
      *
+     * @param ifaceName Name of the interface.
      * @param iSupplicantStaNetwork ISupplicantStaNetwork instance retrieved from HIDL.
      * @return The ISupplicantNetwork object for the given SupplicantNetworkId int, returns null if
      * the call fails
      */
     protected SupplicantStaNetworkHal getStaNetworkMockable(
-            ISupplicantStaNetwork iSupplicantStaNetwork) {
+            @NonNull String ifaceName, ISupplicantStaNetwork iSupplicantStaNetwork) {
         synchronized (mLock) {
             SupplicantStaNetworkHal network =
-                    new SupplicantStaNetworkHal(iSupplicantStaNetwork, mIfaceName, mContext,
+                    new SupplicantStaNetworkHal(iSupplicantStaNetwork, ifaceName, mContext,
                             mWifiMonitor);
             if (network != null) {
                 network.enableVerboseLogging(mVerboseLoggingEnabled);
@@ -771,14 +937,14 @@
      * @return The ISupplicantNetwork object for the given SupplicantNetworkId int, returns null if
      * the call fails
      */
-    private SupplicantStaNetworkHal getNetwork(int id) {
+    private SupplicantStaNetworkHal getNetwork(@NonNull String ifaceName, int id) {
         synchronized (mLock) {
             final String methodStr = "getNetwork";
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return null;
             Mutable<ISupplicantNetwork> gotNetwork = new Mutable<>();
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return null;
             try {
-                mISupplicantStaIface.getNetwork(id, (SupplicantStatus status,
-                        ISupplicantNetwork network) -> {
+                iface.getNetwork(id, (SupplicantStatus status, ISupplicantNetwork network) -> {
                     if (checkStatusAndLogFailure(status, methodStr)) {
                         gotNetwork.value = network;
                     }
@@ -788,6 +954,7 @@
             }
             if (gotNetwork.value != null) {
                 return getStaNetworkMockable(
+                        ifaceName,
                         ISupplicantStaNetwork.asInterface(gotNetwork.value.asBinder()));
             } else {
                 return null;
@@ -796,12 +963,13 @@
     }
 
     /** See ISupplicantStaNetwork.hal for documentation */
-    private boolean registerCallback(ISupplicantStaIfaceCallback callback) {
+    private boolean registerCallback(
+            ISupplicantStaIface iface, ISupplicantStaIfaceCallback callback) {
         synchronized (mLock) {
             final String methodStr = "registerCallback";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            if (iface == null) return false;
             try {
-                SupplicantStatus status =  mISupplicantStaIface.registerCallback(callback);
+                SupplicantStatus status =  iface.registerCallback(callback);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -814,14 +982,14 @@
      * @return a list of SupplicantNetworkID ints for all networks controlled by supplicant, returns
      * null if the call fails
      */
-    private java.util.ArrayList<Integer> listNetworks() {
+    private java.util.ArrayList<Integer> listNetworks(@NonNull String ifaceName) {
         synchronized (mLock) {
             final String methodStr = "listNetworks";
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return null;
             Mutable<ArrayList<Integer>> networkIdList = new Mutable<>();
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return null;
             try {
-                mISupplicantStaIface.listNetworks((SupplicantStatus status,
-                        java.util.ArrayList<Integer> networkIds) -> {
+                iface.listNetworks((SupplicantStatus status, ArrayList<Integer> networkIds) -> {
                     if (checkStatusAndLogFailure(status, methodStr)) {
                         networkIdList.value = networkIds;
                     }
@@ -836,15 +1004,17 @@
     /**
      * Set WPS device name.
      *
+     * @param ifaceName Name of the interface.
      * @param name String to be set.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setWpsDeviceName(String name) {
+    public boolean setWpsDeviceName(@NonNull String ifaceName, String name) {
         synchronized (mLock) {
             final String methodStr = "setWpsDeviceName";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setWpsDeviceName(name);
+                SupplicantStatus status = iface.setWpsDeviceName(name);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -856,10 +1026,11 @@
     /**
      * Set WPS device type.
      *
+     * @param ifaceName Name of the interface.
      * @param typeStr Type specified as a string. Used format: <categ>-<OUI>-<subcateg>
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setWpsDeviceType(String typeStr) {
+    public boolean setWpsDeviceType(@NonNull String ifaceName, String typeStr) {
         synchronized (mLock) {
             try {
                 Matcher match = WPS_DEVICE_TYPE_PATTERN.matcher(typeStr);
@@ -876,7 +1047,7 @@
                 byteBuffer.putShort(categ);
                 byteBuffer.put(oui);
                 byteBuffer.putShort(subCateg);
-                return setWpsDeviceType(bytes);
+                return setWpsDeviceType(ifaceName, bytes);
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Illegal argument " + typeStr, e);
                 return false;
@@ -884,12 +1055,13 @@
         }
     }
 
-    private boolean setWpsDeviceType(byte[/* 8 */] type) {
+    private boolean setWpsDeviceType(@NonNull String ifaceName, byte[/* 8 */] type) {
         synchronized (mLock) {
             final String methodStr = "setWpsDeviceType";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setWpsDeviceType(type);
+                SupplicantStatus status = iface.setWpsDeviceType(type);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -901,15 +1073,17 @@
     /**
      * Set WPS manufacturer.
      *
+     * @param ifaceName Name of the interface.
      * @param manufacturer String to be set.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setWpsManufacturer(String manufacturer) {
+    public boolean setWpsManufacturer(@NonNull String ifaceName, String manufacturer) {
         synchronized (mLock) {
             final String methodStr = "setWpsManufacturer";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setWpsManufacturer(manufacturer);
+                SupplicantStatus status = iface.setWpsManufacturer(manufacturer);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -921,15 +1095,17 @@
     /**
      * Set WPS model name.
      *
+     * @param ifaceName Name of the interface.
      * @param modelName String to be set.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setWpsModelName(String modelName) {
+    public boolean setWpsModelName(@NonNull String ifaceName, String modelName) {
         synchronized (mLock) {
             final String methodStr = "setWpsModelName";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setWpsModelName(modelName);
+                SupplicantStatus status = iface.setWpsModelName(modelName);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -941,15 +1117,17 @@
     /**
      * Set WPS model number.
      *
+     * @param ifaceName Name of the interface.
      * @param modelNumber String to be set.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setWpsModelNumber(String modelNumber) {
+    public boolean setWpsModelNumber(@NonNull String ifaceName, String modelNumber) {
         synchronized (mLock) {
             final String methodStr = "setWpsModelNumber";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setWpsModelNumber(modelNumber);
+                SupplicantStatus status = iface.setWpsModelNumber(modelNumber);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -961,15 +1139,17 @@
     /**
      * Set WPS serial number.
      *
+     * @param ifaceName Name of the interface.
      * @param serialNumber String to be set.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setWpsSerialNumber(String serialNumber) {
+    public boolean setWpsSerialNumber(@NonNull String ifaceName, String serialNumber) {
         synchronized (mLock) {
             final String methodStr = "setWpsSerialNumber";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setWpsSerialNumber(serialNumber);
+                SupplicantStatus status = iface.setWpsSerialNumber(serialNumber);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -981,26 +1161,28 @@
     /**
      * Set WPS config methods
      *
+     * @param ifaceName Name of the interface.
      * @param configMethodsStr List of config methods.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setWpsConfigMethods(String configMethodsStr) {
+    public boolean setWpsConfigMethods(@NonNull String ifaceName, String configMethodsStr) {
         synchronized (mLock) {
             short configMethodsMask = 0;
             String[] configMethodsStrArr = configMethodsStr.split("\\s+");
             for (int i = 0; i < configMethodsStrArr.length; i++) {
                 configMethodsMask |= stringToWpsConfigMethod(configMethodsStrArr[i]);
             }
-            return setWpsConfigMethods(configMethodsMask);
+            return setWpsConfigMethods(ifaceName, configMethodsMask);
         }
     }
 
-    private boolean setWpsConfigMethods(short configMethods) {
+    private boolean setWpsConfigMethods(@NonNull String ifaceName, short configMethods) {
         synchronized (mLock) {
             final String methodStr = "setWpsConfigMethods";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setWpsConfigMethods(configMethods);
+                SupplicantStatus status = iface.setWpsConfigMethods(configMethods);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1012,14 +1194,16 @@
     /**
      * Trigger a reassociation even if the iface is currently connected.
      *
+     * @param ifaceName Name of the interface.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean reassociate() {
+    public boolean reassociate(@NonNull String ifaceName) {
         synchronized (mLock) {
             final String methodStr = "reassociate";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.reassociate();
+                SupplicantStatus status = iface.reassociate();
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1031,14 +1215,16 @@
     /**
      * Trigger a reconnection if the iface is disconnected.
      *
+     * @param ifaceName Name of the interface.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean reconnect() {
+    public boolean reconnect(@NonNull String ifaceName) {
         synchronized (mLock) {
             final String methodStr = "reconnect";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.reconnect();
+                SupplicantStatus status = iface.reconnect();
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1050,14 +1236,16 @@
     /**
      * Trigger a disconnection from the currently connected network.
      *
+     * @param ifaceName Name of the interface.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean disconnect() {
+    public boolean disconnect(@NonNull String ifaceName) {
         synchronized (mLock) {
             final String methodStr = "disconnect";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.disconnect();
+                SupplicantStatus status = iface.disconnect();
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1069,15 +1257,17 @@
     /**
      * Enable or disable power save mode.
      *
+     * @param ifaceName Name of the interface.
      * @param enable true to enable, false to disable.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setPowerSave(boolean enable) {
+    public boolean setPowerSave(@NonNull String ifaceName, boolean enable) {
         synchronized (mLock) {
             final String methodStr = "setPowerSave";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setPowerSave(enable);
+                SupplicantStatus status = iface.setPowerSave(enable);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1089,13 +1279,15 @@
     /**
      * Initiate TDLS discover with the specified AP.
      *
+     * @param ifaceName Name of the interface.
      * @param macAddress MAC Address of the AP.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean initiateTdlsDiscover(String macAddress) {
+    public boolean initiateTdlsDiscover(@NonNull String ifaceName, String macAddress) {
         synchronized (mLock) {
             try {
-                return initiateTdlsDiscover(NativeUtil.macAddressToByteArray(macAddress));
+                return initiateTdlsDiscover(
+                        ifaceName, NativeUtil.macAddressToByteArray(macAddress));
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Illegal argument " + macAddress, e);
                 return false;
@@ -1103,12 +1295,13 @@
         }
     }
     /** See ISupplicantStaIface.hal for documentation */
-    private boolean initiateTdlsDiscover(byte[/* 6 */] macAddress) {
+    private boolean initiateTdlsDiscover(@NonNull String ifaceName, byte[/* 6 */] macAddress) {
         synchronized (mLock) {
             final String methodStr = "initiateTdlsDiscover";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.initiateTdlsDiscover(macAddress);
+                SupplicantStatus status = iface.initiateTdlsDiscover(macAddress);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1120,13 +1313,14 @@
     /**
      * Initiate TDLS setup with the specified AP.
      *
+     * @param ifaceName Name of the interface.
      * @param macAddress MAC Address of the AP.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean initiateTdlsSetup(String macAddress) {
+    public boolean initiateTdlsSetup(@NonNull String ifaceName, String macAddress) {
         synchronized (mLock) {
             try {
-                return initiateTdlsSetup(NativeUtil.macAddressToByteArray(macAddress));
+                return initiateTdlsSetup(ifaceName, NativeUtil.macAddressToByteArray(macAddress));
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Illegal argument " + macAddress, e);
                 return false;
@@ -1134,12 +1328,13 @@
         }
     }
     /** See ISupplicantStaIface.hal for documentation */
-    private boolean initiateTdlsSetup(byte[/* 6 */] macAddress) {
+    private boolean initiateTdlsSetup(@NonNull String ifaceName, byte[/* 6 */] macAddress) {
         synchronized (mLock) {
             final String methodStr = "initiateTdlsSetup";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.initiateTdlsSetup(macAddress);
+                SupplicantStatus status = iface.initiateTdlsSetup(macAddress);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1150,13 +1345,15 @@
 
     /**
      * Initiate TDLS teardown with the specified AP.
+     * @param ifaceName Name of the interface.
      * @param macAddress MAC Address of the AP.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean initiateTdlsTeardown(String macAddress) {
+    public boolean initiateTdlsTeardown(@NonNull String ifaceName, String macAddress) {
         synchronized (mLock) {
             try {
-                return initiateTdlsTeardown(NativeUtil.macAddressToByteArray(macAddress));
+                return initiateTdlsTeardown(
+                        ifaceName, NativeUtil.macAddressToByteArray(macAddress));
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Illegal argument " + macAddress, e);
                 return false;
@@ -1165,12 +1362,13 @@
     }
 
     /** See ISupplicantStaIface.hal for documentation */
-    private boolean initiateTdlsTeardown(byte[/* 6 */] macAddress) {
+    private boolean initiateTdlsTeardown(@NonNull String ifaceName, byte[/* 6 */] macAddress) {
         synchronized (mLock) {
             final String methodStr = "initiateTdlsTeardown";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.initiateTdlsTeardown(macAddress);
+                SupplicantStatus status = iface.initiateTdlsTeardown(macAddress);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1182,16 +1380,19 @@
     /**
      * Request the specified ANQP elements |elements| from the specified AP |bssid|.
      *
+     * @param ifaceName Name of the interface.
      * @param bssid BSSID of the AP
      * @param infoElements ANQP elements to be queried. Refer to ISupplicantStaIface.AnqpInfoId.
      * @param hs20SubTypes HS subtypes to be queried. Refer to ISupplicantStaIface.Hs20AnqpSubTypes.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean initiateAnqpQuery(String bssid, ArrayList<Short> infoElements,
+    public boolean initiateAnqpQuery(@NonNull String ifaceName, String bssid,
+                                     ArrayList<Short> infoElements,
                                      ArrayList<Integer> hs20SubTypes) {
         synchronized (mLock) {
             try {
                 return initiateAnqpQuery(
+                        ifaceName,
                         NativeUtil.macAddressToByteArray(bssid), infoElements, hs20SubTypes);
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Illegal argument " + bssid, e);
@@ -1201,14 +1402,15 @@
     }
 
     /** See ISupplicantStaIface.hal for documentation */
-    private boolean initiateAnqpQuery(byte[/* 6 */] macAddress,
+    private boolean initiateAnqpQuery(@NonNull String ifaceName, byte[/* 6 */] macAddress,
             java.util.ArrayList<Short> infoElements, java.util.ArrayList<Integer> subTypes) {
         synchronized (mLock) {
             final String methodStr = "initiateAnqpQuery";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.initiateAnqpQuery(macAddress,
-                        infoElements, subTypes);
+                SupplicantStatus status = iface.initiateAnqpQuery(
+                        macAddress, infoElements, subTypes);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1220,14 +1422,16 @@
     /**
      * Request the specified ANQP ICON from the specified AP |bssid|.
      *
+     * @param ifaceName Name of the interface.
      * @param bssid BSSID of the AP
      * @param fileName Name of the file to request.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean initiateHs20IconQuery(String bssid, String fileName) {
+    public boolean initiateHs20IconQuery(@NonNull String ifaceName, String bssid, String fileName) {
         synchronized (mLock) {
             try {
-                return initiateHs20IconQuery(NativeUtil.macAddressToByteArray(bssid), fileName);
+                return initiateHs20IconQuery(
+                        ifaceName, NativeUtil.macAddressToByteArray(bssid), fileName);
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Illegal argument " + bssid, e);
                 return false;
@@ -1236,13 +1440,14 @@
     }
 
     /** See ISupplicantStaIface.hal for documentation */
-    private boolean initiateHs20IconQuery(byte[/* 6 */] macAddress, String fileName) {
+    private boolean initiateHs20IconQuery(@NonNull String ifaceName,
+                                          byte[/* 6 */] macAddress, String fileName) {
         synchronized (mLock) {
             final String methodStr = "initiateHs20IconQuery";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.initiateHs20IconQuery(macAddress,
-                        fileName);
+                SupplicantStatus status = iface.initiateHs20IconQuery(macAddress, fileName);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1254,15 +1459,17 @@
     /**
      * Makes a callback to HIDL to getMacAddress from supplicant
      *
+     * @param ifaceName Name of the interface.
      * @return string containing the MAC address, or null on a failed call
      */
-    public String getMacAddress() {
+    public String getMacAddress(@NonNull String ifaceName) {
         synchronized (mLock) {
             final String methodStr = "getMacAddress";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return null;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return null;
             Mutable<String> gotMac = new Mutable<>();
             try {
-                mISupplicantStaIface.getMacAddress((SupplicantStatus status,
+                iface.getMacAddress((SupplicantStatus status,
                         byte[/* 6 */] macAddr) -> {
                     if (checkStatusAndLogFailure(status, methodStr)) {
                         gotMac.value = NativeUtil.macAddressFromByteArray(macAddr);
@@ -1278,14 +1485,16 @@
     /**
      * Start using the added RX filters.
      *
+     * @param ifaceName Name of the interface.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean startRxFilter() {
+    public boolean startRxFilter(@NonNull String ifaceName) {
         synchronized (mLock) {
             final String methodStr = "startRxFilter";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.startRxFilter();
+                SupplicantStatus status = iface.startRxFilter();
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1297,14 +1506,16 @@
     /**
      * Stop using the added RX filters.
      *
+     * @param ifaceName Name of the interface.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean stopRxFilter() {
+    public boolean stopRxFilter(@NonNull String ifaceName) {
         synchronized (mLock) {
             final String methodStr = "stopRxFilter";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.stopRxFilter();
+                SupplicantStatus status = iface.stopRxFilter();
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1316,11 +1527,12 @@
     /**
      * Add an RX filter.
      *
+     * @param ifaceName Name of the interface.
      * @param type one of {@link WifiNative#RX_FILTER_TYPE_V4_MULTICAST}
      *        {@link WifiNative#RX_FILTER_TYPE_V6_MULTICAST} values.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean addRxFilter(int type) {
+    public boolean addRxFilter(@NonNull String ifaceName, int type) {
         synchronized (mLock) {
             byte halType;
             switch (type) {
@@ -1334,16 +1546,17 @@
                     Log.e(TAG, "Invalid Rx Filter type: " + type);
                     return false;
             }
-            return addRxFilter(halType);
+            return addRxFilter(ifaceName, halType);
         }
     }
 
-    public boolean addRxFilter(byte type) {
+    private boolean addRxFilter(@NonNull String ifaceName, byte type) {
         synchronized (mLock) {
             final String methodStr = "addRxFilter";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.addRxFilter(type);
+                SupplicantStatus status = iface.addRxFilter(type);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1355,11 +1568,12 @@
     /**
      * Remove an RX filter.
      *
+     * @param ifaceName Name of the interface.
      * @param type one of {@link WifiNative#RX_FILTER_TYPE_V4_MULTICAST}
      *        {@link WifiNative#RX_FILTER_TYPE_V6_MULTICAST} values.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean removeRxFilter(int type) {
+    public boolean removeRxFilter(@NonNull String ifaceName, int type) {
         synchronized (mLock) {
             byte halType;
             switch (type) {
@@ -1373,16 +1587,17 @@
                     Log.e(TAG, "Invalid Rx Filter type: " + type);
                     return false;
             }
-            return removeRxFilter(halType);
+            return removeRxFilter(ifaceName, halType);
         }
     }
 
-    public boolean removeRxFilter(byte type) {
+    private boolean removeRxFilter(@NonNull String ifaceName, byte type) {
         synchronized (mLock) {
             final String methodStr = "removeRxFilter";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.removeRxFilter(type);
+                SupplicantStatus status = iface.removeRxFilter(type);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1394,12 +1609,13 @@
     /**
      * Set Bt co existense mode.
      *
+     * @param ifaceName Name of the interface.
      * @param mode one of the above {@link WifiNative#BLUETOOTH_COEXISTENCE_MODE_DISABLED},
      *             {@link WifiNative#BLUETOOTH_COEXISTENCE_MODE_ENABLED} or
      *             {@link WifiNative#BLUETOOTH_COEXISTENCE_MODE_SENSE}.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setBtCoexistenceMode(int mode) {
+    public boolean setBtCoexistenceMode(@NonNull String ifaceName, int mode) {
         synchronized (mLock) {
             byte halMode;
             switch (mode) {
@@ -1416,16 +1632,17 @@
                     Log.e(TAG, "Invalid Bt Coex mode: " + mode);
                     return false;
             }
-            return setBtCoexistenceMode(halMode);
+            return setBtCoexistenceMode(ifaceName, halMode);
         }
     }
 
-    private boolean setBtCoexistenceMode(byte mode) {
+    private boolean setBtCoexistenceMode(@NonNull String ifaceName, byte mode) {
         synchronized (mLock) {
             final String methodStr = "setBtCoexistenceMode";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setBtCoexistenceMode(mode);
+                SupplicantStatus status = iface.setBtCoexistenceMode(mode);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1436,16 +1653,18 @@
 
     /** Enable or disable BT coexistence mode.
      *
+     * @param ifaceName Name of the interface.
      * @param enable true to enable, false to disable.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setBtCoexistenceScanModeEnabled(boolean enable) {
+    public boolean setBtCoexistenceScanModeEnabled(@NonNull String ifaceName, boolean enable) {
         synchronized (mLock) {
             final String methodStr = "setBtCoexistenceScanModeEnabled";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
                 SupplicantStatus status =
-                        mISupplicantStaIface.setBtCoexistenceScanModeEnabled(enable);
+                        iface.setBtCoexistenceScanModeEnabled(enable);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1457,15 +1676,17 @@
     /**
      * Enable or disable suspend mode optimizations.
      *
+     * @param ifaceName Name of the interface.
      * @param enable true to enable, false otherwise.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setSuspendModeEnabled(boolean enable) {
+    public boolean setSuspendModeEnabled(@NonNull String ifaceName, boolean enable) {
         synchronized (mLock) {
             final String methodStr = "setSuspendModeEnabled";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setSuspendModeEnabled(enable);
+                SupplicantStatus status = iface.setSuspendModeEnabled(enable);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1477,23 +1698,25 @@
     /**
      * Set country code.
      *
+     * @param ifaceName Name of the interface.
      * @param codeStr 2 byte ASCII string. For ex: US, CA.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setCountryCode(String codeStr) {
+    public boolean setCountryCode(@NonNull String ifaceName, String codeStr) {
         synchronized (mLock) {
             if (TextUtils.isEmpty(codeStr)) return false;
-            return setCountryCode(NativeUtil.stringToByteArray(codeStr));
+            return setCountryCode(ifaceName, NativeUtil.stringToByteArray(codeStr));
         }
     }
 
     /** See ISupplicantStaIface.hal for documentation */
-    private boolean setCountryCode(byte[/* 2 */] code) {
+    private boolean setCountryCode(@NonNull String ifaceName, byte[/* 2 */] code) {
         synchronized (mLock) {
             final String methodStr = "setCountryCode";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setCountryCode(code);
+                SupplicantStatus status = iface.setCountryCode(code);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1505,15 +1728,17 @@
     /**
      * Start WPS pin registrar operation with the specified peer and pin.
      *
+     * @param ifaceName Name of the interface.
      * @param bssidStr BSSID of the peer.
      * @param pin Pin to be used.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean startWpsRegistrar(String bssidStr, String pin) {
+    public boolean startWpsRegistrar(@NonNull String ifaceName, String bssidStr, String pin) {
         synchronized (mLock) {
             if (TextUtils.isEmpty(bssidStr) || TextUtils.isEmpty(pin)) return false;
             try {
-                return startWpsRegistrar(NativeUtil.macAddressToByteArray(bssidStr), pin);
+                return startWpsRegistrar(
+                        ifaceName, NativeUtil.macAddressToByteArray(bssidStr), pin);
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Illegal argument " + bssidStr, e);
                 return false;
@@ -1522,12 +1747,13 @@
     }
 
     /** See ISupplicantStaIface.hal for documentation */
-    private boolean startWpsRegistrar(byte[/* 6 */] bssid, String pin) {
+    private boolean startWpsRegistrar(@NonNull String ifaceName, byte[/* 6 */] bssid, String pin) {
         synchronized (mLock) {
             final String methodStr = "startWpsRegistrar";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.startWpsRegistrar(bssid, pin);
+                SupplicantStatus status = iface.startWpsRegistrar(bssid, pin);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1539,13 +1765,14 @@
     /**
      * Start WPS pin display operation with the specified peer.
      *
+     * @param ifaceName Name of the interface.
      * @param bssidStr BSSID of the peer. Use empty bssid to indicate wildcard.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean startWpsPbc(String bssidStr) {
+    public boolean startWpsPbc(@NonNull String ifaceName, String bssidStr) {
         synchronized (mLock) {
             try {
-                return startWpsPbc(NativeUtil.macAddressToByteArray(bssidStr));
+                return startWpsPbc(ifaceName, NativeUtil.macAddressToByteArray(bssidStr));
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Illegal argument " + bssidStr, e);
                 return false;
@@ -1554,12 +1781,13 @@
     }
 
     /** See ISupplicantStaIface.hal for documentation */
-    private boolean startWpsPbc(byte[/* 6 */] bssid) {
+    private boolean startWpsPbc(@NonNull String ifaceName, byte[/* 6 */] bssid) {
         synchronized (mLock) {
             final String methodStr = "startWpsPbc";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.startWpsPbc(bssid);
+                SupplicantStatus status = iface.startWpsPbc(bssid);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1571,16 +1799,18 @@
     /**
      * Start WPS pin keypad operation with the specified pin.
      *
+     * @param ifaceName Name of the interface.
      * @param pin Pin to be used.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean startWpsPinKeypad(String pin) {
+    public boolean startWpsPinKeypad(@NonNull String ifaceName, String pin) {
         if (TextUtils.isEmpty(pin)) return false;
         synchronized (mLock) {
             final String methodStr = "startWpsPinKeypad";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.startWpsPinKeypad(pin);
+                SupplicantStatus status = iface.startWpsPinKeypad(pin);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1592,13 +1822,14 @@
     /**
      * Start WPS pin display operation with the specified peer.
      *
+     * @param ifaceName Name of the interface.
      * @param bssidStr BSSID of the peer. Use empty bssid to indicate wildcard.
      * @return new pin generated on success, null otherwise.
      */
-    public String startWpsPinDisplay(String bssidStr) {
+    public String startWpsPinDisplay(@NonNull String ifaceName, String bssidStr) {
         synchronized (mLock) {
             try {
-                return startWpsPinDisplay(NativeUtil.macAddressToByteArray(bssidStr));
+                return startWpsPinDisplay(ifaceName, NativeUtil.macAddressToByteArray(bssidStr));
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Illegal argument " + bssidStr, e);
                 return null;
@@ -1607,13 +1838,14 @@
     }
 
     /** See ISupplicantStaIface.hal for documentation */
-    private String startWpsPinDisplay(byte[/* 6 */] bssid) {
+    private String startWpsPinDisplay(@NonNull String ifaceName, byte[/* 6 */] bssid) {
         synchronized (mLock) {
             final String methodStr = "startWpsPinDisplay";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return null;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return null;
             final Mutable<String> gotPin = new Mutable<>();
             try {
-                mISupplicantStaIface.startWpsPinDisplay(bssid,
+                iface.startWpsPinDisplay(bssid,
                         (SupplicantStatus status, String pin) -> {
                             if (checkStatusAndLogFailure(status, methodStr)) {
                                 gotPin.value = pin;
@@ -1629,14 +1861,16 @@
     /**
      * Cancels any ongoing WPS requests.
      *
+     * @param ifaceName Name of the interface.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean cancelWps() {
+    public boolean cancelWps(@NonNull String ifaceName) {
         synchronized (mLock) {
             final String methodStr = "cancelWps";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.cancelWps();
+                SupplicantStatus status = iface.cancelWps();
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1648,15 +1882,17 @@
     /**
      * Sets whether to use external sim for SIM/USIM processing.
      *
+     * @param ifaceName Name of the interface.
      * @param useExternalSim true to enable, false otherwise.
      * @return true if request is sent successfully, false otherwise.
      */
-    public boolean setExternalSim(boolean useExternalSim) {
+    public boolean setExternalSim(@NonNull String ifaceName, boolean useExternalSim) {
         synchronized (mLock) {
             final String methodStr = "setExternalSim";
-            if (!checkSupplicantStaIfaceAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.setExternalSim(useExternalSim);
+                SupplicantStatus status = iface.setExternalSim(useExternalSim);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1666,12 +1902,13 @@
     }
 
     /** See ISupplicant.hal for documentation */
-    public boolean enableAutoReconnect(boolean enable) {
+    public boolean enableAutoReconnect(@NonNull String ifaceName, boolean enable) {
         synchronized (mLock) {
             final String methodStr = "enableAutoReconnect";
-            if (!checkSupplicantAndLogFailure(methodStr)) return false;
+            ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
+            if (iface == null) return false;
             try {
-                SupplicantStatus status = mISupplicantStaIface.enableAutoReconnect(enable);
+                SupplicantStatus status = iface.enableAutoReconnect(enable);
                 return checkStatusAndLogFailure(status, methodStr);
             } catch (RemoteException e) {
                 handleRemoteException(e, methodStr);
@@ -1759,13 +1996,30 @@
     /**
      * Returns false if SupplicantStaIface is null, and logs failure to call methodStr
      */
-    private boolean checkSupplicantStaIfaceAndLogFailure(final String methodStr) {
+    private ISupplicantStaIface checkSupplicantStaIfaceAndLogFailure(
+            @NonNull String ifaceName, final String methodStr) {
         synchronized (mLock) {
-            if (mISupplicantStaIface == null) {
+            ISupplicantStaIface iface = getStaIface(ifaceName);
+            if (iface == null) {
                 Log.e(TAG, "Can't call " + methodStr + ", ISupplicantStaIface is null");
-                return false;
+                return null;
             }
-            return true;
+            return iface;
+        }
+    }
+
+    /**
+     * Returns false if SupplicantStaNetwork is null, and logs failure to call methodStr
+     */
+    private SupplicantStaNetworkHal checkSupplicantStaNetworkAndLogFailure(
+            @NonNull String ifaceName, final String methodStr) {
+        synchronized (mLock) {
+            SupplicantStaNetworkHal networkHal = getCurrentNetworkRemoteHandle(ifaceName);
+            if (networkHal == null) {
+                Log.e(TAG, "Can't call " + methodStr + ", SupplicantStaNetwork is null");
+                return null;
+            }
+            return networkHal;
         }
     }
 
@@ -1922,9 +2176,13 @@
     }
 
     private class SupplicantStaIfaceHalCallback extends ISupplicantStaIfaceCallback.Stub {
-        private static final int WLAN_REASON_IE_IN_4WAY_DIFFERS = 17; // IEEE 802.11i
+        private String mIfaceName;
         private boolean mStateIsFourway = false; // Used to help check for PSK password mismatch
 
+        SupplicantStaIfaceHalCallback(@NonNull String ifaceName) {
+            mIfaceName = ifaceName;
+        }
+
         /**
          * Parses the provided payload into an ANQP element.
          *
@@ -1993,10 +2251,11 @@
                 mStateIsFourway = (newState == ISupplicantStaIfaceCallback.State.FOURWAY_HANDSHAKE);
                 if (newSupplicantState == SupplicantState.COMPLETED) {
                     mWifiMonitor.broadcastNetworkConnectionEvent(
-                            mIfaceName, getCurrentNetworkId(), bssidStr);
+                            mIfaceName, getCurrentNetworkId(mIfaceName), bssidStr);
                 }
                 mWifiMonitor.broadcastSupplicantStateChangeEvent(
-                        mIfaceName, getCurrentNetworkId(), wifiSsid, bssidStr, newSupplicantState);
+                        mIfaceName, getCurrentNetworkId(mIfaceName), wifiSsid,
+                        bssidStr, newSupplicantState);
             }
         }
 
@@ -2067,10 +2326,14 @@
                             + " reasonCode=" + reasonCode);
                 }
                 if (mStateIsFourway
-                        && (!locallyGenerated || reasonCode != WLAN_REASON_IE_IN_4WAY_DIFFERS)) {
+                        && (!locallyGenerated || reasonCode != ReasonCode.IE_IN_4WAY_DIFFERS)) {
                     mWifiMonitor.broadcastAuthenticationFailureEvent(
                             mIfaceName, WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD);
                 }
+                if (reasonCode == ReasonCode.IEEE_802_1X_AUTH_FAILED) {
+                    mWifiMonitor.broadcastAuthenticationFailureEvent(
+                            mIfaceName, WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE);
+                }
                 mWifiMonitor.broadcastNetworkDisconnectionEvent(
                         mIfaceName, locallyGenerated ? 1 : 0, reasonCode,
                         NativeUtil.macAddressFromByteArray(bssid));
diff --git a/service/java/com/android/server/wifi/VelocityBasedConnectedScore.java b/service/java/com/android/server/wifi/VelocityBasedConnectedScore.java
new file mode 100644
index 0000000..38fd1ef
--- /dev/null
+++ b/service/java/com/android/server/wifi/VelocityBasedConnectedScore.java
@@ -0,0 +1,194 @@
+/*
+ * 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.content.Context;
+import android.net.wifi.WifiInfo;
+
+import com.android.internal.R;
+import com.android.server.wifi.util.KalmanFilter;
+import com.android.server.wifi.util.Matrix;
+
+/**
+ * Class used to calculate scores for connected wifi networks and report it to the associated
+ * network agent.
+ */
+public class VelocityBasedConnectedScore extends ConnectedScore {
+
+    // Device configs. The values are examples.
+    private final int mThresholdMinimumRssi5;      // -82
+    private final int mThresholdMinimumRssi24;     // -85
+
+    private int mFrequency = 5000;
+    private double mThresholdMinimumRssi;
+    private double mThresholdAdjustment;
+    private final KalmanFilter mFilter;
+    private long mLastMillis;
+
+    public VelocityBasedConnectedScore(Context context, Clock clock) {
+        super(clock);
+        mThresholdMinimumRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+        mThresholdMinimumRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+        mThresholdMinimumRssi = mThresholdMinimumRssi5;
+        mFilter = new KalmanFilter();
+        mFilter.mH = new Matrix(2, new double[]{1.0, 0.0});
+        mFilter.mR = new Matrix(1, new double[]{1.0});
+    }
+
+    /**
+     * Set the Kalman filter's state transition matrix F and process noise covariance Q given
+     * a time step.
+     *
+     * @param dt delta time, in seconds
+     */
+    private void setDeltaTimeSeconds(double dt) {
+        mFilter.mF = new Matrix(2, new double[]{1.0, dt, 0.0, 1.0});
+        Matrix tG = new Matrix(1, new double[]{0.5 * dt * dt, dt});
+        double stda = 0.02; // standard deviation of modelled acceleration
+        mFilter.mQ = tG.dotTranspose(tG).dot(new Matrix(2, new double[]{
+                stda * stda, 0.0,
+                0.0, stda * stda}));
+    }
+    /**
+     * Reset the filter state.
+     */
+    @Override
+    public void reset() {
+        mLastMillis = 0;
+        mThresholdAdjustment = 0.0;
+    }
+
+    /**
+     * Updates scoring state using RSSI and measurement noise estimate
+     * <p>
+     * This is useful if an RSSI comes from another source (e.g. scan results) and the
+     * expected noise varies by source.
+     *
+     * @param rssi              signal strength (dB).
+     * @param millis            millisecond-resolution time.
+     * @param standardDeviation of the RSSI.
+     */
+    @Override
+    public void updateUsingRssi(int rssi, long millis, double standardDeviation) {
+        if (millis <= 0) return;
+        if (mLastMillis <= 0 || millis < mLastMillis) {
+            double initialVariance = 9.0 * standardDeviation * standardDeviation;
+            mFilter.mx = new Matrix(1, new double[]{rssi, 0.0});
+            mFilter.mP = new Matrix(2, new double[]{initialVariance, 0.0, 0.0, 0.0});
+            mLastMillis = millis;
+            return;
+        }
+        double dt = (millis - mLastMillis) * 0.001;
+        mFilter.mR.put(0, 0, standardDeviation * standardDeviation);
+        setDeltaTimeSeconds(dt);
+        mFilter.predict();
+        mLastMillis = millis;
+        mFilter.update(new Matrix(1, new double[]{rssi}));
+        mFilteredRssi = mFilter.mx.get(0, 0);
+        mEstimatedRateOfRssiChange = mFilter.mx.get(1, 0);
+    }
+
+    /**
+     * Updates the state.
+     */
+    @Override
+    public void updateUsingWifiInfo(WifiInfo wifiInfo, long millis) {
+        int frequency = wifiInfo.getFrequency();
+        if (frequency != mFrequency) {
+            mLastMillis = 0; // Probably roamed; reset filter but retain threshold adjustment
+            // Consider resetting or partially resetting threshold adjustment
+            // Consider checking bssid
+            mFrequency = frequency;
+            mThresholdMinimumRssi =
+                    mFrequency >= 5000 ? mThresholdMinimumRssi5 : mThresholdMinimumRssi24;
+        }
+        updateUsingRssi(wifiInfo.getRssi(), millis, mDefaultRssiStandardDeviation);
+        adjustThreshold(wifiInfo);
+    }
+
+    private double mFilteredRssi;
+    private double mEstimatedRateOfRssiChange;
+
+    /**
+     * Returns the most recently computed extimate of the RSSI.
+     */
+    public double getFilteredRssi() {
+        return mFilteredRssi;
+    }
+
+    /**
+     * Returns the estimated rate of change of RSSI, in dB/second
+     */
+    public double getEstimatedRateOfRssiChange() {
+        return mEstimatedRateOfRssiChange;
+    }
+
+    /**
+     * Returns the adjusted RSSI threshold
+     */
+    public double getAdjustedRssiThreshold() {
+        return mThresholdMinimumRssi + mThresholdAdjustment;
+    }
+
+    private double mMinimumPpsForMeasuringSuccess = 2.0;
+
+    /**
+     * Adjusts the threshold if appropriate
+     * <p>
+     * If the (filtered) rssi is near or below the current effective threshold, and the
+     * rate of rssi change is small, and there is traffic, and the error rate is looking
+     * reasonable, then decrease the effective threshold to keep from dropping a perfectly good
+     * connection.
+     *
+     */
+    private void adjustThreshold(WifiInfo wifiInfo) {
+        if (mThresholdAdjustment < -7) return;
+        if (mFilteredRssi >= getAdjustedRssiThreshold() + 2.0) return;
+        if (Math.abs(mEstimatedRateOfRssiChange) >= 0.2) return;
+        double txSuccessPps = wifiInfo.txSuccessRate;
+        double rxSuccessPps = wifiInfo.rxSuccessRate;
+        if (txSuccessPps < mMinimumPpsForMeasuringSuccess) return;
+        if (rxSuccessPps < mMinimumPpsForMeasuringSuccess) return;
+        double txBadPps = wifiInfo.txBadRate;
+        double probabilityOfSuccessfulTx = txSuccessPps / (txSuccessPps + txBadPps);
+        if (probabilityOfSuccessfulTx >= 0.2) {
+            // May want this amount to vary with how close to threshold we are
+            mThresholdAdjustment -= 0.5;
+        }
+    }
+
+    /**
+     * Velocity scorer - predict the rssi a few seconds from now
+     */
+    @Override
+    public int generateScore() {
+        double badRssi = getAdjustedRssiThreshold();
+        double horizonSeconds = 15.0;
+        Matrix x = new Matrix(mFilter.mx);
+        double filteredRssi = x.get(0, 0);
+        setDeltaTimeSeconds(horizonSeconds);
+        x = mFilter.mF.dot(x);
+        double forecastRssi = x.get(0, 0);
+        if (forecastRssi > filteredRssi) {
+            forecastRssi = filteredRssi; // Be pessimistic about predicting an actual increase
+        }
+        int score = (int) (Math.round(forecastRssi) - badRssi) + WIFI_TRANSITION_SCORE;
+        return score;
+    }
+}
diff --git a/service/java/com/android/server/wifi/WakeupConfigStoreData.java b/service/java/com/android/server/wifi/WakeupConfigStoreData.java
new file mode 100644
index 0000000..5775117
--- /dev/null
+++ b/service/java/com/android/server/wifi/WakeupConfigStoreData.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 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.util.ArraySet;
+
+import com.android.server.wifi.WifiConfigStore.StoreData;
+import com.android.server.wifi.util.XmlUtil;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Config store data for Wifi Wake.
+ */
+public class WakeupConfigStoreData implements StoreData {
+    private static final String TAG = "WakeupConfigStoreData";
+
+    private static final String XML_TAG_IS_ACTIVE = "IsActive";
+    private static final String XML_TAG_NETWORK_SECTION = "Network";
+    private static final String XML_TAG_SSID = "SSID";
+    private static final String XML_TAG_SECURITY = "Security";
+
+    private final DataSource<Boolean> mIsActiveDataSource;
+    private final DataSource<Set<ScanResultMatchInfo>> mNetworkDataSource;
+
+    /**
+     * Interface defining a data source for the store data.
+     *
+     * @param <T> Type of data source
+     */
+    public interface DataSource<T> {
+        /**
+         * Returns the data from the data source.
+         */
+        T getData();
+
+        /**
+         * Updates the data in the data source.
+         *
+         * @param data Data retrieved from the store
+         */
+        void setData(T data);
+    }
+
+    /**
+     * Creates the config store data with its data sources.
+     *
+     * @param isActiveDataSource Data source for isActive
+     * @param networkDataSource Data source for the locked network list
+     */
+    public WakeupConfigStoreData(
+            DataSource<Boolean> isActiveDataSource,
+            DataSource<Set<ScanResultMatchInfo>> networkDataSource) {
+        mIsActiveDataSource = isActiveDataSource;
+        mNetworkDataSource = networkDataSource;
+    }
+
+    @Override
+    public void serializeData(XmlSerializer out, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            throw new XmlPullParserException("Share data not supported");
+        }
+
+        XmlUtil.writeNextValue(out, XML_TAG_IS_ACTIVE, mIsActiveDataSource.getData());
+
+        for (ScanResultMatchInfo scanResultMatchInfo : mNetworkDataSource.getData()) {
+            writeNetwork(out, scanResultMatchInfo);
+        }
+    }
+
+    /**
+     * Writes a {@link ScanResultMatchInfo} to an XML output stream.
+     *
+     * @param out XML output stream
+     * @param scanResultMatchInfo The ScanResultMatchInfo to serizialize
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void writeNetwork(XmlSerializer out, ScanResultMatchInfo scanResultMatchInfo)
+            throws XmlPullParserException, IOException {
+        XmlUtil.writeNextSectionStart(out, XML_TAG_NETWORK_SECTION);
+
+        XmlUtil.writeNextValue(out, XML_TAG_SSID, scanResultMatchInfo.networkSsid);
+        XmlUtil.writeNextValue(out, XML_TAG_SECURITY, scanResultMatchInfo.networkType);
+
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_NETWORK_SECTION);
+    }
+
+    @Override
+    public void deserializeData(XmlPullParser in, int outerTagDepth, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            throw new XmlPullParserException("Shared data not supported");
+        }
+
+        boolean isActive = (Boolean) XmlUtil.readNextValueWithName(in, XML_TAG_IS_ACTIVE);
+        mIsActiveDataSource.setData(isActive);
+
+        Set<ScanResultMatchInfo> networks = new ArraySet<>();
+        while (XmlUtil.gotoNextSectionWithNameOrEnd(in, XML_TAG_NETWORK_SECTION, outerTagDepth)) {
+            networks.add(parseNetwork(in, outerTagDepth + 1));
+        }
+
+        mNetworkDataSource.setData(networks);
+    }
+
+    /**
+     * Parses a {@link ScanResultMatchInfo} from an XML input stream.
+     *
+     * @param in XML input stream
+     * @param outerTagDepth XML tag depth of the containing section
+     * @return The {@link ScanResultMatchInfo}
+     * @throws IOException
+     * @throws XmlPullParserException
+     */
+    private ScanResultMatchInfo parseNetwork(XmlPullParser in, int outerTagDepth)
+            throws IOException, XmlPullParserException {
+        ScanResultMatchInfo scanResultMatchInfo = new ScanResultMatchInfo();
+        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+            String[] valueName = new String[1];
+            Object value = XmlUtil.readCurrentValue(in, valueName);
+            if (valueName[0] == null) {
+                throw new XmlPullParserException("Missing value name");
+            }
+            switch (valueName[0]) {
+                case XML_TAG_SSID:
+                    scanResultMatchInfo.networkSsid = (String) value;
+                    break;
+                case XML_TAG_SECURITY:
+                    scanResultMatchInfo.networkType = (int) value;
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown tag under " + TAG + ": "
+                            + valueName[0]);
+            }
+        }
+
+        return scanResultMatchInfo;
+    }
+
+    @Override
+    public void resetData(boolean shared) {
+        if (!shared) {
+            mNetworkDataSource.setData(Collections.emptySet());
+            mIsActiveDataSource.setData(false);
+        }
+    }
+
+    @Override
+    public String getName() {
+        return TAG;
+    }
+
+    @Override
+    public boolean supportShareData() {
+        return false;
+    }
+}
diff --git a/service/java/com/android/server/wifi/WakeupController.java b/service/java/com/android/server/wifi/WakeupController.java
new file mode 100644
index 0000000..8787bfd
--- /dev/null
+++ b/service/java/com/android/server/wifi/WakeupController.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 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.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * WakeupController is responsible managing Auto Wifi.
+ *
+ * <p>It determines if and when to re-enable wifi after it has been turned off by the user.
+ */
+public class WakeupController {
+
+    // TODO(b/69624403) propagate this to Settings
+    private static final boolean USE_PLATFORM_WIFI_WAKE = false;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final FrameworkFacade mFrameworkFacade;
+    private final ContentObserver mContentObserver;
+    private final WakeupLock mWakeupLock;
+    private final WifiConfigManager mWifiConfigManager;
+
+    /** Whether this feature is enabled in Settings. */
+    private boolean mWifiWakeupEnabled;
+
+    /** Whether the WakeupController is currently active. */
+    private boolean mIsActive = false;
+
+    public WakeupController(
+            Context context,
+            Looper looper,
+            WakeupLock wakeupLock,
+            WifiConfigManager wifiConfigManager,
+            WifiConfigStore wifiConfigStore,
+            FrameworkFacade frameworkFacade) {
+        mContext = context;
+        mHandler = new Handler(looper);
+        mWakeupLock = wakeupLock;
+        mWifiConfigManager = wifiConfigManager;
+        mFrameworkFacade = frameworkFacade;
+        mContentObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mWifiWakeupEnabled = mFrameworkFacade.getIntegerSetting(
+                                mContext, Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
+            }
+        };
+        mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
+                Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver);
+        mContentObserver.onChange(false /* selfChange */);
+
+        // registering the store data here has the effect of reading the persisted value of the
+        // data sources after system boot finishes
+        WakeupConfigStoreData wakeupConfigStoreData =
+                new WakeupConfigStoreData(new IsActiveDataSource(), mWakeupLock.getDataSource());
+        wifiConfigStore.registerStoreData(wakeupConfigStoreData);
+    }
+
+    private void setActive(boolean isActive) {
+        if (mIsActive != isActive) {
+            mIsActive = isActive;
+            mWifiConfigManager.saveToStore(false /* forceWrite */);
+        }
+    }
+
+    /**
+     * Whether the feature is enabled in settings.
+     *
+     * <p>Note: This method is only used to determine whether or not to actually enable wifi. All
+     * other aspects of the WakeupController lifecycle operate normally irrespective of this.
+     */
+    @VisibleForTesting
+    boolean isEnabled() {
+        return mWifiWakeupEnabled;
+    }
+
+    /** Dumps wakeup controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of WakeupController");
+        pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled);
+        pw.println("USE_PLATFORM_WIFI_WAKE: " + USE_PLATFORM_WIFI_WAKE);
+        pw.println("mIsActive: " + mIsActive);
+        mWakeupLock.dump(fd, pw, args);
+    }
+
+    private class IsActiveDataSource implements WakeupConfigStoreData.DataSource<Boolean> {
+
+        @Override
+        public Boolean getData() {
+            return mIsActive;
+        }
+
+        @Override
+        public void setData(Boolean data) {
+            mIsActive = data;
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/WakeupLock.java b/service/java/com/android/server/wifi/WakeupLock.java
new file mode 100644
index 0000000..1fcd9f8
--- /dev/null
+++ b/service/java/com/android/server/wifi/WakeupLock.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 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.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A lock to determine whether Auto Wifi can re-enable Wifi.
+ *
+ * <p>Wakeuplock manages a list of networks to determine whether the device's location has changed.
+ */
+public class WakeupLock {
+
+    private static final String TAG = WakeupLock.class.getSimpleName();
+
+    @VisibleForTesting
+    static final int CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT = 3;
+
+
+    private final WifiConfigManager mWifiConfigManager;
+    private final Map<ScanResultMatchInfo, Integer> mLockedNetworks = new ArrayMap<>();
+
+    public WakeupLock(WifiConfigManager wifiConfigManager) {
+        mWifiConfigManager = wifiConfigManager;
+    }
+
+    /**
+     * Initializes the WakeupLock with the given {@link ScanResultMatchInfo} list.
+     *
+     * <p>This saves the wakeup lock to the store.
+     *
+     * @param scanResultList list of ScanResultMatchInfos to start the lock with
+     */
+    public void initialize(Collection<ScanResultMatchInfo> scanResultList) {
+        mLockedNetworks.clear();
+        for (ScanResultMatchInfo scanResultMatchInfo : scanResultList) {
+            mLockedNetworks.put(scanResultMatchInfo, CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT);
+        }
+
+        Log.d(TAG, "Lock initialized. Number of networks: " + mLockedNetworks.size());
+
+        mWifiConfigManager.saveToStore(false /* forceWrite */);
+    }
+
+    /**
+     * Updates the lock with the given {@link ScanResultMatchInfo} list.
+     *
+     * <p>If a network in the lock is not present in the list, reduce the number of scans
+     * required to evict by one. Remove any entries in the list with 0 scans required to evict. If
+     * any entries in the lock are removed, the store is updated.
+     *
+     * @param scanResultList list of present ScanResultMatchInfos to update the lock with
+     */
+    public void update(Collection<ScanResultMatchInfo> scanResultList) {
+        boolean hasChanged = false;
+        Iterator<Map.Entry<ScanResultMatchInfo, Integer>> it =
+                mLockedNetworks.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<ScanResultMatchInfo, Integer> entry = it.next();
+
+            // if present in scan list, reset to max
+            if (scanResultList.contains(entry.getKey())) {
+                entry.setValue(CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT);
+                continue;
+            }
+
+            // decrement and remove if necessary
+            entry.setValue(entry.getValue() - 1);
+            if (entry.getValue() <= 0) {
+                it.remove();
+                hasChanged = true;
+            }
+        }
+
+        if (hasChanged) {
+            mWifiConfigManager.saveToStore(false /* forceWrite */);
+        }
+    }
+
+    /**
+     * Returns whether the internal network set is empty.
+     */
+    public boolean isEmpty() {
+        return mLockedNetworks.isEmpty();
+    }
+
+    /** Returns the data source for the WakeupLock config store data. */
+    public WakeupConfigStoreData.DataSource<Set<ScanResultMatchInfo>> getDataSource() {
+        return new WakeupLockDataSource();
+    }
+
+    /** Dumps wakeup lock contents. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("WakeupLock: ");
+        pw.println("Locked networks: " + mLockedNetworks.size());
+        for (Map.Entry<ScanResultMatchInfo, Integer> entry : mLockedNetworks.entrySet()) {
+            pw.println(entry.getKey() + ", scans to evict: " + entry.getValue());
+        }
+    }
+
+    private class WakeupLockDataSource
+            implements WakeupConfigStoreData.DataSource<Set<ScanResultMatchInfo>> {
+
+        @Override
+        public Set<ScanResultMatchInfo> getData() {
+            return mLockedNetworks.keySet();
+        }
+
+        @Override
+        public void setData(Set<ScanResultMatchInfo> data) {
+            mLockedNetworks.clear();
+            for (ScanResultMatchInfo network : data) {
+                mLockedNetworks.put(network, CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT);
+            }
+
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiBackupRestore.java b/service/java/com/android/server/wifi/WifiBackupRestore.java
index 60c3b48..ae5e411 100644
--- a/service/java/com/android/server/wifi/WifiBackupRestore.java
+++ b/service/java/com/android/server/wifi/WifiBackupRestore.java
@@ -228,9 +228,8 @@
             }
 
             return parseNetworkConfigurationsFromXml(in, rootTagDepth, version);
-        } catch (XmlPullParserException e) {
-            Log.e(TAG, "Error parsing the backup data: " + e);
-        } catch (IOException e) {
+        } catch (XmlPullParserException | IOException | ClassCastException
+                | IllegalArgumentException e) {
             Log.e(TAG, "Error parsing the backup data: " + e);
         }
         return null;
diff --git a/service/java/com/android/server/wifi/WifiCertManager.java b/service/java/com/android/server/wifi/WifiCertManager.java
deleted file mode 100644
index e180f51..0000000
--- a/service/java/com/android/server/wifi/WifiCertManager.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2015 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.app.admin.IDevicePolicyManager;
-import android.content.Context;
-import android.os.Environment;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.security.Credentials;
-import android.security.KeyStore;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.server.net.DelayedDiskWrite;
-
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Manager class for affiliated Wifi certificates.
- */
-public class WifiCertManager {
-    private static final String TAG = "WifiCertManager";
-    private static final String SEP = "\n";
-
-    private final Context mContext;
-    private final Set<String> mAffiliatedUserOnlyCerts = new HashSet<String>();
-    private final String mConfigFile;
-
-    private static final String CONFIG_FILE =
-            Environment.getDataDirectory() + "/misc/wifi/affiliatedcerts.txt";
-
-    private final DelayedDiskWrite mWriter = new DelayedDiskWrite();
-
-
-    WifiCertManager(Context context) {
-        this(context, CONFIG_FILE);
-    }
-
-    WifiCertManager(Context context, String configFile) {
-        mContext = context;
-        mConfigFile = configFile;
-        final byte[] bytes = readConfigFile();
-        if (bytes == null) {
-            // Config file does not exist or empty.
-            return;
-        }
-
-        String[] keys = new String(bytes, StandardCharsets.UTF_8).split(SEP);
-        for (String key : keys) {
-            mAffiliatedUserOnlyCerts.add(key);
-        }
-
-        // Remove keys that no longer exist in KeyStore.
-        if (mAffiliatedUserOnlyCerts.retainAll(Arrays.asList(listClientCertsForAllUsers()))) {
-            writeConfig();
-        }
-    }
-
-    /** @param  key Unprefixed cert key to hide from unaffiliated users. */
-    public void hideCertFromUnaffiliatedUsers(String key) {
-        if (mAffiliatedUserOnlyCerts.add(Credentials.USER_PRIVATE_KEY + key)) {
-            writeConfig();
-        }
-    }
-
-    /** @return Prefixed cert keys that are visible to the current user. */
-    public String[] listClientCertsForCurrentUser() {
-        HashSet<String> results = new HashSet<String>();
-
-        String[] keys = listClientCertsForAllUsers();
-        if (isAffiliatedUser()) {
-            return keys;
-        }
-
-        for (String key : keys) {
-            if (!mAffiliatedUserOnlyCerts.contains(key)) {
-                results.add(key);
-            }
-        }
-        return results.toArray(new String[results.size()]);
-    }
-
-    private void writeConfig() {
-        String[] values =
-                mAffiliatedUserOnlyCerts.toArray(new String[mAffiliatedUserOnlyCerts.size()]);
-        String value = TextUtils.join(SEP, values);
-        writeConfigFile(value.getBytes(StandardCharsets.UTF_8));
-    }
-
-    protected byte[] readConfigFile() {
-        byte[] bytes = null;
-        try {
-            final File file = new File(mConfigFile);
-            final long fileSize = file.exists() ? file.length() : 0;
-            if (fileSize == 0 || fileSize >= Integer.MAX_VALUE) {
-                // Config file is empty/corrupted/non-existing.
-                return bytes;
-            }
-
-            bytes = new byte[(int) file.length()];
-            final DataInputStream stream = new DataInputStream(new FileInputStream(file));
-            stream.readFully(bytes);
-        } catch (IOException e) {
-            Log.e(TAG, "readConfigFile: failed to read " + e, e);
-        }
-        return bytes;
-    }
-
-    protected void writeConfigFile(byte[] payload) {
-        final byte[] data = payload;
-        mWriter.write(mConfigFile, new DelayedDiskWrite.Writer() {
-            public void onWriteCalled(DataOutputStream out) throws IOException {
-                out.write(data, 0, data.length);
-            }
-        });
-    }
-
-    protected String[] listClientCertsForAllUsers() {
-        return KeyStore.getInstance().list(Credentials.USER_PRIVATE_KEY, UserHandle.myUserId());
-    }
-
-    protected boolean isAffiliatedUser() {
-        IDevicePolicyManager pm = IDevicePolicyManager.Stub.asInterface(
-                ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
-        boolean result = false;
-        try {
-            result = pm.isAffiliatedUser();
-        } catch (Exception e) {
-            Log.e(TAG, "failed to check user affiliation", e);
-        }
-        return result;
-    }
-}
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index f256396..8fb61c2 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -200,12 +200,6 @@
     @VisibleForTesting
     public static final int LINK_CONFIGURATION_BSSID_MATCH_LENGTH = 16;
     /**
-     * Flags to be passed in for |canModifyNetwork| to decide if the change is minor and can
-     * bypass the lockdown checks.
-     */
-    private static final boolean ALLOW_LOCKDOWN_CHECK_BYPASS = true;
-    private static final boolean DISALLOW_LOCKDOWN_CHECK_BYPASS = false;
-    /**
      * Log tag for this class.
      */
     private static final String TAG = "WifiConfigManager";
@@ -646,9 +640,8 @@
      *
      * @param config         WifiConfiguration object corresponding to the network to be modified.
      * @param uid            UID of the app requesting the modification.
-     * @param ignoreLockdown Ignore the configuration lockdown checks for connection attempts.
      */
-    private boolean canModifyNetwork(WifiConfiguration config, int uid, boolean ignoreLockdown) {
+    private boolean canModifyNetwork(WifiConfiguration config, int uid) {
         // System internals can always update networks; they're typically only
         // making meteredHint or meteredOverride changes
         if (uid == Process.SYSTEM_UID) {
@@ -685,12 +678,6 @@
 
         final boolean isCreator = (config.creatorUid == uid);
 
-        // Check if the |uid| holds the |NETWORK_SETTINGS| permission if the caller asks us to
-        // bypass the lockdown checks.
-        if (ignoreLockdown) {
-            return mWifiPermissionsUtil.checkNetworkSettingsPermission(uid);
-        }
-
         // Check if device has DPM capability. If it has and |dpmi| is still null, then we
         // treat this case with suspicion and bail out.
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)
@@ -980,7 +967,7 @@
                 return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
             }
             // Check for the app's permission before we let it update this network.
-            if (!canModifyNetwork(existingInternalConfig, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+            if (!canModifyNetwork(existingInternalConfig, uid)) {
                 Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
                         + config.configKey());
                 return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
@@ -1107,9 +1094,10 @@
      * Removes the specified network configuration from our database.
      *
      * @param config provided WifiConfiguration object.
+     * @param uid UID of the app requesting the network deletion.
      * @return true if successful, false otherwise.
      */
-    private boolean removeNetworkInternal(WifiConfiguration config) {
+    private boolean removeNetworkInternal(WifiConfiguration config, int uid) {
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "Removing network " + config.getPrintableSsid());
         }
@@ -1127,7 +1115,9 @@
 
         localLog("removeNetworkInternal: removed config."
                 + " netId=" + config.networkId
-                + " configKey=" + config.configKey());
+                + " configKey=" + config.configKey()
+                + " uid=" + Integer.toString(uid)
+                + " name=" + mContext.getPackageManager().getNameForUid(uid));
         return true;
     }
 
@@ -1147,12 +1137,12 @@
         if (config == null) {
             return false;
         }
-        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+        if (!canModifyNetwork(config, uid)) {
             Log.e(TAG, "UID " + uid + " does not have permission to delete configuration "
                     + config.configKey());
             return false;
         }
-        if (!removeNetworkInternal(config)) {
+        if (!removeNetworkInternal(config, uid)) {
             Log.e(TAG, "Failed to remove network " + config.getPrintableSsid());
             return false;
         }
@@ -1480,7 +1470,7 @@
         if (config == null) {
             return false;
         }
-        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+        if (!canModifyNetwork(config, uid)) {
             Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
                     + config.configKey());
             return false;
@@ -1515,7 +1505,7 @@
         if (config == null) {
             return false;
         }
-        if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
+        if (!canModifyNetwork(config, uid)) {
             Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
                     + config.configKey());
             return false;
@@ -1532,18 +1522,13 @@
     }
 
     /**
-     * Checks if the |uid| has the necessary permission to force a connection to a network
-     * and updates the last connected UID for the provided configuration.
+     * Updates the last connected UID for the provided configuration.
      *
      * @param networkId network ID corresponding to the network.
      * @param uid       uid of the app requesting the connection.
-     * @return true if |uid| has the necessary permission to trigger explicit connection to the
-     * network, false otherwise.
-     * Note: This returns true only for the system settings/sysui app which holds the
-     * {@link android.Manifest.permission#NETWORK_SETTINGS} permission. We don't want to let
-     * any other app force connection to a network.
+     * @return true if the network was found, false otherwise.
      */
-    public boolean checkAndUpdateLastConnectUid(int networkId, int uid) {
+    public boolean updateLastConnectUid(int networkId, int uid) {
         if (mVerboseLoggingEnabled) {
             Log.v(TAG, "Update network last connect UID for " + networkId);
         }
@@ -1555,11 +1540,6 @@
         if (config == null) {
             return false;
         }
-        if (!canModifyNetwork(config, uid, ALLOW_LOCKDOWN_CHECK_BYPASS)) {
-            Log.e(TAG, "UID " + uid + " does not have permission to update configuration "
-                    + config.configKey());
-            return false;
-        }
         config.lastConnectUid = uid;
         return true;
     }
diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
index dadc8a4..ea58549 100644
--- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java
+++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java
@@ -437,9 +437,10 @@
 
     /**
      * Check if the provided two networks are the same.
+     * Note: This does not check if network selection BSSID's are the same.
      *
-     * @param config      Configuration corresponding to a network.
-     * @param config1      Configuration corresponding to another network.
+     * @param config  Configuration corresponding to a network.
+     * @param config1 Configuration corresponding to another network.
      *
      * @return true if |config| and |config1| are the same network.
      *         false otherwise.
@@ -457,13 +458,6 @@
         if (!Objects.equals(config.SSID, config1.SSID)) {
             return false;
         }
-        String networkSelectionBSSID = config.getNetworkSelectionStatus()
-                .getNetworkSelectionBSSID();
-        String networkSelectionBSSID1 = config1.getNetworkSelectionStatus()
-                .getNetworkSelectionBSSID();
-        if (!Objects.equals(networkSelectionBSSID, networkSelectionBSSID1)) {
-            return false;
-        }
         if (WifiConfigurationUtil.hasCredentialChanged(config, config1)) {
             return false;
         }
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 4b8e682..dc48163 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.net.NetworkKey;
@@ -23,7 +24,6 @@
 import android.net.wifi.IApInterface;
 import android.net.wifi.IWifiScanner;
 import android.net.wifi.IWificond;
-import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiNetworkScoreCache;
 import android.net.wifi.WifiScanner;
@@ -85,7 +85,6 @@
     private final WifiVendorHal mWifiVendorHal;
     private final WifiStateMachine mWifiStateMachine;
     private final WifiSettingsStore mSettingsStore;
-    private final WifiCertManager mCertManager;
     private final OpenNetworkNotifier mOpenNetworkNotifier;
     private final WifiLockManager mLockManager;
     private final WifiController mWifiController;
@@ -116,11 +115,13 @@
     private final PasspointManager mPasspointManager;
     private final SIMAccessor mSimAccessor;
     private HandlerThread mWifiAwareHandlerThread;
+    private HandlerThread mRttHandlerThread;
     private HalDeviceManager mHalDeviceManager;
     private final IBatteryStats mBatteryStats;
     private final WifiStateTracker mWifiStateTracker;
     private final Runtime mJavaRuntime;
     private final SelfRecovery mSelfRecovery;
+    private final WakeupController mWakeupController;
 
     private final boolean mUseRealLogger;
 
@@ -162,7 +163,7 @@
         mWifiMetrics = new WifiMetrics(mClock, wifiStateMachineLooper, awareMetrics);
         // Modules interacting with Native.
         mWifiMonitor = new WifiMonitor(this);
-        mHalDeviceManager = new HalDeviceManager();
+        mHalDeviceManager = new HalDeviceManager(mClock);
         mWifiVendorHal =
                 new WifiVendorHal(mHalDeviceManager, mWifiStateMachineHandlerThread.getLooper());
         mSupplicantStaIfaceHal = new SupplicantStaIfaceHal(mContext, mWifiMonitor);
@@ -223,12 +224,14 @@
                 wifiStateMachineLooper, UserManager.get(mContext),
                 this, mBackupManagerProxy, mCountryCode, mWifiNative,
                 new WrongPasswordNotifier(mContext, mFrameworkFacade));
-        mCertManager = new WifiCertManager(mContext);
         mOpenNetworkNotifier = new OpenNetworkNotifier(mContext,
                 mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mClock, mWifiMetrics,
                 mWifiConfigManager, mWifiConfigStore, mWifiStateMachine,
                 new OpenNetworkRecommender(),
                 new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade));
+        mWakeupController = new WakeupController(mContext,
+                mWifiStateMachineHandlerThread.getLooper(), new WakeupLock(mWifiConfigManager),
+                mWifiConfigManager, mWifiConfigStore, mFrameworkFacade);
         mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
         mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
                 mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
@@ -300,10 +303,6 @@
         return mSettingsStore;
     }
 
-    public WifiCertManager getWifiCertManager() {
-        return mCertManager;
-    }
-
     public WifiLockManager getWifiLockManager() {
         return mLockManager;
     }
@@ -348,6 +347,10 @@
         return mPasspointManager;
     }
 
+    public WakeupController getWakeupController() {
+        return mWakeupController;
+    }
+
     public TelephonyManager makeTelephonyManager() {
         // may not be available when WiFi starts
         return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
@@ -369,17 +372,18 @@
      * changes
      * @param listener listener for SoftApManager
      * @param apInterface network interface to start hostapd against
-     * @param config softAp WifiConfiguration
+     * @param ifaceName name of the ap interface
+     * @param config SoftApModeConfiguration object holding the config and mode
      * @return an instance of SoftApManager
      */
     public SoftApManager makeSoftApManager(INetworkManagementService nmService,
                                            SoftApManager.Listener listener,
-                                           IApInterface apInterface,
-                                           WifiConfiguration config) {
-        return new SoftApManager(mWifiServiceHandlerThread.getLooper(),
-                                 mWifiNative, mCountryCode.getCountryCode(),
-                                 listener, apInterface, nmService,
-                                 mWifiApConfigStore, config, mWifiMetrics);
+                                           @NonNull IApInterface apInterface,
+                                           @NonNull String ifaceName,
+                                           @NonNull SoftApModeConfiguration config) {
+        return new SoftApManager(mContext, mWifiStateMachineHandlerThread.getLooper(),
+                mFrameworkFacade, mWifiNative, mCountryCode.getCountryCode(), listener, apInterface,
+                ifaceName, nmService, mWifiApConfigStore, config, mWifiMetrics);
     }
 
     /**
@@ -455,6 +459,19 @@
     }
 
     /**
+     * Returns a singleton instance of a HandlerThread for injection. Uses lazy initialization.
+     *
+     * TODO: share worker thread with other Wi-Fi handlers (b/27924886)
+     */
+    public HandlerThread getRttHandlerThread() {
+        if (mRttHandlerThread == null) { // lazy initialization
+            mRttHandlerThread = new HandlerThread("wifiRttService");
+            mRttHandlerThread.start();
+        }
+        return mRttHandlerThread;
+    }
+
+    /**
      * Returns a single instance of HalDeviceManager for injection.
      */
     public HalDeviceManager getHalDeviceManager() {
diff --git a/service/java/com/android/server/wifi/WifiLinkLayerStats.java b/service/java/com/android/server/wifi/WifiLinkLayerStats.java
new file mode 100644
index 0000000..0c58670
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiLinkLayerStats.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014 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 java.util.Arrays;
+
+/**
+ * A class representing link layer statistics collected over a Wifi Interface.
+ */
+
+/**
+ * {@hide}
+ */
+public class WifiLinkLayerStats {
+
+    /** Number of beacons received from our own AP */
+    public int beacon_rx;
+
+    /** RSSI of management frames */
+    public int rssi_mgmt;
+
+    /* Packet counters */
+
+    /** WME Best Effort Access Category received mpdu */
+    public long rxmpdu_be;
+    /** WME Best Effort Access Category transmitted mpdu */
+    public long txmpdu_be;
+    /** WME Best Effort Access Category lost mpdu */
+    public long lostmpdu_be;
+    /** WME Best Effort Access Category number of transmission retries */
+    public long retries_be;
+
+    /** WME Background Access Category received mpdu */
+    public long rxmpdu_bk;
+    /** WME Background Access Category transmitted mpdu */
+    public long txmpdu_bk;
+    /** WME Background Access Category lost mpdu */
+    public long lostmpdu_bk;
+    /** WME Background Access Category number of transmission retries */
+    public long retries_bk;
+
+    /** WME Video Access Category received mpdu */
+    public long rxmpdu_vi;
+    /** WME Video Access Category transmitted mpdu */
+    public long txmpdu_vi;
+    /** WME Video Access Category lost mpdu */
+    public long lostmpdu_vi;
+    /** WME Video Access Category number of transmission retries */
+    public long retries_vi;
+
+    /** WME Voice Access Category received mpdu */
+    public long rxmpdu_vo;
+    /** WME Voice Access Category transmitted mpdu */
+    public long txmpdu_vo;
+    /** WME Voice Access Category lost mpdu */
+    public long lostmpdu_vo;
+    /** WME Voice Access Category number of transmission retries */
+    public long retries_vo;
+
+    /**
+     * Cumulative milliseconds when radio is awake
+     */
+    public int on_time;
+    /**
+     * Cumulative milliseconds of active transmission
+     */
+    public int tx_time;
+    /**
+     * Cumulative milliseconds per level of active transmission
+     */
+    public int[] tx_time_per_level;
+    /**
+     * Cumulative milliseconds of active receive
+     */
+    public int rx_time;
+    /**
+     * Cumulative milliseconds when radio is awake due to scan
+     */
+    public int on_time_scan;
+
+    /**
+     * TimeStamp - absolute milliseconds from boot when these stats were sampled.
+     */
+    public long timeStampInMs;
+
+    @Override
+    public String toString() {
+        StringBuilder sbuf = new StringBuilder();
+        sbuf.append(" WifiLinkLayerStats: ").append('\n');
+
+        sbuf.append(" my bss beacon rx: ").append(Integer.toString(this.beacon_rx)).append('\n');
+        sbuf.append(" RSSI mgmt: ").append(Integer.toString(this.rssi_mgmt)).append('\n');
+        sbuf.append(" BE : ").append(" rx=").append(Long.toString(this.rxmpdu_be))
+                .append(" tx=").append(Long.toString(this.txmpdu_be))
+                .append(" lost=").append(Long.toString(this.lostmpdu_be))
+                .append(" retries=").append(Long.toString(this.retries_be)).append('\n');
+        sbuf.append(" BK : ").append(" rx=").append(Long.toString(this.rxmpdu_bk))
+                .append(" tx=").append(Long.toString(this.txmpdu_bk))
+                .append(" lost=").append(Long.toString(this.lostmpdu_bk))
+                .append(" retries=").append(Long.toString(this.retries_bk)).append('\n');
+        sbuf.append(" VI : ").append(" rx=").append(Long.toString(this.rxmpdu_vi))
+                .append(" tx=").append(Long.toString(this.txmpdu_vi))
+                .append(" lost=").append(Long.toString(this.lostmpdu_vi))
+                .append(" retries=").append(Long.toString(this.retries_vi)).append('\n');
+        sbuf.append(" VO : ").append(" rx=").append(Long.toString(this.rxmpdu_vo))
+                .append(" tx=").append(Long.toString(this.txmpdu_vo))
+                .append(" lost=").append(Long.toString(this.lostmpdu_vo))
+                .append(" retries=").append(Long.toString(this.retries_vo)).append('\n');
+        sbuf.append(" on_time : ").append(Integer.toString(this.on_time))
+                .append(" rx_time=").append(Integer.toString(this.rx_time))
+                .append(" scan_time=").append(Integer.toString(this.on_time_scan)).append('\n')
+                .append(" tx_time=").append(Integer.toString(this.tx_time))
+                .append(" tx_time_per_level=" + Arrays.toString(tx_time_per_level));
+        sbuf.append(" ts=" + timeStampInMs);
+        return sbuf.toString();
+    }
+
+}
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index 071b4f8..4e277a1 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -31,16 +31,21 @@
 import android.util.Pair;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.aware.WifiAwareMetrics;
+import com.android.server.wifi.hotspot2.ANQPNetworkKey;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.hotspot2.PasspointManager;
 import com.android.server.wifi.hotspot2.PasspointMatch;
 import com.android.server.wifi.hotspot2.PasspointProvider;
+import com.android.server.wifi.hotspot2.Utils;
 import com.android.server.wifi.nano.WifiMetricsProto;
 import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
 import com.android.server.wifi.nano.WifiMetricsProto.PnoScanMetrics;
+import com.android.server.wifi.nano.WifiMetricsProto.SoftApConnectedClientsEvent;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent.ConfigInfo;
+import com.android.server.wifi.nano.WifiMetricsProto.WpsMetrics;
 import com.android.server.wifi.util.InformationElementUtil;
 import com.android.server.wifi.util.ScanResultUtil;
 
@@ -49,9 +54,11 @@
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Calendar;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -76,6 +83,8 @@
     public static final long TIMEOUT_RSSI_DELTA_MILLIS =  3000;
     private static final int MIN_WIFI_SCORE = 0;
     private static final int MAX_WIFI_SCORE = NetworkAgent.WIFI_BASE_SCORE;
+    @VisibleForTesting
+    static final int LOW_WIFI_SCORE = 50; // Mobile data score
     private final Object mLock = new Object();
     private static final int MAX_CONNECTION_EVENTS = 256;
     // Largest bucket in the NumConnectableNetworkCount histogram,
@@ -84,12 +93,18 @@
     public static final int MAX_CONNECTABLE_BSSID_NETWORK_BUCKET = 50;
     public static final int MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET = 100;
     public static final int MAX_TOTAL_SCAN_RESULTS_BUCKET = 250;
+    public static final int MAX_TOTAL_PASSPOINT_APS_BUCKET = 50;
+    public static final int MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET = 20;
+    public static final int MAX_PASSPOINT_APS_PER_UNIQUE_ESS_BUCKET = 50;
     private static final int CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER = 1000;
+    // Max limit for number of soft AP related events, extra events will be dropped.
+    private static final int MAX_NUM_SOFT_AP_EVENTS = 256;
     private Clock mClock;
     private boolean mScreenOn;
     private int mWifiState;
     private WifiAwareMetrics mWifiAwareMetrics;
     private final PnoScanMetrics mPnoScanMetrics = new PnoScanMetrics();
+    private final WpsMetrics mWpsMetrics = new WpsMetrics();
     private Handler mHandler;
     private WifiConfigManager mWifiConfigManager;
     private WifiNetworkSelector mWifiNetworkSelector;
@@ -160,6 +175,17 @@
     private boolean mIsWifiNetworksAvailableNotificationOn = false;
     private int mNumOpenNetworkConnectMessageFailedToSend = 0;
     private int mNumOpenNetworkRecommendationUpdates = 0;
+    /** List of soft AP events related to number of connected clients in tethered mode */
+    private final List<SoftApConnectedClientsEvent> mSoftApEventListTethered = new ArrayList<>();
+    /** List of soft AP events related to number of connected clients in local only mode */
+    private final List<SoftApConnectedClientsEvent> mSoftApEventListLocalOnly = new ArrayList<>();
+
+    private final SparseIntArray mObservedHotspotR1ApInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2ApInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR1EssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2EssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR1ApsPerEssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2ApsPerEssInScanHistogram = new SparseIntArray();
 
     class RouterFingerPrint {
         private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
@@ -465,6 +491,78 @@
         }
     }
 
+    /**
+     * Increment total number of wps connection attempts
+     */
+    public void incrementWpsAttemptCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsAttempts++;
+        }
+    }
+
+    /**
+     * Increment total number of wps connection success
+     */
+    public void incrementWpsSuccessCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsSuccess++;
+        }
+    }
+
+    /**
+     * Increment total number of wps failure on start
+     */
+    public void incrementWpsStartFailureCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsStartFailure++;
+        }
+    }
+
+    /**
+     * Increment total number of wps overlap failure
+     */
+    public void incrementWpsOverlapFailureCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsOverlapFailure++;
+        }
+    }
+
+    /**
+     * Increment total number of wps timeout failure
+     */
+    public void incrementWpsTimeoutFailureCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsTimeoutFailure++;
+        }
+    }
+
+    /**
+     * Increment total number of other wps failure during connection
+     */
+    public void incrementWpsOtherConnectionFailureCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsOtherConnectionFailure++;
+        }
+    }
+
+    /**
+     * Increment total number of supplicant failure after wps
+     */
+    public void incrementWpsSupplicantFailureCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsSupplicantFailure++;
+        }
+    }
+
+    /**
+     * Increment total number of wps cancellation
+     */
+    public void incrementWpsCancellationCount() {
+        synchronized (mLock) {
+            mWpsMetrics.numWpsCancellation++;
+        }
+    }
+
     // Values used for indexing SystemStateEntries
     private static final int SCREEN_ON = 1;
     private static final int SCREEN_OFF = 0;
@@ -1041,10 +1139,14 @@
         }
     }
 
+    private boolean mWifiWins = false; // Based on scores, use wifi instead of mobile data?
+
     /**
      * Increments occurence of a particular wifi score calculated
      * in WifiScoreReport by current connected network. Scores are bounded
-     * within  [MIN_WIFI_SCORE, MAX_WIFI_SCORE] to limit size of SparseArray
+     * within  [MIN_WIFI_SCORE, MAX_WIFI_SCORE] to limit size of SparseArray.
+     *
+     * Also records events when the current score breaches significant thresholds.
      */
     public void incrementWifiScoreCount(int score) {
         if (score < MIN_WIFI_SCORE || score > MAX_WIFI_SCORE) {
@@ -1053,6 +1155,20 @@
         synchronized (mLock) {
             int count = mWifiScoreCounts.get(score);
             mWifiScoreCounts.put(score, count + 1);
+
+            boolean wifiWins = mWifiWins;
+            if (mWifiWins && score < LOW_WIFI_SCORE) {
+                wifiWins = false;
+            } else if (!mWifiWins && score > LOW_WIFI_SCORE) {
+                wifiWins = true;
+            }
+            mLastScore = score;
+            if (wifiWins != mWifiWins) {
+                mWifiWins = wifiWins;
+                StaEvent event = new StaEvent();
+                event.type = StaEvent.TYPE_SCORE_BREACH;
+                addStaEvent(event);
+            }
         }
     }
 
@@ -1092,6 +1208,53 @@
     }
 
     /**
+     * Adds a record indicating the current up state of soft AP
+     */
+    public void addSoftApUpChangedEvent(boolean isUp, int mode) {
+        SoftApConnectedClientsEvent event = new SoftApConnectedClientsEvent();
+        event.eventType = isUp ? SoftApConnectedClientsEvent.SOFT_AP_UP :
+                SoftApConnectedClientsEvent.SOFT_AP_DOWN;
+        event.numConnectedClients = 0;
+        addSoftApConnectedClientsEvent(event, mode);
+    }
+
+    /**
+     * Adds a record for current number of associated stations to soft AP
+     */
+    public void addSoftApNumAssociatedStationsChangedEvent(int numStations, int mode) {
+        SoftApConnectedClientsEvent event = new SoftApConnectedClientsEvent();
+        event.eventType = SoftApConnectedClientsEvent.NUM_CLIENTS_CHANGED;
+        event.numConnectedClients = numStations;
+        addSoftApConnectedClientsEvent(event, mode);
+    }
+
+    /**
+     * Adds a record to the corresponding event list based on mode param
+     */
+    private void addSoftApConnectedClientsEvent(SoftApConnectedClientsEvent event, int mode) {
+        synchronized (mLock) {
+            List<SoftApConnectedClientsEvent> softApEventList;
+            switch (mode) {
+                case WifiManager.IFACE_IP_MODE_TETHERED:
+                    softApEventList = mSoftApEventListTethered;
+                    break;
+                case WifiManager.IFACE_IP_MODE_LOCAL_ONLY:
+                    softApEventList = mSoftApEventListLocalOnly;
+                    break;
+                default:
+                    return;
+            }
+
+            if (softApEventList.size() > MAX_NUM_SOFT_AP_EVENTS) {
+                return;
+            }
+
+            event.timeStampMillis = mClock.getWallClockMillis();
+            softApEventList.add(event);
+        }
+    }
+
+    /**
      * Increment number of times the HAL crashed.
      */
     public void incrementNumHalCrashes() {
@@ -1192,6 +1355,10 @@
             Set<PasspointProvider> savedPasspointProviderProfiles =
                     new HashSet<PasspointProvider>();
             int savedPasspointProviderBssids = 0;
+            int passpointR1Aps = 0;
+            int passpointR2Aps = 0;
+            Map<ANQPNetworkKey, Integer> passpointR1UniqueEss = new HashMap<>();
+            Map<ANQPNetworkKey, Integer> passpointR2UniqueEss = new HashMap<>();
             for (ScanDetail scanDetail : scanDetails) {
                 NetworkDetail networkDetail = scanDetail.getNetworkDetail();
                 ScanResult scanResult = scanDetail.getScanResult();
@@ -1205,6 +1372,36 @@
                     providerMatch =
                             mPasspointManager.matchProvider(scanResult);
                     passpointProvider = providerMatch != null ? providerMatch.first : null;
+
+                    if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R1) {
+                        passpointR1Aps++;
+                    } else if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R2) {
+                        passpointR2Aps++;
+                    }
+
+                    long bssid = 0;
+                    boolean validBssid = false;
+                    try {
+                        bssid = Utils.parseMac(scanResult.BSSID);
+                        validBssid = true;
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG,
+                                "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
+                    }
+                    if (validBssid) {
+                        ANQPNetworkKey uniqueEss = ANQPNetworkKey.buildKey(scanResult.SSID, bssid,
+                                scanResult.hessid, networkDetail.getAnqpDomainID());
+                        if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R1) {
+                            Integer countObj = passpointR1UniqueEss.get(uniqueEss);
+                            int count = countObj == null ? 0 : countObj;
+                            passpointR1UniqueEss.put(uniqueEss, count + 1);
+                        } else if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R2) {
+                            Integer countObj = passpointR2UniqueEss.get(uniqueEss);
+                            int count = countObj == null ? 0 : countObj;
+                            passpointR2UniqueEss.put(uniqueEss, count + 1);
+                        }
+                    }
+
                 }
                 ssids.add(matchInfo);
                 bssids++;
@@ -1245,6 +1442,18 @@
                     savedPasspointProviderProfiles.size());
             incrementBssid(mAvailableSavedPasspointProviderBssidsInScanHistogram,
                     savedPasspointProviderBssids);
+            incrementTotalPasspointAps(mObservedHotspotR1ApInScanHistogram, passpointR1Aps);
+            incrementTotalPasspointAps(mObservedHotspotR2ApInScanHistogram, passpointR2Aps);
+            incrementTotalUniquePasspointEss(mObservedHotspotR1EssInScanHistogram,
+                    passpointR1UniqueEss.size());
+            incrementTotalUniquePasspointEss(mObservedHotspotR2EssInScanHistogram,
+                    passpointR2UniqueEss.size());
+            for (Integer count : passpointR1UniqueEss.values()) {
+                incrementPasspointPerUniqueEss(mObservedHotspotR1ApsPerEssInScanHistogram, count);
+            }
+            for (Integer count : passpointR2UniqueEss.values()) {
+                incrementPasspointPerUniqueEss(mObservedHotspotR2ApsPerEssInScanHistogram, count);
+            }
         }
     }
 
@@ -1561,6 +1770,53 @@
                         + mNumOpenNetworkRecommendationUpdates);
                 pw.println("mWifiLogProto.numOpenNetworkConnectMessageFailedToSend="
                         + mNumOpenNetworkConnectMessageFailedToSend);
+
+                pw.println("mWifiLogProto.observedHotspotR1ApInScanHistogram="
+                        + mObservedHotspotR1ApInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2ApInScanHistogram="
+                        + mObservedHotspotR2ApInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR1EssInScanHistogram="
+                        + mObservedHotspotR1EssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2EssInScanHistogram="
+                        + mObservedHotspotR2EssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR1ApsPerEssInScanHistogram="
+                        + mObservedHotspotR1ApsPerEssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram="
+                        + mObservedHotspotR2ApsPerEssInScanHistogram);
+
+                pw.println("mSoftApTetheredEvents:");
+                for (SoftApConnectedClientsEvent event : mSoftApEventListTethered) {
+                    StringBuilder eventLine = new StringBuilder();
+                    eventLine.append("event_type=" + event.eventType);
+                    eventLine.append(",time_stamp_millis=" + event.timeStampMillis);
+                    eventLine.append(",num_connected_clients=" + event.numConnectedClients);
+                    pw.println(eventLine.toString());
+                }
+                pw.println("mSoftApLocalOnlyEvents:");
+                for (SoftApConnectedClientsEvent event : mSoftApEventListLocalOnly) {
+                    StringBuilder eventLine = new StringBuilder();
+                    eventLine.append("event_type=" + event.eventType);
+                    eventLine.append(",time_stamp_millis=" + event.timeStampMillis);
+                    eventLine.append(",num_connected_clients=" + event.numConnectedClients);
+                    pw.println(eventLine.toString());
+                }
+
+                pw.println("mWpsMetrics.numWpsAttempts="
+                        + mWpsMetrics.numWpsAttempts);
+                pw.println("mWpsMetrics.numWpsSuccess="
+                        + mWpsMetrics.numWpsSuccess);
+                pw.println("mWpsMetrics.numWpsStartFailure="
+                        + mWpsMetrics.numWpsStartFailure);
+                pw.println("mWpsMetrics.numWpsOverlapFailure="
+                        + mWpsMetrics.numWpsOverlapFailure);
+                pw.println("mWpsMetrics.numWpsTimeoutFailure="
+                        + mWpsMetrics.numWpsTimeoutFailure);
+                pw.println("mWpsMetrics.numWpsOtherConnectionFailure="
+                        + mWpsMetrics.numWpsOtherConnectionFailure);
+                pw.println("mWpsMetrics.numWpsSupplicantFailure="
+                        + mWpsMetrics.numWpsSupplicantFailure);
+                pw.println("mWpsMetrics.numWpsCancellation="
+                        + mWpsMetrics.numWpsCancellation);
             }
         }
     }
@@ -1818,6 +2074,34 @@
                     mNumOpenNetworkRecommendationUpdates;
             mWifiLogProto.numOpenNetworkConnectMessageFailedToSend =
                     mNumOpenNetworkConnectMessageFailedToSend;
+
+            mWifiLogProto.observedHotspotR1ApsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR1ApInScanHistogram);
+            mWifiLogProto.observedHotspotR2ApsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR2ApInScanHistogram);
+            mWifiLogProto.observedHotspotR1EssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR1EssInScanHistogram);
+            mWifiLogProto.observedHotspotR2EssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR2EssInScanHistogram);
+            mWifiLogProto.observedHotspotR1ApsPerEssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                            mObservedHotspotR1ApsPerEssInScanHistogram);
+            mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                            mObservedHotspotR2ApsPerEssInScanHistogram);
+
+            if (mSoftApEventListTethered.size() > 0) {
+                mWifiLogProto.softApConnectedClientsEventsTethered =
+                        mSoftApEventListTethered.toArray(
+                        mWifiLogProto.softApConnectedClientsEventsTethered);
+            }
+            if (mSoftApEventListLocalOnly.size() > 0) {
+                mWifiLogProto.softApConnectedClientsEventsLocalOnly =
+                        mSoftApEventListLocalOnly.toArray(
+                        mWifiLogProto.softApConnectedClientsEventsLocalOnly);
+            }
+
+            mWifiLogProto.wpsMetrics = mWpsMetrics;
         }
     }
 
@@ -1872,6 +2156,15 @@
             mConnectToNetworkNotificationActionCount.clear();
             mNumOpenNetworkRecommendationUpdates = 0;
             mNumOpenNetworkConnectMessageFailedToSend = 0;
+            mObservedHotspotR1ApInScanHistogram.clear();
+            mObservedHotspotR2ApInScanHistogram.clear();
+            mObservedHotspotR1EssInScanHistogram.clear();
+            mObservedHotspotR2EssInScanHistogram.clear();
+            mObservedHotspotR1ApsPerEssInScanHistogram.clear();
+            mObservedHotspotR2ApsPerEssInScanHistogram.clear();
+            mSoftApEventListTethered.clear();
+            mSoftApEventListLocalOnly.clear();
+            mWpsMetrics.clear();
         }
     }
 
@@ -1890,6 +2183,7 @@
     public void setWifiState(int wifiState) {
         synchronized (mLock) {
             mWifiState = wifiState;
+            mWifiWins = (wifiState == WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
         }
     }
 
@@ -1995,6 +2289,7 @@
             case StaEvent.TYPE_CONNECT_NETWORK:
             case StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK:
             case StaEvent.TYPE_FRAMEWORK_DISCONNECT:
+            case StaEvent.TYPE_SCORE_BREACH:
                 break;
             default:
                 Log.e(TAG, "Unknown StaEvent:" + type);
@@ -2015,10 +2310,12 @@
         staEvent.lastFreq = mLastPollFreq;
         staEvent.lastLinkSpeed = mLastPollLinkSpeed;
         staEvent.supplicantStateChangesBitmask = mSupplicantStateChangeBitmask;
+        staEvent.lastScore = mLastScore;
         mSupplicantStateChangeBitmask = 0;
         mLastPollRssi = -127;
         mLastPollFreq = -1;
         mLastPollLinkSpeed = -1;
+        mLastScore = -1;
         mStaEventList.add(new StaEventWithTime(staEvent, mClock.getWallClockMillis()));
         // Prune StaEventList if it gets too long
         if (mStaEventList.size() > MAX_STA_EVENTS) mStaEventList.remove();
@@ -2174,6 +2471,9 @@
                         .append(" reason=")
                         .append(frameworkDisconnectReasonToString(event.frameworkDisconnectReason));
                 break;
+            case StaEvent.TYPE_SCORE_BREACH:
+                sb.append("SCORE_BREACH");
+                break;
             default:
                 sb.append("UNKNOWN " + event.type + ":");
                 break;
@@ -2181,6 +2481,7 @@
         if (event.lastRssi != -127) sb.append(" lastRssi=").append(event.lastRssi);
         if (event.lastFreq != -1) sb.append(" lastFreq=").append(event.lastFreq);
         if (event.lastLinkSpeed != -1) sb.append(" lastLinkSpeed=").append(event.lastLinkSpeed);
+        if (event.lastScore != -1) sb.append(" lastScore=").append(event.lastScore);
         if (event.supplicantStateChangesBitmask != 0) {
             sb.append(", ").append(supplicantStateChangesBitmaskToString(
                     event.supplicantStateChangesBitmask));
@@ -2243,11 +2544,12 @@
         return sb.toString();
     }
 
-    public static final int MAX_STA_EVENTS = 512;
+    public static final int MAX_STA_EVENTS = 768;
     private LinkedList<StaEventWithTime> mStaEventList = new LinkedList<StaEventWithTime>();
     private int mLastPollRssi = -127;
     private int mLastPollLinkSpeed = -1;
     private int mLastPollFreq = -1;
+    private int mLastScore = -1;
 
     /**
      * Converts the first 31 bits of a BitSet to a little endian int
@@ -2272,6 +2574,15 @@
     private void incrementTotalScanSsids(SparseIntArray sia, int element) {
         increment(sia, Math.min(element, MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET));
     }
+    private void incrementTotalPasspointAps(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_PASSPOINT_APS_BUCKET));
+    }
+    private void incrementTotalUniquePasspointEss(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET));
+    }
+    private void incrementPasspointPerUniqueEss(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_PASSPOINT_APS_PER_UNIQUE_ESS_BUCKET));
+    }
     private void increment(SparseIntArray sia, int element) {
         int count = sia.get(element);
         sia.put(element, count + 1);
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 5b12a36..3118761 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.net.apf.ApfCapabilities;
 import android.net.wifi.IApInterface;
@@ -24,7 +25,6 @@
 import android.net.wifi.RttManager.ResponderConfig;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiLinkLayerStats;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiWakeReasonAndCounts;
 import android.os.SystemClock;
@@ -105,12 +105,12 @@
     * @return Pair of <Integer, IClientInterface> to indicate the status and the associated wificond
     * client interface binder handler (will be null on failure).
     */
-    public Pair<Integer, IClientInterface> setupForClientMode() {
+    public Pair<Integer, IClientInterface> setupForClientMode(@NonNull String ifaceName) {
         if (!startHalIfNecessary(true)) {
             Log.e(mTAG, "Failed to start HAL for client mode");
             return Pair.create(SETUP_FAILURE_HAL, null);
         }
-        IClientInterface iClientInterface = mWificondControl.setupDriverForClientMode();
+        IClientInterface iClientInterface = mWificondControl.setupInterfaceForClientMode(ifaceName);
         if (iClientInterface == null) {
             return Pair.create(SETUP_FAILURE_WIFICOND, null);
         }
@@ -126,12 +126,12 @@
      * @return Pair of <Integer, IApInterface> to indicate the status and the associated wificond
      * AP interface binder handler (will be null on failure).
      */
-    public Pair<Integer, IApInterface> setupForSoftApMode() {
+    public Pair<Integer, IApInterface> setupForSoftApMode(@NonNull String ifaceName) {
         if (!startHalIfNecessary(false)) {
             Log.e(mTAG, "Failed to start HAL for AP mode");
             return Pair.create(SETUP_FAILURE_HAL, null);
         }
-        IApInterface iApInterface = mWificondControl.setupDriverForSoftApMode();
+        IApInterface iApInterface = mWificondControl.setupInterfaceForSoftApMode(ifaceName);
         if (iApInterface == null) {
             return Pair.create(SETUP_FAILURE_WIFICOND, null);
         }
@@ -152,6 +152,105 @@
         }
     }
 
+    /**
+     * TODO(b/69426063): NEW API Surface for interface management. This will eventually
+     * deprecate the other interface management API's above. But, for now there will be
+     * some duplication to ease transition.
+     */
+    /**
+     * Initialize the native modules.
+     *
+     * @return true on success, false otherwise.
+     */
+    public boolean initialize() {
+        return false;
+    }
+
+    /**
+     * Callback to notify when the status of one of the native daemons
+     * (wificond, wpa_supplicant & vendor HAL) changes.
+     */
+    public interface StatusListener {
+        /**
+         * @param allReady Indicates if all the native daemons are ready for operation or not.
+         */
+        void onStatusChanged(boolean allReady);
+    }
+
+    /**
+     * Register a StatusListener to get notified about any status changes from the native daemons.
+     *
+     * It is safe to re-register the same callback object - duplicates are detected and only a
+     * single copy kept.
+     *
+     * @param listener StatusListener listener object.
+     */
+    public void registerStatusListener(@NonNull StatusListener listener) {
+    }
+
+    /**
+     * Callback to notify when the associated interface is destroyed, up or down.
+     */
+    public interface InterfaceCallback {
+        /**
+         * Interface destroyed by HalDeviceManager.
+         *
+         * @param ifaceName Name of the iface.
+         */
+        void onDestroyed(String ifaceName);
+
+        /**
+         * Interface is up.
+         *
+         * @param ifaceName Name of the iface.
+         */
+        void onUp(String ifaceName);
+
+        /**
+         * Interface is down.
+         *
+         * @param ifaceName Name of the iface.
+         */
+        void onDown(String ifaceName);
+    }
+
+    /**
+     * Setup an interface for Client mode operations.
+     *
+     * This method configures an interface in STA mode in all the native daemons
+     * (wificond, wpa_supplicant & vendor HAL).
+     *
+     * @param interfaceCallback Associated callback for notifying status changes for the iface.
+     * @return Returns the name of the allocated interface, will be null on failure.
+     */
+    public String setupInterfaceForClientMode(@NonNull InterfaceCallback interfaceCallback) {
+        return null;
+    }
+
+    /**
+     * Setup an interface for Soft AP mode operations.
+     *
+     * This method configures an interface in AP mode in all the native daemons
+     * (wificond, wpa_supplicant & vendor HAL).
+     *
+     * @param interfaceCallback Associated callback for notifying status changes for the iface.
+     * @return Returns the name of the allocated interface, will be null on failure.
+     */
+    public String setupInterfaceForSoftApMode(@NonNull InterfaceCallback interfaceCallback) {
+        return null;
+    }
+
+    /**
+     * Teardown an interface in Client/AP mode.
+     *
+     * This method tears down the associated interface from all the native daemons
+     * (wificond, wpa_supplicant & vendor HAL).
+     *
+     * @param ifaceName Name of the interface.
+     */
+    public void teardownInterface(@NonNull String ifaceName) {
+    }
+
     /********************************************************
      * Wificond operations
      ********************************************************/
@@ -178,6 +277,32 @@
     }
 
     /**
+     * Callback to notify wificond death.
+     */
+    public interface WificondDeathEventHandler {
+        /**
+         * Invoked when the wificond dies.
+         */
+        void onDeath();
+    }
+
+    /**
+     * Registers a death notification for wificond.
+     * @return Returns true on success.
+     */
+    public boolean registerWificondDeathHandler(@NonNull WificondDeathEventHandler handler) {
+        return mWificondControl.registerDeathHandler(handler);
+    }
+
+    /**
+     * Deregisters a death notification for wificond.
+     * @return Returns true on success.
+     */
+    public boolean deregisterWificondDeathHandler() {
+        return mWificondControl.deregisterDeathHandler();
+    }
+
+    /**
     * Disable wpa_supplicant via wificond.
     * @return Returns true on success.
     */
@@ -199,7 +324,7 @@
     * Returns null on failure.
     */
     public SignalPollResult signalPoll() {
-        return mWificondControl.signalPoll();
+        return mWificondControl.signalPoll(mInterfaceName);
     }
 
     /**
@@ -208,7 +333,23 @@
     * Returns null on failure.
     */
     public TxPacketCounters getTxPacketCounters() {
-        return mWificondControl.getTxPacketCounters();
+        return mWificondControl.getTxPacketCounters(mInterfaceName);
+    }
+
+    /**
+     * Query the list of valid frequencies for the provided band.
+     * The result depends on the on the country code that has been set.
+     *
+     * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
+     * The following bands are supported:
+     * WifiScanner.WIFI_BAND_24_GHZ
+     * WifiScanner.WIFI_BAND_5_GHZ
+     * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
+     * @return frequencies vector of valid frequencies (MHz), or null for error.
+     * @throws IllegalArgumentException if band is not recognized.
+     */
+    public int [] getChannelsForBand(int band) {
+        return mWificondControl.getChannelsForBand(band);
     }
 
     /**
@@ -218,7 +359,7 @@
      * @return Returns true on success.
      */
     public boolean scan(Set<Integer> freqs, Set<String> hiddenNetworkSSIDs) {
-        return mWificondControl.scan(freqs, hiddenNetworkSSIDs);
+        return mWificondControl.scan(mInterfaceName, freqs, hiddenNetworkSSIDs);
     }
 
     /**
@@ -227,7 +368,8 @@
      * Returns an empty ArrayList on failure.
      */
     public ArrayList<ScanDetail> getScanResults() {
-        return mWificondControl.getScanResults(WificondControl.SCAN_TYPE_SINGLE_SCAN);
+        return mWificondControl.getScanResults(
+                mInterfaceName, WificondControl.SCAN_TYPE_SINGLE_SCAN);
     }
 
     /**
@@ -236,7 +378,7 @@
      * Returns an empty ArrayList on failure.
      */
     public ArrayList<ScanDetail> getPnoScanResults() {
-        return mWificondControl.getScanResults(WificondControl.SCAN_TYPE_PNO_SCAN);
+        return mWificondControl.getScanResults(mInterfaceName, WificondControl.SCAN_TYPE_PNO_SCAN);
     }
 
     /**
@@ -245,7 +387,7 @@
      * @return true on success.
      */
     public boolean startPnoScan(PnoSettings pnoSettings) {
-        return mWificondControl.startPnoScan(pnoSettings);
+        return mWificondControl.startPnoScan(mInterfaceName, pnoSettings);
     }
 
     /**
@@ -253,7 +395,37 @@
      * @return true on success.
      */
     public boolean stopPnoScan() {
-        return mWificondControl.stopPnoScan();
+        return mWificondControl.stopPnoScan(mInterfaceName);
+    }
+
+    /**
+     * Callbacks for SoftAp interface.
+     */
+    public interface SoftApListener {
+        /**
+         * Invoked when the number of associated stations changes.
+         */
+        void onNumAssociatedStationsChanged(int numStations);
+    }
+
+    /**
+     * Start Soft AP operation using the provided configuration.
+     *
+     * @param config Configuration to use for the soft ap created.
+     * @param listener Callback for AP events.
+     * @return true on success, false otherwise.
+     */
+    public boolean startSoftAp(WifiConfiguration config, SoftApListener listener) {
+        return mWificondControl.startSoftAp(mInterfaceName, config, listener);
+    }
+
+    /**
+     * Stop the ongoing Soft AP operation.
+     *
+     * @return true on success, false otherwise.
+     */
+    public boolean stopSoftAp() {
+        return mWificondControl.stopSoftAp(mInterfaceName);
     }
 
     /********************************************************
@@ -261,7 +433,26 @@
      ********************************************************/
 
     /**
-     * This method is called repeatedly until the connection to wpa_supplicant is established.
+     * Callback to notify supplicant death.
+     */
+    public interface SupplicantDeathEventHandler {
+        /**
+         * Invoked when the supplicant dies.
+         */
+        void onDeath();
+    }
+
+    /**
+     * Registers a death notification for supplicant.
+     * @return Returns true on success.
+     */
+    public boolean registerSupplicantDeathHandler(@NonNull SupplicantDeathEventHandler handler) {
+        return mSupplicantStaIfaceHal.registerDeathHandler(handler);
+    }
+
+    /**
+     * This method is called repeatedly until the connection to wpa_supplicant is
+     * established and a STA iface is setup.
      *
      * @return true if connection is established, false otherwise.
      * TODO: Add unit tests for these once we remove the legacy code.
@@ -273,14 +464,19 @@
             return false;
         }
         // Check if the initialization is complete.
-        return mSupplicantStaIfaceHal.isInitializationComplete();
+        if (!mSupplicantStaIfaceHal.isInitializationComplete()) {
+            return false;
+        }
+        // Setup the STA iface once connection is established.
+        return mSupplicantStaIfaceHal.setupIface(mInterfaceName);
     }
 
     /**
      * Close supplicant connection.
      */
     public void closeSupplicantConnection() {
-        // Nothing to do for HIDL.
+        // Setup the STA iface once connection is established.
+        mSupplicantStaIfaceHal.teardownIface(mInterfaceName);
     }
 
     /**
@@ -298,7 +494,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean reconnect() {
-        return mSupplicantStaIfaceHal.reconnect();
+        return mSupplicantStaIfaceHal.reconnect(mInterfaceName);
     }
 
     /**
@@ -307,7 +503,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean reassociate() {
-        return mSupplicantStaIfaceHal.reassociate();
+        return mSupplicantStaIfaceHal.reassociate(mInterfaceName);
     }
 
     /**
@@ -316,7 +512,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean disconnect() {
-        return mSupplicantStaIfaceHal.disconnect();
+        return mSupplicantStaIfaceHal.disconnect(mInterfaceName);
     }
 
     /**
@@ -325,7 +521,7 @@
      * @return string containing the MAC address, or null on a failed call
      */
     public String getMacAddress() {
-        return mSupplicantStaIfaceHal.getMacAddress();
+        return mSupplicantStaIfaceHal.getMacAddress(mInterfaceName);
     }
 
     public static final int RX_FILTER_TYPE_V4_MULTICAST = 0;
@@ -355,10 +551,10 @@
      * The  SETSUSPENDOPT driver command overrides the filtering rules
      */
     public boolean startFilteringMulticastV4Packets() {
-        return mSupplicantStaIfaceHal.stopRxFilter()
+        return mSupplicantStaIfaceHal.stopRxFilter(mInterfaceName)
                 && mSupplicantStaIfaceHal.removeRxFilter(
-                RX_FILTER_TYPE_V4_MULTICAST)
-                && mSupplicantStaIfaceHal.startRxFilter();
+                        mInterfaceName, RX_FILTER_TYPE_V4_MULTICAST)
+                && mSupplicantStaIfaceHal.startRxFilter(mInterfaceName);
     }
 
     /**
@@ -366,10 +562,10 @@
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      */
     public boolean stopFilteringMulticastV4Packets() {
-        return mSupplicantStaIfaceHal.stopRxFilter()
+        return mSupplicantStaIfaceHal.stopRxFilter(mInterfaceName)
                 && mSupplicantStaIfaceHal.addRxFilter(
-                RX_FILTER_TYPE_V4_MULTICAST)
-                && mSupplicantStaIfaceHal.startRxFilter();
+                        mInterfaceName, RX_FILTER_TYPE_V4_MULTICAST)
+                && mSupplicantStaIfaceHal.startRxFilter(mInterfaceName);
     }
 
     /**
@@ -377,10 +573,10 @@
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      */
     public boolean startFilteringMulticastV6Packets() {
-        return mSupplicantStaIfaceHal.stopRxFilter()
+        return mSupplicantStaIfaceHal.stopRxFilter(mInterfaceName)
                 && mSupplicantStaIfaceHal.removeRxFilter(
-                RX_FILTER_TYPE_V6_MULTICAST)
-                && mSupplicantStaIfaceHal.startRxFilter();
+                        mInterfaceName, RX_FILTER_TYPE_V6_MULTICAST)
+                && mSupplicantStaIfaceHal.startRxFilter(mInterfaceName);
     }
 
     /**
@@ -388,10 +584,10 @@
      * @return {@code true} if the operation succeeded, {@code false} otherwise
      */
     public boolean stopFilteringMulticastV6Packets() {
-        return mSupplicantStaIfaceHal.stopRxFilter()
+        return mSupplicantStaIfaceHal.stopRxFilter(mInterfaceName)
                 && mSupplicantStaIfaceHal.addRxFilter(
-                RX_FILTER_TYPE_V6_MULTICAST)
-                && mSupplicantStaIfaceHal.startRxFilter();
+                        mInterfaceName, RX_FILTER_TYPE_V6_MULTICAST)
+                && mSupplicantStaIfaceHal.startRxFilter(mInterfaceName);
     }
 
     public static final int BLUETOOTH_COEXISTENCE_MODE_ENABLED  = 0;
@@ -406,7 +602,7 @@
       * @return Whether the mode was successfully set.
       */
     public boolean setBluetoothCoexistenceMode(int mode) {
-        return mSupplicantStaIfaceHal.setBtCoexistenceMode(mode);
+        return mSupplicantStaIfaceHal.setBtCoexistenceMode(mInterfaceName, mode);
     }
 
     /**
@@ -418,7 +614,8 @@
      * @return {@code true} if the command succeeded, {@code false} otherwise.
      */
     public boolean setBluetoothCoexistenceScanMode(boolean setCoexScanMode) {
-        return mSupplicantStaIfaceHal.setBtCoexistenceScanModeEnabled(setCoexScanMode);
+        return mSupplicantStaIfaceHal.setBtCoexistenceScanModeEnabled(
+                mInterfaceName, setCoexScanMode);
     }
 
     /**
@@ -428,7 +625,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setSuspendOptimizations(boolean enabled) {
-        return mSupplicantStaIfaceHal.setSuspendModeEnabled(enabled);
+        return mSupplicantStaIfaceHal.setSuspendModeEnabled(mInterfaceName, enabled);
     }
 
     /**
@@ -438,7 +635,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setCountryCode(String countryCode) {
-        return mSupplicantStaIfaceHal.setCountryCode(countryCode);
+        return mSupplicantStaIfaceHal.setCountryCode(mInterfaceName, countryCode);
     }
 
     /**
@@ -449,10 +646,10 @@
      */
     public void startTdls(String macAddr, boolean enable) {
         if (enable) {
-            mSupplicantStaIfaceHal.initiateTdlsDiscover(macAddr);
-            mSupplicantStaIfaceHal.initiateTdlsSetup(macAddr);
+            mSupplicantStaIfaceHal.initiateTdlsDiscover(mInterfaceName, macAddr);
+            mSupplicantStaIfaceHal.initiateTdlsSetup(mInterfaceName, macAddr);
         } else {
-            mSupplicantStaIfaceHal.initiateTdlsTeardown(macAddr);
+            mSupplicantStaIfaceHal.initiateTdlsTeardown(mInterfaceName, macAddr);
         }
     }
 
@@ -463,7 +660,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean startWpsPbc(String bssid) {
-        return mSupplicantStaIfaceHal.startWpsPbc(bssid);
+        return mSupplicantStaIfaceHal.startWpsPbc(mInterfaceName, bssid);
     }
 
     /**
@@ -473,7 +670,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean startWpsPinKeypad(String pin) {
-        return mSupplicantStaIfaceHal.startWpsPinKeypad(pin);
+        return mSupplicantStaIfaceHal.startWpsPinKeypad(mInterfaceName, pin);
     }
 
     /**
@@ -483,7 +680,7 @@
      * @return new pin generated on success, null otherwise.
      */
     public String startWpsPinDisplay(String bssid) {
-        return mSupplicantStaIfaceHal.startWpsPinDisplay(bssid);
+        return mSupplicantStaIfaceHal.startWpsPinDisplay(mInterfaceName, bssid);
     }
 
     /**
@@ -493,7 +690,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setExternalSim(boolean external) {
-        return mSupplicantStaIfaceHal.setExternalSim(external);
+        return mSupplicantStaIfaceHal.setExternalSim(mInterfaceName, external);
     }
 
     /**
@@ -512,11 +709,14 @@
      */
     public boolean simAuthResponse(int id, String type, String response) {
         if (SIM_AUTH_RESP_TYPE_GSM_AUTH.equals(type)) {
-            return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthResponse(response);
+            return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthResponse(
+                    mInterfaceName, response);
         } else if (SIM_AUTH_RESP_TYPE_UMTS_AUTH.equals(type)) {
-            return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthResponse(response);
+            return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthResponse(
+                    mInterfaceName, response);
         } else if (SIM_AUTH_RESP_TYPE_UMTS_AUTS.equals(type)) {
-            return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAutsResponse(response);
+            return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAutsResponse(
+                    mInterfaceName, response);
         } else {
             return false;
         }
@@ -528,7 +728,7 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean simAuthFailedResponse(int id) {
-        return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthFailure();
+        return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimGsmAuthFailure(mInterfaceName);
     }
 
     /**
@@ -537,7 +737,7 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean umtsAuthFailedResponse(int id) {
-        return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthFailure();
+        return mSupplicantStaIfaceHal.sendCurrentNetworkEapSimUmtsAuthFailure(mInterfaceName);
     }
 
     /**
@@ -547,7 +747,8 @@
      * @return true if succeeds, false otherwise.
      */
     public boolean simIdentityResponse(int id, String response) {
-        return mSupplicantStaIfaceHal.sendCurrentNetworkEapIdentityResponse(response);
+        return mSupplicantStaIfaceHal.sendCurrentNetworkEapIdentityResponse(
+                mInterfaceName, response);
     }
 
     /**
@@ -556,7 +757,7 @@
      * @return anonymous identity string if succeeds, null otherwise.
      */
     public String getEapAnonymousIdentity() {
-        return mSupplicantStaIfaceHal.getCurrentNetworkEapAnonymousIdentity();
+        return mSupplicantStaIfaceHal.getCurrentNetworkEapAnonymousIdentity(mInterfaceName);
     }
 
     /**
@@ -567,7 +768,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean startWpsRegistrar(String bssid, String pin) {
-        return mSupplicantStaIfaceHal.startWpsRegistrar(bssid, pin);
+        return mSupplicantStaIfaceHal.startWpsRegistrar(mInterfaceName, bssid, pin);
     }
 
     /**
@@ -576,7 +777,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean cancelWps() {
-        return mSupplicantStaIfaceHal.cancelWps();
+        return mSupplicantStaIfaceHal.cancelWps(mInterfaceName);
     }
 
     /**
@@ -586,7 +787,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setDeviceName(String name) {
-        return mSupplicantStaIfaceHal.setWpsDeviceName(name);
+        return mSupplicantStaIfaceHal.setWpsDeviceName(mInterfaceName, name);
     }
 
     /**
@@ -596,7 +797,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setDeviceType(String type) {
-        return mSupplicantStaIfaceHal.setWpsDeviceType(type);
+        return mSupplicantStaIfaceHal.setWpsDeviceType(mInterfaceName, type);
     }
 
     /**
@@ -606,7 +807,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setConfigMethods(String cfg) {
-        return mSupplicantStaIfaceHal.setWpsConfigMethods(cfg);
+        return mSupplicantStaIfaceHal.setWpsConfigMethods(mInterfaceName, cfg);
     }
 
     /**
@@ -616,7 +817,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setManufacturer(String value) {
-        return mSupplicantStaIfaceHal.setWpsManufacturer(value);
+        return mSupplicantStaIfaceHal.setWpsManufacturer(mInterfaceName, value);
     }
 
     /**
@@ -626,7 +827,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setModelName(String value) {
-        return mSupplicantStaIfaceHal.setWpsModelName(value);
+        return mSupplicantStaIfaceHal.setWpsModelName(mInterfaceName, value);
     }
 
     /**
@@ -636,7 +837,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setModelNumber(String value) {
-        return mSupplicantStaIfaceHal.setWpsModelNumber(value);
+        return mSupplicantStaIfaceHal.setWpsModelNumber(mInterfaceName, value);
     }
 
     /**
@@ -646,7 +847,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean setSerialNumber(String value) {
-        return mSupplicantStaIfaceHal.setWpsSerialNumber(value);
+        return mSupplicantStaIfaceHal.setWpsSerialNumber(mInterfaceName, value);
     }
 
     /**
@@ -655,7 +856,7 @@
      * @param enabled true to enable, false to disable.
      */
     public void setPowerSave(boolean enabled) {
-        mSupplicantStaIfaceHal.setPowerSave(enabled);
+        mSupplicantStaIfaceHal.setPowerSave(mInterfaceName, enabled);
     }
 
     /**
@@ -676,7 +877,7 @@
      * @return true if request is sent successfully, false otherwise.
      */
     public boolean enableStaAutoReconnect(boolean enable) {
-        return mSupplicantStaIfaceHal.enableAutoReconnect(enable);
+        return mSupplicantStaIfaceHal.enableAutoReconnect(mInterfaceName, enable);
     }
 
     /**
@@ -689,7 +890,7 @@
      */
     public boolean migrateNetworksFromSupplicant(Map<String, WifiConfiguration> configs,
                                                  SparseArray<Map<String, String>> networkExtras) {
-        return mSupplicantStaIfaceHal.loadNetworks(configs, networkExtras);
+        return mSupplicantStaIfaceHal.loadNetworks(mInterfaceName, configs, networkExtras);
     }
 
     /**
@@ -707,8 +908,8 @@
      */
     public boolean connectToNetwork(WifiConfiguration configuration) {
         // Abort ongoing scan before connect() to unblock connection request.
-        mWificondControl.abortScan();
-        return mSupplicantStaIfaceHal.connectToNetwork(configuration);
+        mWificondControl.abortScan(mInterfaceName);
+        return mSupplicantStaIfaceHal.connectToNetwork(mInterfaceName, configuration);
     }
 
     /**
@@ -726,8 +927,8 @@
      */
     public boolean roamToNetwork(WifiConfiguration configuration) {
         // Abort ongoing scan before connect() to unblock roaming request.
-        mWificondControl.abortScan();
-        return mSupplicantStaIfaceHal.roamToNetwork(configuration);
+        mWificondControl.abortScan(mInterfaceName);
+        return mSupplicantStaIfaceHal.roamToNetwork(mInterfaceName, configuration);
     }
 
     /**
@@ -747,7 +948,7 @@
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
     public boolean removeAllNetworks() {
-        return mSupplicantStaIfaceHal.removeAllNetworks();
+        return mSupplicantStaIfaceHal.removeAllNetworks(mInterfaceName);
     }
 
     /**
@@ -756,7 +957,7 @@
      * @return true if successful, false otherwise.
      */
     public boolean setConfiguredNetworkBSSID(String bssid) {
-        return mSupplicantStaIfaceHal.setCurrentNetworkBssid(bssid);
+        return mSupplicantStaIfaceHal.setCurrentNetworkBssid(mInterfaceName, bssid);
     }
 
     /**
@@ -779,7 +980,8 @@
         }
         ArrayList<Integer> hs20SubtypeList = new ArrayList<>();
         hs20SubtypeList.addAll(hs20Subtypes);
-        return mSupplicantStaIfaceHal.initiateAnqpQuery(bssid, anqpIdList, hs20SubtypeList);
+        return mSupplicantStaIfaceHal.initiateAnqpQuery(
+                mInterfaceName, bssid, anqpIdList, hs20SubtypeList);
     }
 
     /**
@@ -793,7 +995,7 @@
             Log.e(mTAG, "Invalid arguments for Icon request.");
             return false;
         }
-        return mSupplicantStaIfaceHal.initiateHs20IconQuery(bssid, fileName);
+        return mSupplicantStaIfaceHal.initiateHs20IconQuery(mInterfaceName, bssid, fileName);
     }
 
     /**
@@ -802,7 +1004,7 @@
      * @return Hex string corresponding to the WPS NFC token.
      */
     public String getCurrentNetworkWpsNfcConfigurationToken() {
-        return mSupplicantStaIfaceHal.getCurrentNetworkWpsNfcConfigurationToken();
+        return mSupplicantStaIfaceHal.getCurrentNetworkWpsNfcConfigurationToken(mInterfaceName);
     }
 
     /** Remove the request |networkId| from supplicant if it's the current network,
@@ -811,7 +1013,7 @@
      * @param networkId network id of the network to be removed from supplicant.
      */
     public void removeNetworkIfCurrent(int networkId) {
-        mSupplicantStaIfaceHal.removeNetworkIfCurrent(networkId);
+        mSupplicantStaIfaceHal.removeNetworkIfCurrent(mInterfaceName, networkId);
     }
 
     /********************************************************
@@ -845,7 +1047,11 @@
             Log.i(mTAG, "Vendor HAL not supported, Ignore start...");
             return true;
         }
-        return mWifiVendorHal.startVendorHal(isStaMode);
+        if (isStaMode) {
+            return mWifiVendorHal.startVendorHalSta();
+        } else {
+            return mWifiVendorHal.startVendorHalAp();
+        }
     }
 
     /**
@@ -882,7 +1088,7 @@
      * @return true for success. false for failure
      */
     public boolean getBgScanCapabilities(ScanCapabilities capabilities) {
-        return mWifiVendorHal.getBgScanCapabilities(capabilities);
+        return mWifiVendorHal.getBgScanCapabilities(mInterfaceName, capabilities);
     }
 
     public static class ChannelSettings {
@@ -1032,39 +1238,39 @@
      * @return true for success
      */
     public boolean startBgScan(ScanSettings settings, ScanEventHandler eventHandler) {
-        return mWifiVendorHal.startBgScan(settings, eventHandler);
+        return mWifiVendorHal.startBgScan(mInterfaceName, settings, eventHandler);
     }
 
     /**
      * Stops any ongoing backgound scan
      */
     public void stopBgScan() {
-        mWifiVendorHal.stopBgScan();
+        mWifiVendorHal.stopBgScan(mInterfaceName);
     }
 
     /**
      * Pauses an ongoing backgound scan
      */
     public void pauseBgScan() {
-        mWifiVendorHal.pauseBgScan();
+        mWifiVendorHal.pauseBgScan(mInterfaceName);
     }
 
     /**
      * Restarts a paused scan
      */
     public void restartBgScan() {
-        mWifiVendorHal.restartBgScan();
+        mWifiVendorHal.restartBgScan(mInterfaceName);
     }
 
     /**
      * Gets the latest scan results received.
      */
     public WifiScanner.ScanData[] getBgScanResults() {
-        return mWifiVendorHal.getBgScanResults();
+        return mWifiVendorHal.getBgScanResults(mInterfaceName);
     }
 
-    public WifiLinkLayerStats getWifiLinkLayerStats(String iface) {
-        return mWifiVendorHal.getWifiLinkLayerStats();
+    public WifiLinkLayerStats getWifiLinkLayerStats() {
+        return mWifiVendorHal.getWifiLinkLayerStats(mInterfaceName);
     }
 
     /**
@@ -1073,7 +1279,7 @@
      * @return bitmask defined by WifiManager.WIFI_FEATURE_*
      */
     public int getSupportedFeatureSet() {
-        return mWifiVendorHal.getSupportedFeatureSet();
+        return mWifiVendorHal.getSupportedFeatureSet(mInterfaceName);
     }
 
     public static interface RttEventHandler {
@@ -1130,28 +1336,7 @@
      * @return true for success
      */
     public boolean setScanningMacOui(byte[] oui) {
-        return mWifiVendorHal.setScanningMacOui(oui);
-    }
-
-    /**
-     * Query the list of valid frequencies for the provided band.
-     * The result depends on the on the country code that has been set.
-     *
-     * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
-     * @return frequencies vector of valid frequencies (MHz), or null for error.
-     * @throws IllegalArgumentException if band is not recognized.
-     */
-    public int [] getChannelsForBand(int band) {
-        return mWifiVendorHal.getChannelsForBand(band);
-    }
-
-    /**
-     * Indicates whether getChannelsForBand is supported.
-     *
-     * @return true if it is.
-     */
-    public boolean isGetChannelsForBandSupported() {
-        return mWifiVendorHal.isGetChannelsForBandSupported();
+        return mWifiVendorHal.setScanningMacOui(mInterfaceName, oui);
     }
 
     /**
@@ -1165,7 +1350,7 @@
      * Get the APF (Android Packet Filter) capabilities of the device
      */
     public ApfCapabilities getApfCapabilities() {
-        return mWifiVendorHal.getApfCapabilities();
+        return mWifiVendorHal.getApfCapabilities(mInterfaceName);
     }
 
     /**
@@ -1175,7 +1360,7 @@
      * @return true for success
      */
     public boolean installPacketFilter(byte[] filter) {
-        return mWifiVendorHal.installPacketFilter(filter);
+        return mWifiVendorHal.installPacketFilter(mInterfaceName, filter);
     }
 
     /**
@@ -1185,7 +1370,7 @@
      * @return true for success
      */
     public boolean setCountryCodeHal(String countryCode) {
-        return mWifiVendorHal.setCountryCodeHal(countryCode);
+        return mWifiVendorHal.setCountryCodeHal(mInterfaceName, countryCode);
     }
 
     //---------------------------------------------------------------------------------
@@ -1525,7 +1710,7 @@
      * @return true for success, false otherwise.
      */
     public boolean startPktFateMonitoring() {
-        return mWifiVendorHal.startPktFateMonitoring();
+        return mWifiVendorHal.startPktFateMonitoring(mInterfaceName);
     }
 
     /**
@@ -1534,14 +1719,14 @@
      * @return true for success, false otherwise.
      */
     public boolean getTxPktFates(TxFateReport[] reportBufs) {
-        return mWifiVendorHal.getTxPktFates(reportBufs);
+        return mWifiVendorHal.getTxPktFates(mInterfaceName, reportBufs);
     }
 
     /**
      * Fetch the most recent RX packet fates from the HAL. Fails unless HAL is started.
      */
     public boolean getRxPktFates(RxFateReport[] reportBufs) {
-        return mWifiVendorHal.getRxPktFates(reportBufs);
+        return mWifiVendorHal.getRxPktFates(mInterfaceName, reportBufs);
     }
 
     /**
@@ -1560,7 +1745,7 @@
             Integer hexVal = Integer.parseInt(macAddrStr[i], 16);
             srcMac[i] = hexVal.byteValue();
         }
-        return mWifiVendorHal.startSendingOffloadedPacket(
+        return mWifiVendorHal.startSendingOffloadedPacket(mInterfaceName,
                 slot, srcMac, keepAlivePacket, period);
     }
 
@@ -1571,7 +1756,7 @@
      * @return 0 for success, -1 for error
      */
     public int stopSendingOffloadedPacket(int slot) {
-        return mWifiVendorHal.stopSendingOffloadedPacket(slot);
+        return mWifiVendorHal.stopSendingOffloadedPacket(mInterfaceName, slot);
     }
 
     public static interface WifiRssiEventHandler {
@@ -1588,11 +1773,12 @@
      */
     public int startRssiMonitoring(byte maxRssi, byte minRssi,
                                    WifiRssiEventHandler rssiEventHandler) {
-        return mWifiVendorHal.startRssiMonitoring(maxRssi, minRssi, rssiEventHandler);
+        return mWifiVendorHal.startRssiMonitoring(
+                mInterfaceName, maxRssi, minRssi, rssiEventHandler);
     }
 
     public int stopRssiMonitoring() {
-        return mWifiVendorHal.stopRssiMonitoring();
+        return mWifiVendorHal.stopRssiMonitoring(mInterfaceName);
     }
 
     /**
@@ -1611,7 +1797,7 @@
      * @return true for success, false otherwise.
      */
     public boolean configureNeighborDiscoveryOffload(boolean enabled) {
-        return mWifiVendorHal.configureNeighborDiscoveryOffload(enabled);
+        return mWifiVendorHal.configureNeighborDiscoveryOffload(mInterfaceName, enabled);
     }
 
     // Firmware roaming control.
@@ -1629,7 +1815,7 @@
      * @return true for success, false otherwise.
      */
     public boolean getRoamingCapabilities(RoamingCapabilities capabilities) {
-        return mWifiVendorHal.getRoamingCapabilities(capabilities);
+        return mWifiVendorHal.getRoamingCapabilities(mInterfaceName, capabilities);
     }
 
     /**
@@ -1644,7 +1830,7 @@
      * @return error code returned from HAL.
      */
     public int enableFirmwareRoaming(int state) {
-        return mWifiVendorHal.enableFirmwareRoaming(state);
+        return mWifiVendorHal.enableFirmwareRoaming(mInterfaceName, state);
     }
 
     /**
@@ -1660,7 +1846,7 @@
      */
     public boolean configureRoaming(RoamingConfig config) {
         Log.d(mTAG, "configureRoaming ");
-        return mWifiVendorHal.configureRoaming(config);
+        return mWifiVendorHal.configureRoaming(mInterfaceName, config);
     }
 
     /**
@@ -1669,7 +1855,7 @@
     public boolean resetRoamingConfiguration() {
         // Pass in an empty RoamingConfig object which translates to zero size
         // blacklist and whitelist to reset the firmware roaming configuration.
-        return mWifiVendorHal.configureRoaming(new RoamingConfig());
+        return mWifiVendorHal.configureRoaming(mInterfaceName, new RoamingConfig());
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/WifiNetworkSelector.java b/service/java/com/android/server/wifi/WifiNetworkSelector.java
index 071fc07..46ded1c 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSelector.java
@@ -152,9 +152,8 @@
         boolean hasQualifiedRssi =
                 (wifiInfo.is24GHz() && (currentRssi > mThresholdQualifiedRssi24))
                         || (wifiInfo.is5GHz() && (currentRssi > mThresholdQualifiedRssi5));
-        // getTxSuccessRate() and getRxSuccessRate() returns the packet rate in per 5 seconds unit.
-        boolean hasActiveStream = (wifiInfo.getTxSuccessRatePps() > mStayOnNetworkMinimumTxRate)
-                || (wifiInfo.getRxSuccessRatePps() > mStayOnNetworkMinimumRxRate);
+        boolean hasActiveStream = (wifiInfo.txSuccessRate > mStayOnNetworkMinimumTxRate)
+                || (wifiInfo.rxSuccessRate > mStayOnNetworkMinimumRxRate);
         if (hasQualifiedRssi && hasActiveStream) {
             localLog("Stay on current network because of good RSSI and ongoing traffic");
             return true;
diff --git a/service/java/com/android/server/wifi/WifiScoreReport.java b/service/java/com/android/server/wifi/WifiScoreReport.java
index 894d57c..1c258e1 100644
--- a/service/java/com/android/server/wifi/WifiScoreReport.java
+++ b/service/java/com/android/server/wifi/WifiScoreReport.java
@@ -49,11 +49,13 @@
 
     ConnectedScore mConnectedScore;
     ConnectedScore mAggressiveConnectedScore;
+    VelocityBasedConnectedScore mFancyConnectedScore;
 
     WifiScoreReport(Context context, WifiConfigManager wifiConfigManager, Clock clock) {
         mClock = clock;
         mConnectedScore = new LegacyConnectedScore(context, wifiConfigManager, clock);
         mAggressiveConnectedScore = new AggressiveConnectedScore(context, clock);
+        mFancyConnectedScore = new VelocityBasedConnectedScore(context, clock);
     }
 
     /**
@@ -76,6 +78,7 @@
         }
         mConnectedScore.reset();
         mAggressiveConnectedScore.reset();
+        mFancyConnectedScore.reset();
         if (mVerboseLoggingEnabled) Log.d(TAG, "reset");
     }
 
@@ -113,18 +116,20 @@
                                         int aggressiveHandover, WifiMetrics wifiMetrics) {
         int score;
 
-        long millis = mConnectedScore.getMillis();
+        long millis = mClock.getWallClockMillis();
 
         mConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
         mAggressiveConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
+        mFancyConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
 
         int s0 = mConnectedScore.generateScore();
         int s1 = mAggressiveConnectedScore.generateScore();
+        int s2 = mFancyConnectedScore.generateScore();
 
         if (aggressiveHandover == 0) {
-            score = s0;
+            score = s2;
         } else {
-            score = s1;
+            score = s2; // TODO Remove aggressive handover plumbing (b/27877641)
         }
 
         //sanitize boundaries
@@ -135,7 +140,7 @@
             score = 0;
         }
 
-        logLinkMetrics(wifiInfo, s0, s1);
+        logLinkMetrics(wifiInfo, millis, s0, s1, s2);
 
         //report score
         if (score != wifiInfo.score) {
@@ -163,29 +168,34 @@
     /**
      * Data logging for dumpsys
      */
-    private void logLinkMetrics(WifiInfo wifiInfo, int s0, int s1) {
-        long now = mClock.getWallClockMillis();
+    private void logLinkMetrics(WifiInfo wifiInfo, long now, int s0, int s1, int s2) {
         if (now < FIRST_REASONABLE_WALL_CLOCK) return;
         double rssi = wifiInfo.getRssi();
+        double filteredRssi = mFancyConnectedScore.getFilteredRssi();
+        double rssiThreshold = mFancyConnectedScore.getAdjustedRssiThreshold();
         int freq = wifiInfo.getFrequency();
         int linkSpeed = wifiInfo.getLinkSpeed();
         double txSuccessRate = wifiInfo.txSuccessRate;
         double txRetriesRate = wifiInfo.txRetriesRate;
         double txBadRate = wifiInfo.txBadRate;
         double rxSuccessRate = wifiInfo.rxSuccessRate;
+        String s;
         try {
             String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
-            String s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
-                    "%s,%d,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d",
-                    timestamp, mSessionNumber, rssi, freq, linkSpeed,
+            s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
+                    "%s,%d,%.1f,%.1f,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d",
+                    timestamp, mSessionNumber, rssi, filteredRssi, rssiThreshold, freq, linkSpeed,
                     txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
-                    s0, s1);
-            mLinkMetricsHistory.add(s);
+                    s0, s1, s2);
         } catch (Exception e) {
             Log.e(TAG, "format problem", e);
+            return;
         }
-        while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) {
-            mLinkMetricsHistory.removeFirst();
+        synchronized (mLinkMetricsHistory) {
+            mLinkMetricsHistory.add(s);
+            while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) {
+                mLinkMetricsHistory.removeFirst();
+            }
         }
     }
 
@@ -201,9 +211,15 @@
      * @param args unused
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("time,session,rssi,freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1");
-        for (String line : mLinkMetricsHistory) {
+        LinkedList<String> history;
+        synchronized (mLinkMetricsHistory) {
+            history = new LinkedList<>(mLinkMetricsHistory);
+        }
+        pw.println("time,session,rssi,filtered_rssi,rssi_threshold,"
+                + "freq,linkspeed,tx_good,tx_retry,tx_bad,rx_pps,s0,s1,s2");
+        for (String line : history) {
             pw.println(line);
         }
+        history.clear();
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index d2f90e0..0fb4bfb 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -69,10 +69,10 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConnectionStatistics;
 import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiLinkLayerStats;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
 import android.net.wifi.WifiScanner;
+import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.AsyncTask;
@@ -141,11 +141,6 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
-    // Dumpsys argument to enable/disable disconnect on IP reachability failures.
-    private static final String DUMP_ARG_SET_IPREACH_DISCONNECT = "set-ipreach-disconnect";
-    private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_ENABLED = "enabled";
-    private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_DISABLED = "disabled";
-
     // Default scan background throttling interval if not overriden in settings
     private static final long DEFAULT_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000;
 
@@ -175,8 +170,6 @@
     final WifiSettingsStore mSettingsStore;
     /* Logs connection events and some general router and scan stats */
     private final WifiMetrics mWifiMetrics;
-    /* Manages affiliated certificates for current user */
-    private final WifiCertManager mCertManager;
 
     private final WifiInjector mWifiInjector;
     /* Backup/Restore Module */
@@ -438,7 +431,6 @@
         mPowerManager = mContext.getSystemService(PowerManager.class);
         mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        mCertManager = mWifiInjector.getWifiCertManager();
         mWifiLockManager = mWifiInjector.getWifiLockManager();
         mWifiMulticastLockManager = mWifiInjector.getWifiMulticastLockManager();
         HandlerThread wifiServiceHandlerThread = mWifiInjector.getWifiServiceHandlerThread();
@@ -857,32 +849,6 @@
     }
 
     /**
-     * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
-     * @param wifiConfig SSID, security and channel details as
-     *        part of WifiConfiguration
-     * @param enabled true to enable and false to disable
-     */
-    @Override
-    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
-        enforceChangePermission();
-        mWifiPermissionsUtil.enforceTetherChangePermission(mContext);
-
-        mLog.info("setWifiApEnabled uid=% enable=%").c(Binder.getCallingUid()).c(enabled).flush();
-
-        if (mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
-            throw new SecurityException("DISALLOW_CONFIG_TETHERING is enabled for this user.");
-        }
-        // null wifiConfig is a meaningful input for CMD_SET_AP
-        if (wifiConfig == null || isValid(wifiConfig)) {
-            int mode = WifiManager.IFACE_IP_MODE_UNSPECIFIED;
-            SoftApModeConfiguration softApConfig = new SoftApModeConfiguration(mode, wifiConfig);
-            mWifiController.sendMessage(CMD_SET_AP, enabled ? 1 : 0, 0, softApConfig);
-        } else {
-            Slog.e(TAG, "Invalid WifiConfiguration");
-        }
-    }
-
-    /**
      * see {@link WifiManager#getWifiApState()}
      * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
      *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
@@ -1588,6 +1554,26 @@
     }
 
     /**
+     * Return the list of all matching Wifi configurations for this ScanResult.
+     *
+     * An empty list will be returned when no configurations are installed or if no configurations
+     * match the ScanResult.
+     *
+     * @param scanResult scanResult that represents the BSSID
+     * @return A list of {@link WifiConfiguration}
+     */
+    @Override
+    public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+        enforceAccessPermission();
+        mLog.info("getMatchingPasspointConfigurations uid=%").c(Binder.getCallingUid()).flush();
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)) {
+            throw new UnsupportedOperationException("Passpoint not enabled");
+        }
+        return mWifiStateMachine.getAllMatchingWifiConfigs(scanResult, mWifiStateMachineChannel);
+    }
+
+    /**
      * Returns list of OSU (Online Sign-Up) providers associated with the given Passpoint network.
      *
      * @param scanResult scanResult of the Passpoint AP
@@ -1744,12 +1730,19 @@
     @Override
     public WifiInfo getConnectionInfo(String callingPackage) {
         enforceAccessPermission();
-        mLog.info("getConnectionInfo uid=%").c(Binder.getCallingUid()).flush();
+        int uid = Binder.getCallingUid();
+        mLog.info("getConnectionInfo uid=%").c(uid).flush();
         /*
          * Make sure we have the latest information, by sending
          * a status request to the supplicant.
          */
-        return mWifiStateMachine.syncRequestConnectionInfo(callingPackage);
+        long ident = Binder.clearCallingIdentity();
+        try {
+            WifiInfo result = mWifiStateMachine.syncRequestConnectionInfo(callingPackage, uid);
+            return result;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
     }
 
     /**
@@ -1888,16 +1881,11 @@
     /**
      * Set the country code
      * @param countryCode ISO 3166 country code.
-     * @param persist {@code true} if the setting should be remembered.
      *
-     * The persist behavior exists so that wifi can fall back to the last
-     * persisted country code on a restart, when the locale information is
-     * not available from telephony.
      */
     @Override
-    public void setCountryCode(String countryCode, boolean persist) {
-        Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
-                " with persist set to " + persist);
+    public void setCountryCode(String countryCode) {
+        Slog.i(TAG, "WifiService trying to set country code to " + countryCode);
         enforceConnectivityInternalPermission();
         mLog.info("setCountryCode uid=%").c(Binder.getCallingUid()).flush();
         final long token = Binder.clearCallingIdentity();
@@ -2383,6 +2371,7 @@
     @Override
     public void enableVerboseLogging(int verbose) {
         enforceAccessPermission();
+        enforceNetworkSettingsPermission();
         mLog.info("enableVerboseLogging uid=% verbose=%")
                 .c(Binder.getCallingUid())
                 .c(verbose).flush();
@@ -2425,38 +2414,6 @@
         return mWifiStateMachine.getAggressiveHandover();
     }
 
-    @Override
-    public void setAllowScansWithTraffic(int enabled) {
-        enforceAccessPermission();
-        mLog.info("setAllowScansWithTraffic uid=% enabled=%")
-                .c(Binder.getCallingUid())
-                .c(enabled).flush();
-        mWifiStateMachine.setAllowScansWithTraffic(enabled);
-    }
-
-    @Override
-    public int getAllowScansWithTraffic() {
-        enforceAccessPermission();
-        mLog.info("getAllowScansWithTraffic uid=%").c(Binder.getCallingUid()).flush();
-        return mWifiStateMachine.getAllowScansWithTraffic();
-    }
-
-    @Override
-    public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
-        enforceChangePermission();
-        mLog.info("setEnableAutoJoinWhenAssociated uid=% enabled=%")
-                .c(Binder.getCallingUid())
-                .c(enabled).flush();
-        return mWifiStateMachine.setEnableAutoJoinWhenAssociated(enabled);
-    }
-
-    @Override
-    public boolean getEnableAutoJoinWhenAssociated() {
-        enforceAccessPermission();
-        mLog.info("getEnableAutoJoinWhenAssociated uid=%").c(Binder.getCallingUid()).flush();
-        return mWifiStateMachine.getEnableAutoJoinWhenAssociated();
-    }
-
     /* Return the Wifi Connection statistics object */
     @Override
     public WifiConnectionStatistics getConnectionStatistics() {
@@ -2564,14 +2521,6 @@
         return sb.toString();
     }
 
-    public void hideCertFromUnaffiliatedUsers(String alias) {
-        mCertManager.hideCertFromUnaffiliatedUsers(alias);
-    }
-
-    public String[] listClientCertsForCurrentUser() {
-        return mCertManager.listClientCertsForCurrentUser();
-    }
-
     /**
      * Enable/disable WifiConnectivityManager at runtime
      *
@@ -2674,4 +2623,33 @@
         restoreNetworks(wifiConfigurations);
         Slog.d(TAG, "Restored supplicant backup data");
     }
+
+    /**
+     * Starts subscription provisioning with a provider
+     *
+     * @param provider {@link OsuProvider} the provider to provision with
+     * @param callback {@link IProvisoningCallback} the callback object to inform status
+     */
+    @Override
+    public void startSubscriptionProvisioning(OsuProvider provider,
+            IProvisioningCallback callback) {
+        if (provider == null) {
+            throw new IllegalArgumentException("Provider must not be null");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        enforceNetworkSettingsPermission();
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)) {
+            throw new UnsupportedOperationException("Passpoint not enabled");
+        }
+        final int uid = Binder.getCallingUid();
+        mLog.trace("startSubscriptionProvisioning uid=%").c(uid).flush();
+        if (mWifiStateMachine.syncStartSubscriptionProvisioning(uid, provider,
+                callback, mWifiStateMachineChannel)) {
+            mLog.trace("Subscription provisioning started with %")
+                    .c(provider.toString()).flush();
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index ff3aea9..1521bb9 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -29,9 +29,7 @@
 import static android.telephony.TelephonyManager.CALL_STATE_IDLE;
 import static android.telephony.TelephonyManager.CALL_STATE_OFFHOOK;
 
-import android.Manifest;
 import android.app.ActivityManager;
-import android.app.AppGlobals;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
@@ -39,7 +37,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
@@ -71,18 +68,17 @@
 import android.net.wifi.WifiConnectionStatistics;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiLinkLayerStats;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.WpsInfo;
 import android.net.wifi.WpsResult;
 import android.net.wifi.WpsResult.Status;
+import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.os.BatteryStats;
-import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -128,6 +124,7 @@
 import com.android.server.wifi.util.TelephonyUtil.SimAuthRequestData;
 import com.android.server.wifi.util.TelephonyUtil.SimAuthResponseData;
 import com.android.server.wifi.util.WifiPermissionsUtil;
+import com.android.server.wifi.util.WifiPermissionsWrapper;
 
 import java.io.BufferedReader;
 import java.io.FileDescriptor;
@@ -181,9 +178,10 @@
 
     private static final String EXTRA_OSU_ICON_QUERY_BSSID = "BSSID";
     private static final String EXTRA_OSU_ICON_QUERY_FILENAME = "FILENAME";
+    private static final String EXTRA_OSU_PROVIDER = "OsuProvider";
 
     private boolean mVerboseLoggingEnabled = false;
-
+    private final WifiPermissionsWrapper mWifiPermissionsWrapper;
     /* debug flag, indicating if handling of ASSOCIATION_REJECT ended up blacklisting
      * the corresponding BSSID.
      */
@@ -250,8 +248,9 @@
     private String mLastBssid;
     private int mLastNetworkId; // The network Id we successfully joined
     private boolean mIsLinkDebouncing = false;
-    private final StateMachineDeathRecipient mDeathRecipient =
-            new StateMachineDeathRecipient(this, CMD_CLIENT_INTERFACE_BINDER_DEATH);
+    private final WifiNative.WificondDeathEventHandler mWificondDeathRecipient = () -> {
+        sendMessage(CMD_WIFICOND_BINDER_DEATH);
+    };
     private final WifiNative.VendorHalDeathEventHandler mVendorHalDeathRecipient = () -> {
         sendMessage(CMD_VENDOR_HAL_HWBINDER_DEATH);
     };
@@ -367,7 +366,7 @@
     private DhcpResults mDhcpResults;
 
     // NOTE: Do not return to clients - see syncRequestConnectionInfo()
-    private final WifiInfo mWifiInfo;
+    private final ExtendedWifiInfo mWifiInfo;
     private NetworkInfo mNetworkInfo;
     private final NetworkCapabilities mDfltNetworkCapabilities;
     private SupplicantStateTracker mSupplicantStateTracker;
@@ -698,8 +697,9 @@
     /* Enable/Disable WifiConnectivityManager */
     static final int CMD_ENABLE_WIFI_CONNECTIVITY_MANAGER               = BASE + 166;
 
-    /* Enable/Disable AutoJoin when associated */
-    static final int CMD_ENABLE_AUTOJOIN_WHEN_ASSOCIATED                = BASE + 167;
+
+    /* Get all matching Passpoint configurations */
+    static final int CMD_GET_ALL_MATCHING_CONFIGS                       = BASE + 168;
 
     /**
      * Used to handle messages bounced between WifiStateMachine and IpClient.
@@ -725,8 +725,8 @@
     /* used to indicate that the foreground user was switched */
     static final int CMD_USER_STOP                                      = BASE + 207;
 
-    /* Signals that IClientInterface instance underpinning our state is dead. */
-    private static final int CMD_CLIENT_INTERFACE_BINDER_DEATH          = BASE + 250;
+    /* Signals that wificond is dead. */
+    private static final int CMD_WIFICOND_BINDER_DEATH          = BASE + 250;
 
     /* Signals that the Vendor HAL instance underpinning our state is dead. */
     private static final int CMD_VENDOR_HAL_HWBINDER_DEATH              = BASE + 251;
@@ -737,6 +737,9 @@
     /* Used to set the tx power limit for SAR during the start of a phone call. */
     private static final int CMD_SELECT_TX_POWER_SCENARIO               = BASE + 253;
 
+    // Start subscription provisioning with a given provider
+    private static final int CMD_START_SUBSCRIPTION_PROVISIONING        = BASE + 254;
+
     // For message logging.
     private static final Class[] sMessageClasses = {
             AsyncChannel.class, WifiStateMachine.class, DhcpClient.class };
@@ -791,8 +794,6 @@
      */
     private long mSupplicantScanIntervalMs;
 
-    private boolean mEnableAutoJoinWhenAssociated;
-    private int mAlwaysEnableScansWhileAssociated;
     private final int mThresholdQualifiedRssi24;
     private final int mThresholdQualifiedRssi5;
     private final int mThresholdSaturatedRssi24;
@@ -943,8 +944,9 @@
 
         mWifiMonitor = mWifiInjector.getWifiMonitor();
         mWifiDiagnostics = mWifiInjector.makeWifiDiagnostics(mWifiNative);
+        mWifiPermissionsWrapper = mWifiInjector.getWifiPermissionsWrapper();
 
-        mWifiInfo = new WifiInfo();
+        mWifiInfo = new ExtendedWifiInfo();
         mSupplicantStateTracker =
                 mFacade.makeSupplicantStateTracker(context, mWifiConfigManager, getHandler());
 
@@ -1032,8 +1034,6 @@
                 com.android.internal.R.string.config_wifi_tcp_buffers);
 
         // Load Device configs
-        mEnableAutoJoinWhenAssociated = context.getResources().getBoolean(
-                R.bool.config_wifi_framework_enable_associated_network_selection);
         mThresholdQualifiedRssi24 = context.getResources().getInteger(
                 R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz);
         mThresholdQualifiedRssi5 = context.getResources().getInteger(
@@ -1243,6 +1243,7 @@
         mWifiNative.enableVerboseLogging(verbose);
         mWifiConfigManager.enableVerboseLogging(verbose);
         mSupplicantStateTracker.enableVerboseLogging(verbose);
+        mPasspointManager.enableVerboseLogging(verbose);
     }
 
     private static final String SYSTEM_PROPERTY_LOG_CONTROL_WIFIHAL = "log.tag.WifiHAL";
@@ -1271,26 +1272,6 @@
         // mWifiConfigManager.trimANQPCache(true);
     }
 
-    public void setAllowScansWithTraffic(int enabled) {
-        mAlwaysEnableScansWhileAssociated = enabled;
-    }
-
-    public int getAllowScansWithTraffic() {
-        return mAlwaysEnableScansWhileAssociated;
-    }
-
-    /*
-     * Dynamically turn on/off if switching networks while connected is allowd.
-     */
-    public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
-        sendMessage(CMD_ENABLE_AUTOJOIN_WHEN_ASSOCIATED, enabled ? 1 : 0);
-        return true;
-    }
-
-    public boolean getEnableAutoJoinWhenAssociated() {
-        return mEnableAutoJoinWhenAssociated;
-    }
-
     private boolean setRandomMacOui() {
         String oui = mContext.getResources().getString(R.string.config_wifi_random_mac_oui);
         if (TextUtils.isEmpty(oui)) {
@@ -1337,7 +1318,7 @@
             return false;
         }
         if (!mWifiConfigManager.enableNetwork(netId, true, uid)
-                || !mWifiConfigManager.checkAndUpdateLastConnectUid(netId, uid)) {
+                || !mWifiConfigManager.updateLastConnectUid(netId, uid)) {
             logi("connectToUserSelectNetwork Allowing uid " + uid
                     + " with insufficient permissions to connect=" + netId);
         } else {
@@ -1463,9 +1444,8 @@
     WifiLinkLayerStats getWifiLinkLayerStats() {
         WifiLinkLayerStats stats = null;
         if (mWifiLinkLayerStatsSupported > 0) {
-            String name = "wlan0";
-            stats = mWifiNative.getWifiLinkLayerStats(name);
-            if (name != null && stats == null && mWifiLinkLayerStatsSupported > 0) {
+            stats = mWifiNative.getWifiLinkLayerStats();
+            if (stats == null && mWifiLinkLayerStatsSupported > 0) {
                 mWifiLinkLayerStatsSupported -= 1;
             } else if (stats != null) {
                 lastLinkLayerStatsUpdate = mClock.getWallClockMillis();
@@ -1755,25 +1735,21 @@
     /**
      * Get status information for the current connection, if any.
      *
+     * @param callingPackage string indicating the calling package of the caller
+     * @param uid the calling uid
      * @return a {@link WifiInfo} object containing information about the current connection
      */
-    public WifiInfo syncRequestConnectionInfo(String callingPackage) {
-        int uid = Binder.getCallingUid();
+    public WifiInfo syncRequestConnectionInfo(String callingPackage, int uid) {
         WifiInfo result = new WifiInfo(mWifiInfo);
-        if (uid == Process.myUid()) return result;
         boolean hideBssidAndSsid = true;
         result.setMacAddress(WifiInfo.DEFAULT_MAC_ADDRESS);
 
-        IPackageManager packageManager = AppGlobals.getPackageManager();
-
         try {
-            if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS,
-                    uid) == PackageManager.PERMISSION_GRANTED) {
+            if (mWifiPermissionsWrapper.getLocalMacAddressPermission(uid)
+                    == PackageManager.PERMISSION_GRANTED) {
                 result.setMacAddress(mWifiInfo.getMacAddress());
             }
-            final WifiConfiguration currentWifiConfiguration = getCurrentWifiConfiguration();
-            if (mWifiPermissionsUtil.canAccessFullConnectionInfo(
-                    currentWifiConfiguration,
+            if (mWifiPermissionsUtil.canAccessScanResults(
                     callingPackage,
                     uid,
                     Build.VERSION_CODES.O)) {
@@ -1940,6 +1916,15 @@
         return config;
     }
 
+    List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult,
+            AsyncChannel channel) {
+        Message resultMsg = channel.sendMessageSynchronously(CMD_GET_ALL_MATCHING_CONFIGS,
+                scanResult);
+        List<WifiConfiguration> configs = (List<WifiConfiguration>) resultMsg.obj;
+        resultMsg.recycle();
+        return configs;
+    }
+
     /**
      * Retrieve a list of {@link OsuProvider} associated with the given AP synchronously.
      *
@@ -2001,6 +1986,25 @@
     }
 
     /**
+     * Start subscription provisioning synchronously
+     *
+     * @param provider {@link OsuProvider} the provider to provision with
+     * @param callback {@link IProvisioningCallback} callback for provisioning status
+     * @return boolean true indicates provisioning was started, false otherwise
+     */
+    public boolean syncStartSubscriptionProvisioning(int callingUid, OsuProvider provider,
+            IProvisioningCallback callback, AsyncChannel channel) {
+        Message msg = Message.obtain();
+        msg.what = CMD_START_SUBSCRIPTION_PROVISIONING;
+        msg.arg1 = callingUid;
+        msg.obj = callback;
+        msg.getData().putParcelable(EXTRA_OSU_PROVIDER, provider);
+        Message resultMsg = channel.sendMessageSynchronously(msg);
+        boolean result = resultMsg.arg1 != 0;
+        resultMsg.recycle();
+        return result;
+    }
+    /**
      * Get connection statistics synchronously
      *
      * @param channel
@@ -2277,6 +2281,7 @@
         } else {
             pw.println("mWifiConnectivityManager is not initialized");
         }
+        mWifiInjector.getWakeupController().dump(fd, pw, args);
     }
 
     public void handleUserSwitch(int userId) {
@@ -2916,23 +2921,6 @@
         mWifiApState.set(wifiApState);
 
         if (mVerboseLoggingEnabled) log("setWifiApState: " + syncGetWifiApStateByName());
-
-        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiApState);
-        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
-        if (wifiApState == WifiManager.WIFI_AP_STATE_FAILED) {
-            //only set reason number when softAP start failed
-            intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason);
-        }
-
-        if (ifaceName == null) {
-            loge("Updating wifiApState with a null iface name");
-        }
-        intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, ifaceName);
-        intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mode);
-
-        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     private void setScanResults() {
@@ -3262,16 +3250,18 @@
                 || stateChangeResult.wifiSsid.toString().isEmpty()) && isLinkDebouncing()) {
             return state;
         }
-        // Network id is only valid when we start connecting
+        // Network id and SSID are only valid when we start connecting
         if (SupplicantState.isConnecting(state)) {
             mWifiInfo.setNetworkId(lookupFrameworkNetworkId(stateChangeResult.networkId));
+            mWifiInfo.setBSSID(stateChangeResult.BSSID);
+            mWifiInfo.setSSID(stateChangeResult.wifiSsid);
         } else {
+            // Reset parameters according to WifiInfo.reset()
             mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
+            mWifiInfo.setBSSID(null);
+            mWifiInfo.setSSID(null);
         }
 
-        mWifiInfo.setBSSID(stateChangeResult.BSSID);
-        mWifiInfo.setSSID(stateChangeResult.wifiSsid);
-
         final WifiConfiguration config = getCurrentWifiConfiguration();
         if (config != null) {
             mWifiInfo.setEphemeral(config.ephemeral);
@@ -3903,6 +3893,8 @@
                     break;
                 case CMD_INITIALIZE:
                     ok = mWifiNative.initializeVendorHal(mVendorHalDeathRecipient);
+                    mPasspointManager.initializeProvisioner(
+                            mWifiInjector.getWifiServiceHandlerThread().getLooper());
                     replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
                     break;
                 case CMD_BOOT_COMPLETED:
@@ -4035,6 +4027,9 @@
                 case CMD_GET_MATCHING_OSU_PROVIDERS:
                     replyToMessage(message, message.what, new ArrayList<OsuProvider>());
                     break;
+                case CMD_START_SUBSCRIPTION_PROVISIONING:
+                    replyToMessage(message, message.what, 0);
+                    break;
                 case CMD_IP_CONFIGURATION_SUCCESSFUL:
                 case CMD_IP_CONFIGURATION_LOST:
                 case CMD_IP_REACHABILITY_LOST:
@@ -4116,7 +4111,7 @@
                         mWifiNative.stopFilteringMulticastV4Packets();
                     }
                     break;
-                case CMD_CLIENT_INTERFACE_BINDER_DEATH:
+                case CMD_WIFICOND_BINDER_DEATH:
                     Log.e(TAG, "wificond died unexpectedly. Triggering recovery");
                     mWifiMetrics.incrementNumWificondCrashes();
                     mWifiDiagnostics.captureBugReportData(
@@ -4133,6 +4128,9 @@
                     mWifiDiagnostics.reportConnectionEvent(
                             (Long) message.obj, BaseWifiDiagnostics.CONNECTION_EVENT_FAILED);
                     break;
+                case CMD_GET_ALL_MATCHING_CONFIGS:
+                    replyToMessage(message, message.what, new ArrayList<WifiConfiguration>());
+                    break;
                 case 0:
                     // We want to notice any empty messages (with what == 0) that might crop up.
                     // For example, we may have recycled a message sent to multiple handlers.
@@ -4152,7 +4150,7 @@
             // Tearing down the client interfaces below is going to stop our supplicant.
             mWifiMonitor.stopAllMonitoring();
 
-            mDeathRecipient.unlinkToDeath();
+            mWifiNative.deregisterWificondDeathHandler();
             mWifiNative.tearDown();
         }
 
@@ -4168,14 +4166,14 @@
             switch (message.what) {
                 case CMD_START_SUPPLICANT:
                     Pair<Integer, IClientInterface> statusAndInterface =
-                            mWifiNative.setupForClientMode();
+                            mWifiNative.setupForClientMode(mInterfaceName);
                     if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) {
                         mClientInterface = statusAndInterface.second;
                     } else {
                         incrementMetricsForSetupFailure(statusAndInterface.first);
                     }
                     if (mClientInterface == null
-                            || !mDeathRecipient.linkToDeath(mClientInterface.asBinder())) {
+                            || !mWifiNative.registerWificondDeathHandler(mWificondDeathRecipient)) {
                         setWifiState(WifiManager.WIFI_STATE_UNKNOWN);
                         cleanup();
                         break;
@@ -4532,15 +4530,6 @@
                 case CMD_ENABLE_WIFI_CONNECTIVITY_MANAGER:
                     mWifiConnectivityManager.enable(message.arg1 == 1 ? true : false);
                     break;
-                case CMD_ENABLE_AUTOJOIN_WHEN_ASSOCIATED:
-                    final boolean allowed = (message.arg1 > 0);
-                    boolean old_state = mEnableAutoJoinWhenAssociated;
-                    mEnableAutoJoinWhenAssociated = allowed;
-                    if (!old_state && allowed && mScreenOn
-                            && getCurrentState() == mConnectedState) {
-                        mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
-                    }
-                    break;
                 case CMD_SELECT_TX_POWER_SCENARIO:
                     int txPowerScenario = message.arg1;
                     logd("Setting Tx power scenario to " + txPowerScenario);
@@ -5160,6 +5149,14 @@
                     replyToMessage(message, message.what,
                             mPasspointManager.getMatchingOsuProviders((ScanResult) message.obj));
                     break;
+                case CMD_START_SUBSCRIPTION_PROVISIONING:
+                    IProvisioningCallback callback = (IProvisioningCallback) message.obj;
+                    OsuProvider provider =
+                            (OsuProvider) message.getData().getParcelable(EXTRA_OSU_PROVIDER);
+                    int res = mPasspointManager.startSubscriptionProvisioning(
+                                    message.arg1, provider, callback) ? 1 : 0;
+                    replyToMessage(message, message.what, res);
+                    break;
                 case CMD_RECONNECT:
                     WorkSource workSource = (WorkSource) message.obj;
                     mWifiConnectivityManager.forceConnectivityScan(workSource);
@@ -5330,8 +5327,10 @@
                     }
                     break;
                 case WifiManager.START_WPS:
+                    mWifiMetrics.incrementWpsAttemptCount();
                     WpsInfo wpsInfo = (WpsInfo) message.obj;
                     if (wpsInfo == null) {
+                        mWifiMetrics.incrementWpsStartFailureCount();
                         loge("Cannot start WPS with null WpsInfo object");
                         replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR);
                         break;
@@ -5377,6 +5376,7 @@
                         replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, wpsResult);
                         transitionTo(mWpsRunningState);
                     } else {
+                        mWifiMetrics.incrementWpsStartFailureCount();
                         loge("Failed to start WPS with config " + wpsInfo.toString());
                         replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR);
                     }
@@ -5496,6 +5496,10 @@
                 case CMD_ENABLE_P2P:
                     p2pSendMessage(WifiStateMachine.CMD_ENABLE_P2P);
                     break;
+                case CMD_GET_ALL_MATCHING_CONFIGS:
+                    replyToMessage(message, message.what,
+                            mPasspointManager.getAllMatchingWifiConfigs((ScanResult) message.obj));
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -6798,8 +6802,10 @@
                     int netId = loadResult.second;
                     if (success) {
                         message.arg1 = netId;
+                        mWifiMetrics.incrementWpsSuccessCount();
                         replyToMessage(mSourceMessage, WifiManager.WPS_COMPLETED);
                     } else {
+                        mWifiMetrics.incrementWpsSupplicantFailureCount();
                         replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
                                 WifiManager.ERROR);
                     }
@@ -6809,6 +6815,7 @@
                     transitionTo(mDisconnectedState);
                     break;
                 case WifiMonitor.WPS_OVERLAP_EVENT:
+                    mWifiMetrics.incrementWpsOverlapFailureCount();
                     replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
                             WifiManager.WPS_OVERLAP_ERROR);
                     mSourceMessage.recycle();
@@ -6818,6 +6825,7 @@
                 case WifiMonitor.WPS_FAIL_EVENT:
                     // Arg1 has the reason for the failure
                     if ((message.arg1 != WifiManager.ERROR) || (message.arg2 != 0)) {
+                        mWifiMetrics.incrementWpsOtherConnectionFailureCount();
                         replyToMessage(mSourceMessage, WifiManager.WPS_FAILED, message.arg1);
                         mSourceMessage.recycle();
                         mSourceMessage = null;
@@ -6829,6 +6837,7 @@
                     }
                     break;
                 case WifiMonitor.WPS_TIMEOUT_EVENT:
+                    mWifiMetrics.incrementWpsTimeoutFailureCount();
                     replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
                             WifiManager.WPS_TIMED_OUT);
                     mSourceMessage.recycle();
@@ -6839,6 +6848,7 @@
                     replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.IN_PROGRESS);
                     break;
                 case WifiManager.CANCEL_WPS:
+                    mWifiMetrics.incrementWpsCancellationCount();
                     if (mWifiNative.cancelWps()) {
                         replyToMessage(message, WifiManager.CANCEL_WPS_SUCCEDED);
                     } else {
@@ -6958,7 +6968,8 @@
             mMode = config.getTargetMode();
 
             IApInterface apInterface = null;
-            Pair<Integer, IApInterface> statusAndInterface = mWifiNative.setupForSoftApMode();
+            Pair<Integer, IApInterface> statusAndInterface =
+                    mWifiNative.setupForSoftApMode(mInterfaceName);
             if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) {
                 apInterface = statusAndInterface.second;
             } else {
@@ -6967,10 +6978,7 @@
             if (apInterface == null) {
                 setWifiApState(WIFI_AP_STATE_FAILED,
                         WifiManager.SAP_START_FAILURE_GENERAL, null, mMode);
-                /**
-                 * Transition to InitialState to reset the
-                 * driver/HAL back to the initial state.
-                 */
+                // Transition to InitialState to reset the driver/HAL back to the initial state.
                 transitionTo(mInitialState);
                 return;
             }
@@ -6978,16 +6986,20 @@
             try {
                 mIfaceName = apInterface.getInterfaceName();
             } catch (RemoteException e) {
-                // Failed to get the interface name. The name will not be available for
-                // the enabled broadcast, but since we had an error getting the name, we most likely
-                // won't be able to fully start softap mode.
+                // Failed to get the interface name. This is not a good sign and we should report
+                // a failure and switch back to the initial state to reset the driver and HAL.
+                setWifiApState(WIFI_AP_STATE_FAILED,
+                        WifiManager.SAP_START_FAILURE_GENERAL, null, mMode);
+                transitionTo(mInitialState);
+                return;
             }
 
             checkAndSetConnectivityInstance();
             mSoftApManager = mWifiInjector.makeSoftApManager(mNwService,
                                                              new SoftApListener(),
                                                              apInterface,
-                                                             config.getWifiConfiguration());
+                                                             mIfaceName,
+                                                             config);
             mSoftApManager.start();
             mWifiStateTracker.updateState(WifiStateTracker.SOFT_AP);
         }
diff --git a/service/java/com/android/server/wifi/WifiStateMachinePrime.java b/service/java/com/android/server/wifi/WifiStateMachinePrime.java
index 2006884..de970ee 100644
--- a/service/java/com/android/server/wifi/WifiStateMachinePrime.java
+++ b/service/java/com/android/server/wifi/WifiStateMachinePrime.java
@@ -16,8 +16,8 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.net.wifi.IApInterface;
-import android.net.wifi.IWificond;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.INetworkManagementService;
@@ -25,6 +25,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
@@ -46,11 +47,12 @@
 
     private final WifiInjector mWifiInjector;
     private final Looper mLooper;
+    private final WifiNative mWifiNative;
     private final INetworkManagementService mNMService;
 
-    private IWificond mWificond;
+    private Queue<SoftApModeConfiguration> mApConfigQueue = new ConcurrentLinkedQueue<>();
 
-    private Queue<WifiConfiguration> mApConfigQueue = new ConcurrentLinkedQueue<>();
+    private String mInterfaceName;
 
     /* The base for wifi message types */
     static final int BASE = Protocol.BASE_WIFI;
@@ -66,22 +68,17 @@
 
     WifiStateMachinePrime(WifiInjector wifiInjector,
                           Looper looper,
+                          WifiNative wifiNative,
                           INetworkManagementService nmService) {
         mWifiInjector = wifiInjector;
         mLooper = looper;
+        mWifiNative = wifiNative;
         mNMService = nmService;
 
-        // Clean up existing interfaces in wificond.
-        // This ensures that the framework and wificond are in a consistent state after a framework
-        // restart.
-        try {
-            mWificond = mWifiInjector.makeWificond();
-            if (mWificond != null) {
-                mWificond.tearDownInterfaces();
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "wificond died during framework startup");
-        }
+        mInterfaceName = mWifiNative.getInterfaceName();
+
+        // make sure we do not have leftover state in the event of a restart
+        mWifiNative.tearDown();
     }
 
     /**
@@ -106,17 +103,13 @@
     /**
      * Method to enable soft ap for wifi hotspot.
      *
-     * The WifiConfiguration is generally going to be null to indicate that the
-     * currently saved config in WifiApConfigManager should be used.  When the config is
-     * not null, it will be saved in the WifiApConfigManager. This save is performed in the
-     * constructor of SoftApManager.
+     * The supplied SoftApModeConfiguration includes the target softap WifiConfiguration (or null if
+     * the persisted config is to be used) and the target operating mode (ex,
+     * {@link WifiManager.IFACE_IP_MODE_TETHERED} {@link WifiManager.IFACE_IP_MODE_LOCAL_ONLY}).
      *
-     * @param wifiConfig WifiConfiguration for the hostapd softap
+     * @param wifiConfig SoftApModeConfiguration for the hostapd softap
      */
-    public void enterSoftAPMode(WifiConfiguration wifiConfig) {
-        if (wifiConfig == null) {
-            wifiConfig = new WifiConfiguration();
-        }
+    public void enterSoftAPMode(@NonNull SoftApModeConfiguration wifiConfig) {
         mApConfigQueue.offer(wifiConfig);
         changeMode(ModeStateMachine.CMD_START_SOFT_AP_MODE);
     }
@@ -214,23 +207,13 @@
             return HANDLED;
         }
 
-        private void tearDownInterfaces() {
-            if (mWificond != null) {
-                try {
-                    mWificond.tearDownInterfaces();
-                } catch (RemoteException e) {
-                    // There is very little we can do here
-                    Log.e(TAG, "Failed to tear down interfaces via wificond");
-                }
-                mWificond = null;
-            }
-            return;
+        private void cleanup() {
+            mWifiNative.tearDown();
         }
 
         class ClientModeState extends State {
             @Override
             public void enter() {
-                mWificond = mWifiInjector.makeWificond();
             }
 
             @Override
@@ -243,7 +226,7 @@
 
             @Override
             public void exit() {
-                tearDownInterfaces();
+                cleanup();
             }
         }
 
@@ -270,6 +253,7 @@
 
         class SoftAPModeState extends State {
             IApInterface mApInterface = null;
+            String mIfaceName = null;
 
             @Override
             public void enter() {
@@ -281,22 +265,28 @@
 
                 // Continue with setup since we are changing modes
                 mApInterface = null;
-                mWificond = mWifiInjector.makeWificond();
-                if (mWificond == null) {
-                    Log.e(TAG, "Failed to get reference to wificond");
-                    writeApConfigDueToStartFailure();
-                    mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
+
+                Pair<Integer, IApInterface> statusAndInterface =
+                        mWifiNative.setupForSoftApMode(mInterfaceName);
+                if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) {
+                    mApInterface = statusAndInterface.second;
+                } else {
+                    // TODO: incorporate metrics - or this particular one will be done in WifiNative
+                    //incrementMetricsForSetupFailure(statusAndInterface.first);
+                }
+                if (mApInterface == null) {
+                    // TODO: update battery stats that a failure occured - best to do in
+                    // initialization Failed.  Keeping line copied from WSM for a reference
+                    //setWifiApState(WIFI_AP_STATE_FAILED,
+                    //        WifiManager.SAP_START_FAILURE_GENERAL, null, mMode);
+                    initializationFailed("Could not get IApInterface instance");
                     return;
                 }
 
                 try {
-                    mApInterface = mWificond.createApInterface();
-                } catch (RemoteException e1) { }
-
-                if (mApInterface == null) {
-                    Log.e(TAG, "Could not get IApInterface instance from wificond");
-                    writeApConfigDueToStartFailure();
-                    mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
+                    mIfaceName = mApInterface.getInterfaceName();
+                } catch (RemoteException e) {
+                    initializationFailed("Could not get IApInterface name");
                     return;
                 }
                 mModeStateMachine.transitionTo(mSoftAPModeActiveState);
@@ -316,6 +306,8 @@
                         // not in active state, nothing to stop.
                         break;
                     case CMD_START_AP_FAILURE:
+                        // remove the saved config for the start attempt
+                        mApConfigQueue.poll();
                         Log.e(TAG, "Failed to start SoftApMode.  Wait for next mode command.");
                         break;
                     case CMD_AP_STOPPED:
@@ -329,19 +321,20 @@
 
             @Override
             public void exit() {
-                tearDownInterfaces();
+                cleanup();
             }
 
             protected IApInterface getInterface() {
                 return mApInterface;
             }
 
-            private void writeApConfigDueToStartFailure() {
-                WifiConfiguration config = mApConfigQueue.poll();
-                if (config != null && config.SSID != null) {
-                    // Save valid configs for future calls.
-                    mWifiInjector.getWifiApConfigStore().setApConfiguration(config);
-                }
+            protected String getInterfaceName() {
+                return mIfaceName;
+            }
+
+            private void initializationFailed(String message) {
+                Log.e(TAG, message);
+                mModeStateMachine.sendMessage(CMD_START_AP_FAILURE);
             }
         }
 
@@ -409,16 +402,17 @@
             @Override
             public void enter() {
                 Log.d(TAG, "Entering SoftApModeActiveState");
-                WifiConfiguration config = mApConfigQueue.poll();
+                SoftApModeConfiguration softApModeConfig = mApConfigQueue.poll();
+                WifiConfiguration config = softApModeConfig.getWifiConfiguration();
+                // TODO (b/67601382): add checks for valid softap configs
                 if (config != null && config.SSID != null) {
                     Log.d(TAG, "Passing config to SoftApManager! " + config);
                 } else {
                     config = null;
                 }
-
                 this.mActiveModeManager = mWifiInjector.makeSoftApManager(mNMService,
                         new SoftApListener(), ((SoftAPModeState) mSoftAPModeState).getInterface(),
-                        config);
+                        ((SoftAPModeState) mSoftAPModeState).getInterfaceName(), softApModeConfig);
                 mActiveModeManager.start();
             }
 
diff --git a/service/java/com/android/server/wifi/WifiVendorHal.java b/service/java/com/android/server/wifi/WifiVendorHal.java
index 12674aa..f1d2add 100644
--- a/service/java/com/android/server/wifi/WifiVendorHal.java
+++ b/service/java/com/android/server/wifi/WifiVendorHal.java
@@ -59,7 +59,6 @@
 import android.net.wifi.RttManager.ResponderConfig;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiLinkLayerStats;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
@@ -67,16 +66,22 @@
 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.MutableInt;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.connectivity.KeepalivePacketData;
+import com.android.server.wifi.HalDeviceManager.InterfaceDestroyedListener;
 import com.android.server.wifi.util.BitMask;
 import com.android.server.wifi.util.NativeUtil;
 
+import libcore.util.NonNull;
+
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.Set;
 
 /**
@@ -138,7 +143,7 @@
     }
 
     /**
-     * Logs if the argument is false.
+     * Logs the argument along with the method name.
      *
      * Always returns its argument.
      */
@@ -158,6 +163,26 @@
     }
 
     /**
+     * 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 at method entry
      *
      * @param format string with % placeholders
@@ -200,9 +225,9 @@
 
     // Vendor HAL HIDL interface objects.
     private IWifiChip mIWifiChip;
-    private IWifiStaIface mIWifiStaIface;
-    private IWifiApIface mIWifiApIface;
     private IWifiRttController mIWifiRttController;
+    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;
@@ -248,7 +273,8 @@
     public boolean initialize(WifiNative.VendorHalDeathEventHandler handler) {
         synchronized (sLock) {
             mHalDeviceManager.initialize();
-            mHalDeviceManager.registerStatusListener(mHalDeviceManagerStatusCallbacks, mLooper);
+            mHalDeviceManager.registerStatusListener(mHalDeviceManagerStatusCallbacks,
+                    mHalEventHandler);
             mDeathEventHandler = handler;
             return true;
         }
@@ -269,7 +295,16 @@
      * @return true for success
      */
     public boolean startVendorHalAp() {
-        return startVendorHal(AP_MODE);
+        synchronized (sLock) {
+            if (!startVendorHal()) {
+                return false;
+            }
+            if (TextUtils.isEmpty(createApIface(null))) {
+                stopVendorHal();
+                return false;
+            }
+            return true;
+        }
     }
 
     /**
@@ -278,85 +313,182 @@
      * @return true for success
      */
     public boolean startVendorHalSta() {
-        return startVendorHal(STA_MODE);
+        synchronized (sLock) {
+            if (!startVendorHal()) {
+                return false;
+            }
+            if (TextUtils.isEmpty(createStaIface(null))) {
+                stopVendorHal();
+                return false;
+            }
+            return true;
+        }
     }
 
-    public static final boolean STA_MODE = true;
-    public static final boolean AP_MODE = false;
-
     /**
-     * Bring up the HIDL Vendor HAL and configure for STA mode or AP mode.
-     *
-     * @param isStaMode true to start HAL in STA mode, false to start in AP mode.
+     * Bring up the HIDL Vendor HAL.
+     * @return true on success, false otherwise.
      */
-    public boolean startVendorHal(boolean isStaMode) {
+    public boolean startVendorHal() {
         synchronized (sLock) {
-            if (mIWifiStaIface != null) return boolResult(false);
-            if (mIWifiApIface != null) return boolResult(false);
             if (!mHalDeviceManager.start()) {
-                return startFailedTo("start the vendor HAL");
-            }
-            IWifiIface iface;
-            if (isStaMode) {
-                mIWifiStaIface = mHalDeviceManager.createStaIface(null, null);
-                if (mIWifiStaIface == null) {
-                    return startFailedTo("create STA Iface");
-                }
-                iface = (IWifiIface) mIWifiStaIface;
-                if (!registerStaIfaceCallback()) {
-                    return startFailedTo("register sta iface callback");
-                }
-                mIWifiRttController = mHalDeviceManager.createRttController(iface);
-                if (mIWifiRttController == null) {
-                    return startFailedTo("create RTT controller");
-                }
-                if (!registerRttEventCallback()) {
-                    return startFailedTo("register RTT iface callback");
-                }
-                enableLinkLayerStats();
-            } else {
-                mIWifiApIface = mHalDeviceManager.createApIface(null, null);
-                if (mIWifiApIface == null) {
-                    return startFailedTo("create AP Iface");
-                }
-                iface = (IWifiIface) mIWifiApIface;
-            }
-            mIWifiChip = mHalDeviceManager.getChip(iface);
-            if (mIWifiChip == null) {
-                return startFailedTo("get the chip created for the Iface");
-            }
-            if (!registerChipCallback()) {
-                return startFailedTo("register chip callback");
+                mLog.err("Failed to start vendor HAL");
+                return false;
             }
             mLog.i("Vendor Hal started successfully");
             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);
+        }
+    }
+
     /**
-     * Logs a message and cleans up after a failing start attempt
+     * Create a STA iface using {@link HalDeviceManager}.
      *
-     * The lock should be held.
-     * @param message describes what was being attempted
-     * @return false
+     * @param destroyedListener Listener to be invoked when the interface is destroyed.
+     * @return iface name on success, null otherwise.
      */
-    private boolean startFailedTo(String message) {
-        mVerboseLog.err("Failed to %. Vendor Hal start failed").c(message).flush();
-        mHalDeviceManager.stop();
-        clearState();
-        return false;
+    public String createStaIface(InterfaceDestroyedListener destroyedListener) {
+        synchronized (sLock) {
+            IWifiStaIface iface;
+            iface = mHalDeviceManager.createStaIface(destroyedListener, null);
+            if (iface == null) {
+                mLog.err("Failed to create STA iface");
+                return stringResult(null);
+            }
+            String ifaceName = mHalDeviceManager.getName((IWifiIface) iface);
+            if (TextUtils.isEmpty(ifaceName)) {
+                mLog.err("Failed to get iface name");
+                return stringResult(null);
+            }
+            if (!registerStaIfaceCallback(iface)) {
+                mLog.err("Failed to register STA iface callback");
+                return stringResult(null);
+            }
+            mIWifiRttController = mHalDeviceManager.createRttController();
+            if (mIWifiRttController == null) {
+                mLog.err("Failed to create RTT controller");
+                return stringResult(null);
+            }
+            if (!registerRttEventCallback()) {
+                mLog.err("Failed to register RTT controller callback");
+                return stringResult(null);
+            }
+            if (!retrieveWifiChip((IWifiIface) iface)) {
+                mLog.err("Failed to get wifi chip");
+                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");
+                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);
+        }
+    }
+
+    /**
+     * 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;
+            iface = mHalDeviceManager.createApIface(destroyedListener, null);
+            if (iface == null) {
+                mLog.err("Failed to create AP iface");
+                return stringResult(null);
+            }
+            String ifaceName = mHalDeviceManager.getName((IWifiIface) iface);
+            if (TextUtils.isEmpty(ifaceName)) {
+                mLog.err("Failed to get iface name");
+                return stringResult(null);
+            }
+            if (!retrieveWifiChip((IWifiIface) iface)) {
+                mLog.err("Failed to get wifi chip");
+                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");
+                return boolResult(false);
+            }
+            mIWifiApIfaces.remove(ifaceName);
+            return true;
+        }
+    }
+
+    private boolean retrieveWifiChip(IWifiIface iface) {
+        synchronized (sLock) {
+            mIWifiChip = mHalDeviceManager.getChip(iface);
+            if (mIWifiChip == null) {
+                mLog.err("Failed to get the chip created for the Iface");
+                return false;
+            }
+            if (!registerChipCallback()) {
+                mLog.err("Failed to register chip callback");
+                return false;
+            }
+            return true;
+        }
     }
 
     /**
      * Registers the sta iface callback.
      */
-    private boolean registerStaIfaceCallback() {
+    private boolean registerStaIfaceCallback(IWifiStaIface iface) {
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return boolResult(false);
+            if (iface == null) return boolResult(false);
             if (mIWifiStaIfaceEventCallback == null) return boolResult(false);
             try {
                 WifiStatus status =
-                        mIWifiStaIface.registerEventCallback(mIWifiStaIfaceEventCallback);
+                        iface.registerEventCallback(mIWifiStaIfaceEventCallback);
                 return ok(status);
             } catch (RemoteException e) {
                 handleRemoteException(e);
@@ -388,6 +520,7 @@
     private boolean registerRttEventCallback() {
         synchronized (sLock) {
             if (mIWifiRttController == null) return boolResult(false);
+            if (mRttEventCallback == null) return boolResult(false);
             try {
                 WifiStatus status = mIWifiRttController.registerEventCallback(mRttEventCallback);
                 return ok(status);
@@ -416,37 +549,39 @@
      */
     private void clearState() {
         mIWifiChip = null;
-        mIWifiStaIface = null;
-        mIWifiApIface = null;
+        mIWifiStaIfaces.clear();
+        mIWifiApIfaces.clear();
         mIWifiRttController = null;
         mDriverDescription = null;
         mFirmwareDescription = null;
-        mChannelsForBandSupport = null;
     }
 
     /**
-     * Tests whether the HAL is running or not
+     * 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 (mIWifiStaIface != null || mIWifiApIface != null);
+            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(WifiNative.ScanCapabilities capabilities) {
+    public boolean getBgScanCapabilities(
+            @NonNull String ifaceName, WifiNative.ScanCapabilities capabilities) {
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return boolResult(false);
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return boolResult(false);
             try {
                 MutableBoolean ans = new MutableBoolean(false);
                 WifiNative.ScanCapabilities out = capabilities;
-                mIWifiStaIface.getBackgroundScanCapabilities((status, cap) -> {
+                iface.getBackgroundScanCapabilities((status, cap) -> {
                             if (!ok(status)) return;
                             mVerboseLog.info("scan capabilities %").c(cap.toString()).flush();
                             out.max_scan_cache_size = cap.maxCacheSize;
@@ -581,24 +716,27 @@
      *
      * 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(WifiNative.ScanSettings settings,
+    public boolean startBgScan(@NonNull String ifaceName,
+                               WifiNative.ScanSettings settings,
                                WifiNative.ScanEventHandler eventHandler) {
         WifiStatus status;
         if (eventHandler == null) return boolResult(false);
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return boolResult(false);
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return boolResult(false);
             try {
                 if (mScan != null && !mScan.paused) {
-                    ok(mIWifiStaIface.stopBackgroundScan(mScan.cmdId));
+                    ok(iface.stopBackgroundScan(mScan.cmdId));
                     mScan = null;
                 }
                 mLastScanCmdId = (mLastScanCmdId % 9) + 1; // cycle through non-zero single digits
                 CurrentBackgroundScan scan = new CurrentBackgroundScan(mLastScanCmdId, settings);
-                status = mIWifiStaIface.startBackgroundScan(scan.cmdId, scan.param);
+                status = iface.startBackgroundScan(scan.cmdId, scan.param);
                 if (!ok(status)) return false;
                 scan.eventHandler = eventHandler;
                 mScan = scan;
@@ -613,14 +751,17 @@
 
     /**
      * Stops any ongoing backgound scan
+     *
+     * @param ifaceName Name of the interface.
      */
-    public void stopBgScan() {
+    public void stopBgScan(@NonNull String ifaceName) {
         WifiStatus status;
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return;
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return;
             try {
                 if (mScan != null) {
-                    ok(mIWifiStaIface.stopBackgroundScan(mScan.cmdId));
+                    ok(iface.stopBackgroundScan(mScan.cmdId));
                     mScan = null;
                 }
             } catch (RemoteException e) {
@@ -631,14 +772,17 @@
 
     /**
      * Pauses an ongoing backgound scan
+     *
+     * @param ifaceName Name of the interface.
      */
-    public void pauseBgScan() {
+    public void pauseBgScan(@NonNull String ifaceName) {
         WifiStatus status;
         synchronized (sLock) {
             try {
-                if (mIWifiStaIface == null) return;
+                IWifiStaIface iface = getStaIface(ifaceName);
+                if (iface == null) return;
                 if (mScan != null && !mScan.paused) {
-                    status = mIWifiStaIface.stopBackgroundScan(mScan.cmdId);
+                    status = iface.stopBackgroundScan(mScan.cmdId);
                     if (!ok(status)) return;
                     mScan.paused = true;
                 }
@@ -650,14 +794,17 @@
 
     /**
      * Restarts a paused background scan
+     *
+     * @param ifaceName Name of the interface.
      */
-    public void restartBgScan() {
+    public void restartBgScan(@NonNull String ifaceName) {
         WifiStatus status;
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return;
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return;
             try {
                 if (mScan != null && mScan.paused) {
-                    status = mIWifiStaIface.startBackgroundScan(mScan.cmdId, mScan.param);
+                    status = iface.startBackgroundScan(mScan.cmdId, mScan.param);
                     if (!ok(status)) return;
                     mScan.paused = false;
                 }
@@ -671,10 +818,13 @@
      * 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() {
+    public WifiScanner.ScanData[] getBgScanResults(@NonNull String ifaceName) {
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return null;
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return null;
             if (mScan == null) return null;
             return mScan.latestScanResults;
         }
@@ -685,17 +835,19 @@
      *
      * 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() {
+    public WifiLinkLayerStats getWifiLinkLayerStats(@NonNull String ifaceName) {
         class AnswerBox {
             public StaLinkLayerStats value = null;
         }
         AnswerBox answer = new AnswerBox();
         synchronized (sLock) {
             try {
-                if (mIWifiStaIface == null) return null;
-                mIWifiStaIface.getLinkLayerStats((status, stats) -> {
+                IWifiStaIface iface = getStaIface(ifaceName);
+                if (iface == null) return null;
+                iface.getLinkLayerStats((status, stats) -> {
                     if (!ok(status)) return;
                     answer.value = stats;
                 });
@@ -715,7 +867,6 @@
     static WifiLinkLayerStats frameworkFromHalLinkLayerStats(StaLinkLayerStats stats) {
         if (stats == null) return null;
         WifiLinkLayerStats out = new WifiLinkLayerStats();
-        // unpopulated: out.status, out.SSID, out.BSSID
         out.beacon_rx = stats.iface.beaconRx;
         out.rssi_mgmt = stats.iface.avgRssiMgmt;
         // Statistics are broken out by Wireless Multimedia Extensions categories
@@ -751,7 +902,7 @@
             out.rx_time = radioStats.rxTimeInMs;
             out.on_time_scan = radioStats.onTimeInMsForScan;
         }
-        // unused: stats.timeStampInMs;
+        out.timeStampInMs = stats.timeStampInMs;
         return out;
     }
 
@@ -762,12 +913,14 @@
      * Enables the linkLayerStats in the Hal.
      *
      * This is called unconditionally whenever we create a STA interface.
+     *
+     * @param iface Iface object.
      */
-    private void enableLinkLayerStats() {
+    private void enableLinkLayerStats(IWifiStaIface iface) {
         synchronized (sLock) {
             try {
                 WifiStatus status;
-                status = mIWifiStaIface.enableLinkLayerStatsCollection(mLinkLayerStatsDebug);
+                status = iface.enableLinkLayerStatsCollection(mLinkLayerStatsDebug);
                 if (!ok(status)) {
                     mLog.e("unable to enable link layer stats collection");
                 }
@@ -876,9 +1029,10 @@
      *
      * 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 int getSupportedFeatureSet() {
+    public int getSupportedFeatureSet(@NonNull String ifaceName) {
         int featureSet = 0;
         if (!mHalDeviceManager.isStarted()) {
             return featureSet; // TODO: can't get capabilities with Wi-Fi down
@@ -892,8 +1046,9 @@
                         feat.value = wifiFeatureMaskFromChipCapabilities(capabilities);
                     });
                 }
-                if (mIWifiStaIface != null) {
-                    mIWifiStaIface.getCapabilities((status, capabilities) -> {
+                IWifiStaIface iface = getStaIface(ifaceName);
+                if (iface != null) {
+                    iface.getCapabilities((status, capabilities) -> {
                         if (!ok(status)) return;
                         feat.value |= wifiFeatureMaskFromStaCapabilities(capabilities);
                     });
@@ -1432,16 +1587,18 @@
      * 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(byte[] oui) {
+    public boolean setScanningMacOui(@NonNull String ifaceName, byte[] oui) {
         if (oui == null) return boolResult(false);
         if (oui.length != 3) return boolResult(false);
         synchronized (sLock) {
             try {
-                if (mIWifiStaIface == null) return boolResult(false);
-                WifiStatus status = mIWifiStaIface.setScanningMacOui(oui);
+                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) {
@@ -1452,89 +1609,21 @@
     }
 
     /**
-     * Query the list of valid frequencies for the provided band.
-     * <p>
-     * The result depends on the on the country code that has been set.
-     *
-     * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
-     * @return frequencies vector of valid frequencies (MHz), or null for error.
-     * @throws IllegalArgumentException if band is not recognized.
-     */
-    public int[] getChannelsForBand(int band) {
-        enter("%").c(band).flush();
-        class AnswerBox {
-            public int[] value = null;
-        }
-        synchronized (sLock) {
-            try {
-                AnswerBox box = new AnswerBox();
-                int hb = makeWifiBandFromFrameworkBand(band);
-                if (mIWifiStaIface != null) {
-                    mIWifiStaIface.getValidFrequenciesForBand(hb, (status, frequencies) -> {
-                        if (status.code == WifiStatusCode.ERROR_NOT_SUPPORTED) {
-                            mChannelsForBandSupport = false;
-                        }
-                        if (!ok(status)) return;
-                        mChannelsForBandSupport = true;
-                        box.value = intArrayFromArrayList(frequencies);
-                    });
-                } else if (mIWifiApIface != null) {
-                    mIWifiApIface.getValidFrequenciesForBand(hb, (status, frequencies) -> {
-                        if (status.code == WifiStatusCode.ERROR_NOT_SUPPORTED) {
-                            mChannelsForBandSupport = false;
-                        }
-                        if (!ok(status)) return;
-                        mChannelsForBandSupport = true;
-                        box.value = intArrayFromArrayList(frequencies);
-                    });
-                }
-                return box.value;
-            } catch (RemoteException e) {
-                handleRemoteException(e);
-                return null;
-            }
-        }
-    }
-
-    private int[] intArrayFromArrayList(ArrayList<Integer> in) {
-        int[] ans = new int[in.size()];
-        int i = 0;
-        for (Integer e : in) ans[i++] = e;
-        return ans;
-    }
-
-    /**
-     * This holder is null until we know whether or not there is frequency-for-band support.
-     * <p>
-     * Set as a side-effect of getChannelsForBand.
-     */
-    @VisibleForTesting
-    Boolean mChannelsForBandSupport = null;
-
-    /**
-     * Indicates whether getChannelsForBand is supported.
-     *
-     * @return true if it is.
-     */
-    public boolean isGetChannelsForBandSupported() {
-        if (mChannelsForBandSupport != null) return mChannelsForBandSupport;
-        getChannelsForBand(WifiBand.BAND_24GHZ);
-        if (mChannelsForBandSupport != null) return mChannelsForBandSupport;
-        return false;
-    }
-
-    /**
      * Get the APF (Android Packet Filter) capabilities of the device
+     *
+     * @param ifaceName Name of the interface.
+     * @return APF capabilities object.
      */
-    public ApfCapabilities getApfCapabilities() {
+    public ApfCapabilities getApfCapabilities(@NonNull String ifaceName) {
         class AnswerBox {
             public ApfCapabilities value = sNoApfCapabilities;
         }
         synchronized (sLock) {
             try {
-                if (mIWifiStaIface == null) return sNoApfCapabilities;
+                IWifiStaIface iface = getStaIface(ifaceName);
+                if (iface == null) return sNoApfCapabilities;
                 AnswerBox box = new AnswerBox();
-                mIWifiStaIface.getApfPacketFilterCapabilities((status, capabilities) -> {
+                iface.getApfPacketFilterCapabilities((status, capabilities) -> {
                     if (!ok(status)) return;
                     box.value = new ApfCapabilities(
                         /* apfVersionSupported */   capabilities.version,
@@ -1554,10 +1643,11 @@
     /**
      * 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(byte[] filter) {
+    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.
@@ -1565,8 +1655,9 @@
         enter("filter length %").c(filter.length).flush();
         synchronized (sLock) {
             try {
-                if (mIWifiStaIface == null) return boolResult(false);
-                WifiStatus status = mIWifiStaIface.installApfPacketFilter(cmdId, program);
+                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) {
@@ -1579,10 +1670,11 @@
     /**
      * 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(String countryCode) {
+    public boolean setCountryCodeHal(@NonNull String ifaceName, String countryCode) {
         if (countryCode == null) return boolResult(false);
         if (countryCode.length() != 2) return boolResult(false);
         byte[] code;
@@ -1593,8 +1685,9 @@
         }
         synchronized (sLock) {
             try {
-                if (mIWifiApIface == null) return boolResult(false);
-                WifiStatus status = mIWifiApIface.setCountryCode(code);
+                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) {
@@ -1880,13 +1973,15 @@
      * <p>
      * Once started, monitoring remains active until HAL is unloaded.
      *
+     * @param ifaceName Name of the interface.
      * @return true for success
      */
-    public boolean startPktFateMonitoring() {
+    public boolean startPktFateMonitoring(@NonNull String ifaceName) {
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return boolResult(false);
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return boolResult(false);
             try {
-                WifiStatus status = mIWifiStaIface.startDebugPacketFateMonitoring();
+                WifiStatus status = iface.startDebugPacketFateMonitoring();
                 return ok(status);
             } catch (RemoteException e) {
                 handleRemoteException(e);
@@ -1969,16 +2064,18 @@
      * <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(WifiNative.TxFateReport[] reportBufs) {
+    public boolean getTxPktFates(@NonNull String ifaceName, WifiNative.TxFateReport[] reportBufs) {
         if (ArrayUtils.isEmpty(reportBufs)) return boolResult(false);
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return boolResult(false);
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return boolResult(false);
             try {
                 MutableBoolean ok = new MutableBoolean(false);
-                mIWifiStaIface.getDebugTxPacketFates((status, fates) -> {
+                iface.getDebugTxPacketFates((status, fates) -> {
                             if (!ok(status)) return;
                             int i = 0;
                             for (WifiDebugTxPacketFateReport fate : fates) {
@@ -2009,16 +2106,18 @@
      * <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(WifiNative.RxFateReport[] reportBufs) {
+    public boolean getRxPktFates(@NonNull String ifaceName, WifiNative.RxFateReport[] reportBufs) {
         if (ArrayUtils.isEmpty(reportBufs)) return boolResult(false);
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return boolResult(false);
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return boolResult(false);
             try {
                 MutableBoolean ok = new MutableBoolean(false);
-                mIWifiStaIface.getDebugRxPacketFates((status, fates) -> {
+                iface.getDebugRxPacketFates((status, fates) -> {
                             if (!ok(status)) return;
                             int i = 0;
                             for (WifiDebugRxPacketFateReport fate : fates) {
@@ -2047,6 +2146,7 @@
     /**
      * Start sending the specified keep alive packets periodically.
      *
+     * @param ifaceName Name of the interface.
      * @param slot
      * @param srcMac
      * @param keepAlivePacket
@@ -2054,16 +2154,18 @@
      * @return 0 for success, -1 for error
      */
     public int startSendingOffloadedPacket(
-            int slot, byte[] srcMac, KeepalivePacketData keepAlivePacket, int periodInMs) {
+            @NonNull String ifaceName, int slot, byte[] srcMac,
+            KeepalivePacketData keepAlivePacket, int periodInMs) {
         enter("slot=% periodInMs=%").c(slot).c(periodInMs).flush();
 
         ArrayList<Byte> data = NativeUtil.byteArrayToArrayList(keepAlivePacket.data);
         short protocol = (short) (keepAlivePacket.protocol);
 
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return -1;
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return -1;
             try {
-                WifiStatus status = mIWifiStaIface.startSendingKeepAlivePackets(
+                WifiStatus status = iface.startSendingKeepAlivePackets(
                         slot,
                         data,
                         protocol,
@@ -2082,16 +2184,18 @@
     /**
      * 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(int slot) {
+    public int stopSendingOffloadedPacket(@NonNull String ifaceName, int slot) {
         enter("slot=%").c(slot).flush();
 
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return -1;
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return -1;
             try {
-                WifiStatus status = mIWifiStaIface.stopSendingKeepAlivePackets(slot);
+                WifiStatus status = iface.stopSendingKeepAlivePackets(slot);
                 if (!ok(status)) return -1;
                 return 0;
             } catch (RemoteException e) {
@@ -2115,22 +2219,24 @@
     /**
      * 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(byte maxRssi, byte minRssi,
+    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) {
-            if (mIWifiStaIface == null) return -1;
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return -1;
             try {
-                mIWifiStaIface.stopRssiMonitoring(sRssiMonCmdId);
+                iface.stopRssiMonitoring(sRssiMonCmdId);
                 WifiStatus status;
-                status = mIWifiStaIface.startRssiMonitoring(sRssiMonCmdId, maxRssi, minRssi);
+                status = iface.startRssiMonitoring(sRssiMonCmdId, maxRssi, minRssi);
                 if (!ok(status)) return -1;
                 mWifiRssiEventHandler = rssiEventHandler;
                 return 0;
@@ -2144,15 +2250,16 @@
     /**
      * Stop RSSI monitoring
      *
+     * @param ifaceName Name of the interface.
      * @return 0 for success, -1 for failure
      */
-    public int stopRssiMonitoring() {
+    public int stopRssiMonitoring(@NonNull String ifaceName) {
         synchronized (sLock) {
             mWifiRssiEventHandler = null;
-            if (mIWifiStaIface == null) return -1;
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return -1;
             try {
-                mIWifiStaIface.stopRssiMonitoring(sRssiMonCmdId);
-                WifiStatus status = mIWifiStaIface.stopRssiMonitoring(sRssiMonCmdId);
+                WifiStatus status = iface.stopRssiMonitoring(sRssiMonCmdId);
                 if (!ok(status)) return -1;
                 return 0;
             } catch (RemoteException e) {
@@ -2229,14 +2336,17 @@
     /**
      * 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(boolean enabled) {
+    public boolean configureNeighborDiscoveryOffload(@NonNull String ifaceName, boolean enabled) {
         enter("enabled=%").c(enabled).flush();
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return boolResult(false);
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return boolResult(false);
             try {
-                WifiStatus status = mIWifiStaIface.enableNdOffload(enabled);
+                WifiStatus status = iface.enableNdOffload(enabled);
                 if (!ok(status)) return false;
             } catch (RemoteException e) {
                 handleRemoteException(e);
@@ -2251,16 +2361,19 @@
     /**
      * 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(WifiNative.RoamingCapabilities capabilities) {
+    public boolean getRoamingCapabilities(@NonNull String ifaceName,
+                                          WifiNative.RoamingCapabilities capabilities) {
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return boolResult(false);
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return boolResult(false);
             try {
                 MutableBoolean ok = new MutableBoolean(false);
                 WifiNative.RoamingCapabilities out = capabilities;
-                mIWifiStaIface.getRoamingCapabilities((status, cap) -> {
+                iface.getRoamingCapabilities((status, cap) -> {
                     if (!ok(status)) return;
                     out.maxBlacklistSize = cap.maxBlacklistSize;
                     out.maxWhitelistSize = cap.maxWhitelistSize;
@@ -2277,12 +2390,14 @@
     /**
      * Enable/disable firmware roaming.
      *
+     * @param ifaceName Name of the interface.
      * @param state the intended roaming state
      * @return SUCCESS, FAILURE, or BUSY
      */
-    public int enableFirmwareRoaming(int state) {
+    public int enableFirmwareRoaming(@NonNull String ifaceName, int state) {
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return WifiStatusCode.ERROR_NOT_STARTED;
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return WifiStatusCode.ERROR_NOT_STARTED;
             try {
                 byte val;
                 switch (state) {
@@ -2297,7 +2412,7 @@
                         return WifiStatusCode.ERROR_INVALID_ARGS;
                 }
 
-                WifiStatus status = mIWifiStaIface.setRoamingState(val);
+                WifiStatus status = iface.setRoamingState(val);
                 mVerboseLog.d("setRoamingState returned " + status.code);
                 return status.code;
             } catch (RemoteException e) {
@@ -2310,12 +2425,14 @@
     /**
      * 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(WifiNative.RoamingConfig config) {
+    public boolean configureRoaming(@NonNull String ifaceName, WifiNative.RoamingConfig config) {
         synchronized (sLock) {
-            if (mIWifiStaIface == null) return boolResult(false);
+            IWifiStaIface iface = getStaIface(ifaceName);
+            if (iface == null) return boolResult(false);
             try {
                 StaRoamingConfig roamingConfig = new StaRoamingConfig();
 
@@ -2346,7 +2463,7 @@
                     }
                 }
 
-                WifiStatus status = mIWifiStaIface.configureRoaming(roamingConfig);
+                WifiStatus status = iface.configureRoaming(roamingConfig);
                 if (!ok(status)) return false;
             } catch (RemoteException e) {
                 handleRemoteException(e);
@@ -2582,8 +2699,25 @@
                 // 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.
-                eventHandler.onRingBufferData(
-                        ringBufferStatus(status), NativeUtil.byteArrayFromArrayList(data));
+                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());
+                }
             });
         }
 
diff --git a/service/java/com/android/server/wifi/WificondControl.java b/service/java/com/android/server/wifi/WificondControl.java
index b6104a8..68da98f 100644
--- a/service/java/com/android/server/wifi/WificondControl.java
+++ b/service/java/com/android/server/wifi/WificondControl.java
@@ -16,19 +16,24 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.net.wifi.IApInterface;
+import android.net.wifi.IApInterfaceEventCallback;
 import android.net.wifi.IClientInterface;
 import android.net.wifi.IPnoScanEvent;
 import android.net.wifi.IScanEvent;
 import android.net.wifi.IWifiScannerImpl;
 import android.net.wifi.IWificond;
 import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.server.wifi.WifiNative.SoftApListener;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.util.InformationElementUtil;
 import com.android.server.wifi.util.NativeUtil;
@@ -40,14 +45,17 @@
 import com.android.server.wifi.wificond.PnoSettings;
 import com.android.server.wifi.wificond.SingleScanSettings;
 
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 
 /**
  * This class provides methods for WifiNative to send control commands to wificond.
  * NOTE: This class should only be used from WifiNative.
  */
-public class WificondControl {
+public class WificondControl implements IBinder.DeathRecipient {
     private boolean mVerboseLoggingEnabled = false;
 
     private static final String TAG = "WificondControl";
@@ -64,26 +72,31 @@
 
     // Cached wificond binder handlers.
     private IWificond mWificond;
-    private IClientInterface mClientInterface;
-    private IApInterface mApInterface;
-    private IWifiScannerImpl mWificondScanner;
-    private IScanEvent mScanEventHandler;
-    private IPnoScanEvent mPnoScanEventHandler;
-
-    private String mClientInterfaceName;
-
+    private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
+    private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
+    private HashMap<String, IWifiScannerImpl> mWificondScanners = new HashMap<>();
+    private HashMap<String, IScanEvent> mScanEventHandlers = new HashMap<>();
+    private HashMap<String, IPnoScanEvent> mPnoScanEventHandlers = new HashMap<>();
+    private HashMap<String, IApInterfaceEventCallback> mApInterfaceListeners = new HashMap<>();
+    private WifiNative.WificondDeathEventHandler mDeathEventHandler;
 
     private class ScanEventHandler extends IScanEvent.Stub {
+        private String mIfaceName;
+
+        ScanEventHandler(@NonNull String ifaceName) {
+            mIfaceName = ifaceName;
+        }
+
         @Override
         public void OnScanResultReady() {
             Log.d(TAG, "Scan result ready event");
-            mWifiMonitor.broadcastScanResultEvent(mClientInterfaceName);
+            mWifiMonitor.broadcastScanResultEvent(mIfaceName);
         }
 
         @Override
         public void OnScanFailed() {
             Log.d(TAG, "Scan failed event");
-            mWifiMonitor.broadcastScanFailedEvent(mClientInterfaceName);
+            mWifiMonitor.broadcastScanFailedEvent(mIfaceName);
         }
     }
 
@@ -95,10 +108,16 @@
     }
 
     private class PnoScanEventHandler extends IPnoScanEvent.Stub {
+        private String mIfaceName;
+
+        PnoScanEventHandler(@NonNull String ifaceName) {
+            mIfaceName = ifaceName;
+        }
+
         @Override
         public void OnPnoNetworkFound() {
             Log.d(TAG, "Pno scan result event");
-            mWifiMonitor.broadcastPnoScanResultEvent(mClientInterfaceName);
+            mWifiMonitor.broadcastPnoScanResultEvent(mIfaceName);
             mWifiInjector.getWifiMetrics().incrementPnoFoundNetworkEventCount();
         }
 
@@ -121,6 +140,38 @@
         }
     }
 
+    /**
+     * Listener for AP Interface events.
+     */
+    private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub {
+        private SoftApListener mSoftApListener;
+
+        ApInterfaceEventCallback(SoftApListener listener) {
+            mSoftApListener = listener;
+        }
+
+        @Override
+        public void onNumAssociatedStationsChanged(int numStations) {
+            mSoftApListener.onNumAssociatedStationsChanged(numStations);
+        }
+    }
+
+    /**
+     * Called by the binder subsystem upon remote object death.
+     * Invoke all the register death handlers and clear state.
+     */
+    @Override
+    public void binderDied() {
+        Log.e(TAG, "Wificond died!");
+        if (mDeathEventHandler != null) {
+            mDeathEventHandler.onDeath();
+        }
+        clearState();
+        // Invalidate the global wificond handle on death. Will be refereshed
+        // on the next setup call.
+        mWificond = null;
+    }
+
     /** Enable or disable verbose logging of WificondControl.
      *  @param enable True to enable verbose logging. False to disable verbose logging.
      */
@@ -129,21 +180,70 @@
     }
 
     /**
-    * Setup driver for client mode via wificond.
-    * @return An IClientInterface as wificond client interface binder handler.
-    * Returns null on failure.
-    */
-    public IClientInterface setupDriverForClientMode() {
-        Log.d(TAG, "Setting up driver for client mode");
+     * Registers a death notification for wificond.
+     * @return Returns true on success.
+     */
+    public boolean registerDeathHandler(@NonNull WifiNative.WificondDeathEventHandler handler) {
+        if (mDeathEventHandler != null) {
+            Log.e(TAG, "Death handler already present");
+            return false;
+        }
+        mDeathEventHandler = handler;
+        return true;
+    }
+
+    /**
+     * Deregisters a death notification for wificond.
+     * @return Returns true on success.
+     */
+    public boolean deregisterDeathHandler() {
+        if (mDeathEventHandler == null) {
+            Log.e(TAG, "No Death handler present");
+            return false;
+        }
+        mDeathEventHandler = null;
+        return true;
+    }
+
+    /**
+     * Helper method to retrieve the global wificond handle and register for
+     * death notifications.
+     */
+    private boolean retrieveWificondAndRegisterForDeath() {
+        if (mWificond != null) {
+            Log.d(TAG, "Wificond handle already retrieved");
+            // We already have a wificond handle.
+            return true;
+        }
         mWificond = mWifiInjector.makeWificond();
         if (mWificond == null) {
             Log.e(TAG, "Failed to get reference to wificond");
+            return false;
+        }
+        try {
+            mWificond.asBinder().linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register death notification for wificond");
+            // The remote has already died.
+            return false;
+        }
+        return true;
+    }
+
+    /**
+    * Setup interface for client mode via wificond.
+    * @return An IClientInterface as wificond client interface binder handler.
+    * Returns null on failure.
+    */
+    public IClientInterface setupInterfaceForClientMode(@NonNull String ifaceName) {
+        Log.d(TAG, "Setting up interface for client mode");
+        if (!retrieveWificondAndRegisterForDeath()) {
             return null;
         }
 
         IClientInterface clientInterface = null;
         try {
-            clientInterface = mWificond.createClientInterface();
+            clientInterface = mWificond.createClientInterface(ifaceName);
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to get IClientInterface due to remote exception");
             return null;
@@ -156,19 +256,21 @@
         Binder.allowBlocking(clientInterface.asBinder());
 
         // Refresh Handlers
-        mClientInterface = clientInterface;
+        mClientInterfaces.put(ifaceName, clientInterface);
         try {
-            mClientInterfaceName = clientInterface.getInterfaceName();
-            mWificondScanner = mClientInterface.getWifiScannerImpl();
-            if (mWificondScanner == null) {
+            IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl();
+            if (wificondScanner == null) {
                 Log.e(TAG, "Failed to get WificondScannerImpl");
                 return null;
             }
-            Binder.allowBlocking(mWificondScanner.asBinder());
-            mScanEventHandler = new ScanEventHandler();
-            mWificondScanner.subscribeScanEvents(mScanEventHandler);
-            mPnoScanEventHandler = new PnoScanEventHandler();
-            mWificondScanner.subscribePnoScanEvents(mPnoScanEventHandler);
+            mWificondScanners.put(ifaceName, wificondScanner);
+            Binder.allowBlocking(wificondScanner.asBinder());
+            ScanEventHandler scanEventHandler = new ScanEventHandler(ifaceName);
+            mScanEventHandlers.put(ifaceName,  scanEventHandler);
+            wificondScanner.subscribeScanEvents(scanEventHandler);
+            PnoScanEventHandler pnoScanEventHandler = new PnoScanEventHandler(ifaceName);
+            mPnoScanEventHandlers.put(ifaceName,  pnoScanEventHandler);
+            wificondScanner.subscribePnoScanEvents(pnoScanEventHandler);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
         }
@@ -177,21 +279,55 @@
     }
 
     /**
-    * Setup driver for softAp mode via wificond.
+     * Teardown a specific STA interface configured in wificond.
+     *
+     * @return Returns true on success.
+     */
+    public boolean tearDownClientInterface(@NonNull String ifaceName) {
+        boolean success;
+        try {
+            IWifiScannerImpl scannerImpl = mWificondScanners.get(ifaceName);
+            if (scannerImpl != null) {
+                scannerImpl.unsubscribeScanEvents();
+                scannerImpl.unsubscribePnoScanEvents();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to unsubscribe wificond scanner due to remote exception");
+            return false;
+        }
+
+        try {
+            success = mWificond.tearDownClientInterface(ifaceName);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to teardown client interface due to remote exception");
+            return false;
+        }
+        if (!success) {
+            Log.e(TAG, "Failed to teardown client interface");
+            return false;
+        }
+
+        mClientInterfaces.remove(ifaceName);
+        mWificondScanners.remove(ifaceName);
+        mScanEventHandlers.remove(ifaceName);
+        mPnoScanEventHandlers.remove(ifaceName);
+        return true;
+    }
+
+    /**
+    * Setup interface for softAp mode via wificond.
     * @return An IApInterface as wificond Ap interface binder handler.
     * Returns null on failure.
     */
-    public IApInterface setupDriverForSoftApMode() {
-        Log.d(TAG, "Setting up driver for soft ap mode");
-        mWificond = mWifiInjector.makeWificond();
-        if (mWificond == null) {
-            Log.e(TAG, "Failed to get reference to wificond");
+    public IApInterface setupInterfaceForSoftApMode(@NonNull String ifaceName) {
+        Log.d(TAG, "Setting up interface for soft ap mode");
+        if (!retrieveWificondAndRegisterForDeath()) {
             return null;
         }
 
         IApInterface apInterface = null;
         try {
-            apInterface = mWificond.createApInterface();
+            apInterface = mWificond.createApInterface(ifaceName);
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to get IApInterface due to remote exception");
             return null;
@@ -204,12 +340,33 @@
         Binder.allowBlocking(apInterface.asBinder());
 
         // Refresh Handlers
-        mApInterface = apInterface;
-
+        mApInterfaces.put(ifaceName, apInterface);
         return apInterface;
     }
 
     /**
+     * Teardown a specific AP interface configured in wificond.
+     *
+     * @return Returns true on success.
+     */
+    public boolean tearDownSoftApInterface(@NonNull String ifaceName) {
+        boolean success;
+        try {
+            success = mWificond.tearDownApInterface(ifaceName);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to teardown AP interface due to remote exception");
+            return false;
+        }
+        if (!success) {
+            Log.e(TAG, "Failed to teardown AP interface");
+            return false;
+        }
+        mApInterfaces.remove(ifaceName);
+        mApInterfaceListeners.remove(ifaceName);
+        return true;
+    }
+
+    /**
     * Teardown all interfaces configured in wificond.
     * @return Returns true on success.
     */
@@ -217,26 +374,17 @@
         Log.d(TAG, "tearing down interfaces in wificond");
         // Explicitly refresh the wificodn handler because |tearDownInterfaces()|
         // could be used to cleanup before we setup any interfaces.
-        mWificond = mWifiInjector.makeWificond();
-        if (mWificond == null) {
-            Log.e(TAG, "Failed to get reference to wificond");
+        if (!retrieveWificondAndRegisterForDeath()) {
             return false;
         }
 
         try {
-            if (mWificondScanner != null) {
-                mWificondScanner.unsubscribeScanEvents();
-                mWificondScanner.unsubscribePnoScanEvents();
+            for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
+                entry.getValue().unsubscribeScanEvents();
+                entry.getValue().unsubscribePnoScanEvents();
             }
             mWificond.tearDownInterfaces();
-
-            // Refresh handlers
-            mClientInterface = null;
-            mWificondScanner = null;
-            mPnoScanEventHandler = null;
-            mScanEventHandler = null;
-            mApInterface = null;
-
+            clearState();
             return true;
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to tear down interfaces due to remote exception");
@@ -245,17 +393,21 @@
         return false;
     }
 
+    /** Helper function to look up the interface handle using name */
+    private IClientInterface getClientInterface(@NonNull String ifaceName) {
+        return mClientInterfaces.get(ifaceName);
+    }
+
     /**
     * Disable wpa_supplicant via wificond.
     * @return Returns true on success.
     */
     public boolean disableSupplicant() {
-        if (mClientInterface == null) {
-            Log.e(TAG, "No valid wificond client interface handler");
+        if (!retrieveWificondAndRegisterForDeath()) {
             return false;
         }
         try {
-            return mClientInterface.disableSupplicant();
+            return mWificond.disableSupplicant();
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to disable supplicant due to remote exception");
         }
@@ -267,13 +419,11 @@
     * @return Returns true on success.
     */
     public boolean enableSupplicant() {
-        if (mClientInterface == null) {
-            Log.e(TAG, "No valid wificond client interface handler");
+        if (!retrieveWificondAndRegisterForDeath()) {
             return false;
         }
-
         try {
-            return mClientInterface.enableSupplicant();
+            return mWificond.enableSupplicant();
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to enable supplicant due to remote exception");
         }
@@ -281,19 +431,21 @@
     }
 
     /**
-    * Request signal polling to wificond.
-    * Returns an SignalPollResult object.
-    * Returns null on failure.
-    */
-    public WifiNative.SignalPollResult signalPoll() {
-        if (mClientInterface == null) {
+     * Request signal polling to wificond.
+     * @param ifaceName Name of the interface.
+     * Returns an SignalPollResult object.
+     * Returns null on failure.
+     */
+    public WifiNative.SignalPollResult signalPoll(@NonNull String ifaceName) {
+        IClientInterface iface = getClientInterface(ifaceName);
+        if (iface == null) {
             Log.e(TAG, "No valid wificond client interface handler");
             return null;
         }
 
         int[] resultArray;
         try {
-            resultArray = mClientInterface.signalPoll();
+            resultArray = iface.signalPoll();
             if (resultArray == null || resultArray.length != 3) {
                 Log.e(TAG, "Invalid signal poll result from wificond");
                 return null;
@@ -310,19 +462,21 @@
     }
 
     /**
-    * Fetch TX packet counters on current connection from wificond.
-    * Returns an TxPacketCounters object.
-    * Returns null on failure.
-    */
-    public WifiNative.TxPacketCounters getTxPacketCounters() {
-        if (mClientInterface == null) {
+     * Fetch TX packet counters on current connection from wificond.
+     * @param ifaceName Name of the interface.
+     * Returns an TxPacketCounters object.
+     * Returns null on failure.
+     */
+    public WifiNative.TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) {
+        IClientInterface iface = getClientInterface(ifaceName);
+        if (iface == null) {
             Log.e(TAG, "No valid wificond client interface handler");
             return null;
         }
 
         int[] resultArray;
         try {
-            resultArray = mClientInterface.getPacketCounters();
+            resultArray = iface.getPacketCounters();
             if (resultArray == null || resultArray.length != 2) {
                 Log.e(TAG, "Invalid signal poll result from wificond");
                 return null;
@@ -337,23 +491,30 @@
         return counters;
     }
 
+    /** Helper function to look up the scanner impl handle using name */
+    private IWifiScannerImpl getScannerImpl(@NonNull String ifaceName) {
+        return mWificondScanners.get(ifaceName);
+    }
+
     /**
     * Fetch the latest scan result from kernel via wificond.
+    * @param ifaceName Name of the interface.
     * @return Returns an ArrayList of ScanDetail.
     * Returns an empty ArrayList on failure.
     */
-    public ArrayList<ScanDetail> getScanResults(int scanType) {
+    public ArrayList<ScanDetail> getScanResults(@NonNull String ifaceName, int scanType) {
         ArrayList<ScanDetail> results = new ArrayList<>();
-        if (mWificondScanner == null) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
             Log.e(TAG, "No valid wificond scanner interface handler");
             return results;
         }
         try {
             NativeScanResult[] nativeResults;
             if (scanType == SCAN_TYPE_SINGLE_SCAN) {
-                nativeResults = mWificondScanner.getScanResults();
+                nativeResults = scannerImpl.getScanResults();
             } else {
-                nativeResults = mWificondScanner.getPnoScanResults();
+                nativeResults = scannerImpl.getPnoScanResults();
             }
             for (NativeScanResult result : nativeResults) {
                 WifiSsid wifiSsid = WifiSsid.createFromByteArray(result.ssid);
@@ -408,12 +569,16 @@
 
     /**
      * Start a scan using wificond for the given parameters.
+     * @param ifaceName Name of the interface.
      * @param freqs list of frequencies to scan for, if null scan all supported channels.
      * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
      * @return Returns true on success.
      */
-    public boolean scan(Set<Integer> freqs, Set<String> hiddenNetworkSSIDs) {
-        if (mWificondScanner == null) {
+    public boolean scan(@NonNull String ifaceName,
+                        Set<Integer> freqs,
+                        Set<String> hiddenNetworkSSIDs) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
             Log.e(TAG, "No valid wificond scanner interface handler");
             return false;
         }
@@ -442,7 +607,7 @@
         }
 
         try {
-            return mWificondScanner.scan(settings);
+            return scannerImpl.scan(settings);
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to request scan due to remote exception");
         }
@@ -451,11 +616,13 @@
 
     /**
      * Start PNO scan.
+     * @param ifaceName Name of the interface.
      * @param pnoSettings Pno scan configuration.
      * @return true on success.
      */
-    public boolean startPnoScan(WifiNative.PnoSettings pnoSettings) {
-        if (mWificondScanner == null) {
+    public boolean startPnoScan(@NonNull String ifaceName, WifiNative.PnoSettings pnoSettings) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
             Log.e(TAG, "No valid wificond scanner interface handler");
             return false;
         }
@@ -481,7 +648,7 @@
         }
 
         try {
-            boolean success = mWificondScanner.startPnoScan(settings);
+            boolean success = scannerImpl.startPnoScan(settings);
             mWifiInjector.getWifiMetrics().incrementPnoScanStartAttempCount();
             if (!success) {
                 mWifiInjector.getWifiMetrics().incrementPnoScanFailedCount();
@@ -495,15 +662,17 @@
 
     /**
      * Stop PNO scan.
+     * @param ifaceName Name of the interface.
      * @return true on success.
      */
-    public boolean stopPnoScan() {
-        if (mWificondScanner == null) {
+    public boolean stopPnoScan(@NonNull String ifaceName) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
             Log.e(TAG, "No valid wificond scanner interface handler");
             return false;
         }
         try {
-            return mWificondScanner.stopPnoScan();
+            return scannerImpl.stopPnoScan();
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to stop pno scan due to remote exception");
         }
@@ -512,17 +681,163 @@
 
     /**
      * Abort ongoing single scan.
+     * @param ifaceName Name of the interface.
      */
-    public void abortScan() {
-        if (mWificondScanner == null) {
+    public void abortScan(@NonNull String ifaceName) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
             Log.e(TAG, "No valid wificond scanner interface handler");
             return;
         }
         try {
-            mWificondScanner.abortScan();
+            scannerImpl.abortScan();
         } catch (RemoteException e1) {
             Log.e(TAG, "Failed to request abortScan due to remote exception");
         }
     }
 
+    /**
+     * Query the list of valid frequencies for the provided band.
+     * The result depends on the on the country code that has been set.
+     *
+     * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
+     * The following bands are supported:
+     * WifiScanner.WIFI_BAND_24_GHZ
+     * WifiScanner.WIFI_BAND_5_GHZ
+     * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
+     * @return frequencies vector of valid frequencies (MHz), or null for error.
+     * @throws IllegalArgumentException if band is not recognized.
+     */
+    public int [] getChannelsForBand(int band) {
+        if (mWificond == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler");
+            return null;
+        }
+        try {
+            switch (band) {
+                case WifiScanner.WIFI_BAND_24_GHZ:
+                    return mWificond.getAvailable2gChannels();
+                case WifiScanner.WIFI_BAND_5_GHZ:
+                    return mWificond.getAvailable5gNonDFSChannels();
+                case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY:
+                    return mWificond.getAvailableDFSChannels();
+                default:
+                    throw new IllegalArgumentException("unsupported band " + band);
+            }
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
+        }
+        return null;
+    }
+
+    /** Helper function to look up the interface handle using name */
+    private IApInterface getApInterface(@NonNull String ifaceName) {
+        return mApInterfaces.get(ifaceName);
+    }
+
+    /**
+     * Start Soft AP operation using the provided configuration.
+     *
+     * @param ifaceName Name of the interface.
+     * @param config Configuration to use for the soft ap created.
+     * @param listener Callback for AP events.
+     * @return true on success, false otherwise.
+     */
+    public boolean startSoftAp(@NonNull String ifaceName,
+                               WifiConfiguration config,
+                               SoftApListener listener) {
+        IApInterface iface = getApInterface(ifaceName);
+        if (iface == null) {
+            Log.e(TAG, "No valid ap interface handler");
+            return false;
+        }
+        int encryptionType = getIApInterfaceEncryptionType(config);
+        try {
+            // TODO(b/67745880) Note that config.SSID is intended to be either a
+            // hex string or "double quoted".
+            // However, it seems that whatever is handing us these configurations does not obey
+            // this convention.
+            boolean success = iface.writeHostapdConfig(
+                    config.SSID.getBytes(StandardCharsets.UTF_8), config.hiddenSSID,
+                    config.apChannel, encryptionType,
+                    (config.preSharedKey != null)
+                            ? config.preSharedKey.getBytes(StandardCharsets.UTF_8)
+                            : new byte[0]);
+            if (!success) {
+                Log.e(TAG, "Failed to write hostapd configuration");
+                return false;
+            }
+            IApInterfaceEventCallback  callback = new ApInterfaceEventCallback(listener);
+            mApInterfaceListeners.put(ifaceName, callback);
+            success = iface.startHostapd(callback);
+            if (!success) {
+                Log.e(TAG, "Failed to start hostapd.");
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in starting soft AP: " + e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Stop the ongoing Soft AP operation.
+     *
+     * @param ifaceName Name of the interface.
+     * @return true on success, false otherwise.
+     */
+    public boolean stopSoftAp(@NonNull String ifaceName) {
+        IApInterface iface = getApInterface(ifaceName);
+        if (iface == null) {
+            Log.e(TAG, "No valid ap interface handler");
+            return false;
+        }
+        try {
+            boolean success = iface.stopHostapd();
+            if (!success) {
+                Log.e(TAG, "Failed to stop hostapd.");
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in stopping soft AP: " + e);
+            return false;
+        }
+        mApInterfaceListeners.remove(ifaceName);
+        return true;
+    }
+
+    private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) {
+        int encryptionType;
+        switch (localConfig.getAuthType()) {
+            case WifiConfiguration.KeyMgmt.NONE:
+                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
+                break;
+            case WifiConfiguration.KeyMgmt.WPA_PSK:
+                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA;
+                break;
+            case WifiConfiguration.KeyMgmt.WPA2_PSK:
+                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2;
+                break;
+            default:
+                // We really shouldn't default to None, but this was how NetworkManagementService
+                // used to do this.
+                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
+                break;
+        }
+        return encryptionType;
+    }
+
+    /**
+     * Clear all internal handles.
+     */
+    private void clearState() {
+        // Refresh handlers
+        mClientInterfaces.clear();
+        mWificondScanners.clear();
+        mPnoScanEventHandlers.clear();
+        mScanEventHandlers.clear();
+        mApInterfaces.clear();
+        mApInterfaceListeners.clear();
+    }
 }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareClientState.java b/service/java/com/android/server/wifi/aware/WifiAwareClientState.java
index 3570f1d..987d49e 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareClientState.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareClientState.java
@@ -20,7 +20,6 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.net.wifi.RttManager;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.IWifiAwareEventCallback;
 import android.os.RemoteException;
@@ -271,47 +270,6 @@
     }
 
     /**
-     * Called on RTT success - forwards call to client.
-     */
-    public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
-        if (VDBG) {
-            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
-        }
-        try {
-            mCallback.onRangingSuccess(rangingId, results);
-        } catch (RemoteException e) {
-            Log.w(TAG, "onRangingSuccess: RemoteException - ignored: " + e);
-        }
-    }
-
-    /**
-     * Called on RTT failure - forwards call to client.
-     */
-    public void onRangingFailure(int rangingId, int reason, String description) {
-        if (VDBG) {
-            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
-                    + ", description=" + description);
-        }
-        try {
-            mCallback.onRangingFailure(rangingId, reason, description);
-        } catch (RemoteException e) {
-            Log.w(TAG, "onRangingFailure: RemoteException - ignored: " + e);
-        }
-    }
-
-    /**
-     * Called on RTT operation aborted - forwards call to client.
-     */
-    public void onRangingAborted(int rangingId) {
-        if (VDBG) Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId);
-        try {
-            mCallback.onRangingAborted(rangingId);
-        } catch (RemoteException e) {
-            Log.w(TAG, "onRangingAborted: RemoteException - ignored: " + e);
-        }
-    }
-
-    /**
      * Dump the internal state of the class.
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
index efeca65..82bc2c4 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDataPathStateManager.java
@@ -122,6 +122,7 @@
         sNetworkCapabilitiesFilter
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
         sNetworkCapabilitiesFilter.setNetworkSpecifier(new MatchAllNetworkSpecifier());
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java b/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
index 86f4e37..3358a4a 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
@@ -244,13 +244,20 @@
      *            (usually not used in the match decisions).
      * @param matchFilter The filter from the discovery advertisement (which was
      *            used in the match decision).
+     * @param rangingIndication Bit mask indicating the type of ranging event triggered.
+     * @param rangeMm The range to the peer in mm (valid if rangingIndication specifies ingress
+     *                or egress events - i.e. non-zero).
      */
     public void onMatch(int requestorInstanceId, byte[] peerMac, byte[] serviceSpecificInfo,
-            byte[] matchFilter) {
+            byte[] matchFilter, int rangingIndication, int rangeMm) {
         int peerId = getPeerIdOrAddIfNew(requestorInstanceId, peerMac);
 
         try {
-            mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter);
+            if (rangingIndication == 0) {
+                mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter);
+            } else {
+                mCallback.onMatchWithDistance(peerId, serviceSpecificInfo, matchFilter, rangeMm);
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "onMatch: RemoteException (FYI): " + e);
         }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java
index a6e724f..c8b5fce 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeApi.java
@@ -26,6 +26,7 @@
 import android.hardware.wifi.V1_0.NanInitiateDataPathRequest;
 import android.hardware.wifi.V1_0.NanMatchAlg;
 import android.hardware.wifi.V1_0.NanPublishRequest;
+import android.hardware.wifi.V1_0.NanRangingIndication;
 import android.hardware.wifi.V1_0.NanRespondToDataPathIndicationRequest;
 import android.hardware.wifi.V1_0.NanSubscribeRequest;
 import android.hardware.wifi.V1_0.NanTransmitFollowupRequest;
@@ -430,11 +431,13 @@
         req.baseConfigs.disableMatchExpirationIndication = true;
         req.baseConfigs.disableFollowupReceivedIndication = false;
 
-        // TODO: configure ranging and security
-        req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
-        req.baseConfigs.rangingRequired = false;
         req.autoAcceptDataPathRequests = false;
 
+        req.baseConfigs.rangingRequired = publishConfig.mEnableRanging;
+
+        // TODO: configure security
+        req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
+
         req.publishType = publishConfig.mPublishType;
         req.txType = NanTxType.BROADCAST;
 
@@ -493,9 +496,23 @@
         req.baseConfigs.disableMatchExpirationIndication = true;
         req.baseConfigs.disableFollowupReceivedIndication = false;
 
-        // TODO: configure ranging and security
+        req.baseConfigs.rangingRequired =
+                subscribeConfig.mMinDistanceMmSet || subscribeConfig.mMaxDistanceMmSet;
+        req.baseConfigs.configRangingIndications = 0;
+        // TODO: b/69428593 remove correction factors once HAL converted from CM to MM
+        if (subscribeConfig.mMinDistanceMmSet) {
+            req.baseConfigs.distanceIngressCm = (short) Math.min(
+                    subscribeConfig.mMinDistanceMm / 10, Short.MAX_VALUE);
+            req.baseConfigs.configRangingIndications |= NanRangingIndication.INGRESS_MET_MASK;
+        }
+        if (subscribeConfig.mMaxDistanceMmSet) {
+            req.baseConfigs.distanceEgressCm = (short) Math.min(subscribeConfig.mMaxDistanceMm / 10,
+                    Short.MAX_VALUE);
+            req.baseConfigs.configRangingIndications |= NanRangingIndication.EGRESS_MET_MASK;
+        }
+
+        // TODO: configure security
         req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
-        req.baseConfigs.rangingRequired = false;
 
         req.subscribeType = subscribeConfig.mSubscribeType;
 
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
index 2121160..b45978b 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeCallback.java
@@ -405,13 +405,17 @@
                     + (event.serviceSpecificInfo == null ? 0 : event.serviceSpecificInfo.size())
                     + ", matchFilter=" + Arrays.toString(
                     convertArrayListToNativeByteArray(event.matchFilter)) + ", mf.size()=" + (
-                    event.matchFilter == null ? 0 : event.matchFilter.size()));
+                    event.matchFilter == null ? 0 : event.matchFilter.size())
+                    + ", rangingIndicationType=" + event.rangingIndicationType
+                    + ", rangingMeasurementInCm=" + event.rangingMeasurementInCm);
         }
         incrementCbCount(CB_EV_MATCH);
 
+        // TODO: b/69428593 get rid of conversion once HAL moves from CM to MM
         mWifiAwareStateManager.onMatchNotification(event.discoverySessionId, event.peerId,
                 event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo),
-                convertArrayListToNativeByteArray(event.matchFilter));
+                convertArrayListToNativeByteArray(event.matchFilter), event.rangingIndicationType,
+                event.rangingMeasurementInCm * 10);
     }
 
     @Override
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
index e855d81..f0a86bb 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
@@ -16,10 +16,12 @@
 
 package com.android.server.wifi.aware;
 
+import android.annotation.NonNull;
 import android.hardware.wifi.V1_0.IWifiNanIface;
 import android.hardware.wifi.V1_0.IfaceType;
 import android.hardware.wifi.V1_0.WifiStatus;
 import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -41,6 +43,7 @@
 
     private WifiAwareStateManager mWifiAwareStateManager;
     private HalDeviceManager mHalDeviceManager;
+    private Handler mHandler;
     private WifiAwareNativeCallback mWifiAwareNativeCallback;
     private IWifiNanIface mWifiNanIface = null;
     private InterfaceDestroyedListener mInterfaceDestroyedListener =
@@ -56,7 +59,13 @@
         mWifiAwareNativeCallback = wifiAwareNativeCallback;
     }
 
-    public void start() {
+    /**
+     * Initialize the class - intended for late initialization.
+     *
+     * @param handler Handler on which to execute interface available callbacks.
+     */
+    public void start(Handler handler) {
+        mHandler = handler;
         mHalDeviceManager.initialize();
         mHalDeviceManager.registerStatusListener(
                 new HalDeviceManager.ManagerStatusListener() {
@@ -69,15 +78,15 @@
                             // 1. no problem registering duplicates - only one will be called
                             // 2. will be called immediately if available
                             mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                                    IfaceType.NAN, mInterfaceAvailableForRequestListener, null);
+                                    IfaceType.NAN, mInterfaceAvailableForRequestListener, mHandler);
                         } else {
                             awareIsDown();
                         }
                     }
-                }, null);
+                }, mHandler);
         if (mHalDeviceManager.isStarted()) {
             mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                    IfaceType.NAN, mInterfaceAvailableForRequestListener, null);
+                    IfaceType.NAN, mInterfaceAvailableForRequestListener, mHandler);
             tryToGetAware();
         }
     }
@@ -104,7 +113,7 @@
                 return;
             }
             IWifiNanIface iface = mHalDeviceManager.createNanIface(mInterfaceDestroyedListener,
-                    null);
+                    mHandler);
             if (iface == null) {
                 if (DBG) Log.d(TAG, "Was not able to obtain an IWifiNanIface");
             } else {
@@ -142,7 +151,7 @@
     private class InterfaceDestroyedListener implements
             HalDeviceManager.InterfaceDestroyedListener {
         @Override
-        public void onDestroyed() {
+        public void onDestroyed(@NonNull String ifaceName) {
             if (DBG) Log.d(TAG, "Interface was destroyed");
             awareIsDown();
         }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareRttStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareRttStateManager.java
deleted file mode 100644
index 9d0441f..0000000
--- a/service/java/com/android/server/wifi/aware/WifiAwareRttStateManager.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2016 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.aware;
-
-import android.content.Context;
-import android.net.wifi.IRttManager;
-import android.net.wifi.RttManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Arrays;
-
-
-/**
- * Manages interactions between the Aware and the RTT service. Duplicates some of the functionality
- * of the RttManager.
- */
-public class WifiAwareRttStateManager {
-    private static final String TAG = "WifiAwareRttStateMgr";
-
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false; // STOPSHIP if true
-
-    private final SparseArray<WifiAwareClientState> mPendingOperations = new SparseArray<>();
-    private AsyncChannel mAsyncChannel;
-    private Context mContext;
-
-    /**
-     * Initializes the connection to the RTT service.
-     */
-    public void start(Context context, Looper looper) {
-        if (VDBG) Log.v(TAG, "start()");
-
-        IBinder b = ServiceManager.getService(Context.WIFI_RTT_SERVICE);
-        IRttManager service = IRttManager.Stub.asInterface(b);
-        if (service == null) {
-            Log.e(TAG, "start(): not able to get WIFI_RTT_SERVICE");
-            return;
-        }
-
-        startWithRttService(context, looper, service);
-    }
-
-    /**
-     * Initializes the connection to the RTT service.
-     */
-    @VisibleForTesting
-    public void startWithRttService(Context context, Looper looper, IRttManager service) {
-        Messenger messenger;
-        try {
-            messenger = service.getMessenger(null, new int[1]);
-        } catch (RemoteException e) {
-            Log.e(TAG, "start(): not able to getMessenger() of WIFI_RTT_SERVICE");
-            return;
-        }
-
-        mAsyncChannel = new AsyncChannel();
-        mAsyncChannel.connect(context, new AwareRttHandler(looper), messenger);
-        mContext = context;
-    }
-
-    private WifiAwareClientState getAndRemovePendingOperationClient(int rangingId) {
-        WifiAwareClientState client = mPendingOperations.get(rangingId);
-        mPendingOperations.delete(rangingId);
-        return client;
-    }
-
-    /**
-     * Start a ranging operation for the client + peer MAC.
-     */
-    public void startRanging(int rangingId, WifiAwareClientState client,
-                             RttManager.RttParams[] params) {
-        if (VDBG) {
-            Log.v(TAG, "startRanging: rangingId=" + rangingId + ", parms="
-                    + Arrays.toString(params));
-        }
-
-        if (mAsyncChannel == null) {
-            Log.d(TAG, "startRanging(): AsyncChannel to RTT service not configured - failing");
-            client.onRangingFailure(rangingId, RttManager.REASON_NOT_AVAILABLE,
-                    "Aware service not able to configure connection to RTT service");
-            return;
-        }
-
-        mPendingOperations.put(rangingId, client);
-        RttManager.ParcelableRttParams pparams = new RttManager.ParcelableRttParams(params);
-        mAsyncChannel.sendMessage(RttManager.CMD_OP_START_RANGING, 0, rangingId, pparams);
-    }
-
-    private class AwareRttHandler extends Handler {
-        AwareRttHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (VDBG) Log.v(TAG, "handleMessage(): " + msg.what);
-
-            // channel configuration messages
-            switch (msg.what) {
-                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                        mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION,
-                                new RttManager.RttClient(mContext.getPackageName()));
-                    } else {
-                        Log.e(TAG, "Failed to set up channel connection to RTT service");
-                        mAsyncChannel = null;
-                    }
-                    return;
-                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
-                    /* NOP */
-                    return;
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                    Log.e(TAG, "Channel connection to RTT service lost");
-                    mAsyncChannel = null;
-                    return;
-            }
-
-            // RTT-specific messages
-            WifiAwareClientState client = getAndRemovePendingOperationClient(msg.arg2);
-            if (client == null) {
-                Log.e(TAG, "handleMessage(): RTT message (" + msg.what
-                        + ") -- cannot find registered pending operation client for ID "
-                        + msg.arg2);
-                return;
-            }
-
-            switch (msg.what) {
-                case RttManager.CMD_OP_SUCCEEDED: {
-                    int rangingId = msg.arg2;
-                    RttManager.ParcelableRttResults results = (RttManager.ParcelableRttResults)
-                            msg.obj;
-                    if (VDBG) {
-                        Log.v(TAG, "CMD_OP_SUCCEEDED: rangingId=" + rangingId + ", results="
-                                + results);
-                    }
-                    for (int i = 0; i < results.mResults.length; ++i) {
-                        /*
-                         * TODO: store peer ID rather than null in the return result.
-                         */
-                        results.mResults[i].bssid = null;
-                    }
-                    client.onRangingSuccess(rangingId, results);
-                    break;
-                }
-                case RttManager.CMD_OP_FAILED: {
-                    int rangingId = msg.arg2;
-                    int reason = msg.arg1;
-                    String description = ((Bundle) msg.obj).getString(RttManager.DESCRIPTION_KEY);
-                    if (VDBG) {
-                        Log.v(TAG, "CMD_OP_FAILED: rangingId=" + rangingId + ", reason=" + reason
-                                + ", description=" + description);
-                    }
-                    client.onRangingFailure(rangingId, reason, description);
-                    break;
-                }
-                case RttManager.CMD_OP_ABORTED: {
-                    int rangingId = msg.arg2;
-                    if (VDBG) {
-                        Log.v(TAG, "CMD_OP_ABORTED: rangingId=" + rangingId);
-                    }
-                    client.onRangingAborted(rangingId);
-                    break;
-                }
-                default:
-                    Log.e(TAG, "handleMessage(): ignoring message " + msg.what);
-                    break;
-            }
-        }
-    }
-
-    /**
-     * Dump the internal state of the class.
-     */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("WifiAwareRttStateManager:");
-        pw.println("  mPendingOperations: [" + mPendingOperations + "]");
-    }
-}
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java b/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
index dd4c2e0..62cc3ec 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
@@ -16,15 +16,16 @@
 
 package com.android.server.wifi.aware;
 
+import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.wifi.V1_0.NanStatusType;
-import android.net.wifi.RttManager;
 import android.net.wifi.aware.Characteristics;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.DiscoverySession;
 import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
 import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
 import android.net.wifi.aware.IWifiAwareManager;
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
@@ -42,7 +43,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.Arrays;
+import java.util.List;
 
 /**
  * Implementation of the IWifiAwareManager AIDL interface. Performs validity
@@ -63,7 +64,6 @@
     private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
             new SparseArray<>();
     private int mNextClientId = 1;
-    private int mNextRangingId = 1;
     private final SparseIntArray mUidByClientId = new SparseIntArray();
 
     public WifiAwareServiceImpl(Context context) {
@@ -134,7 +134,7 @@
         }
 
         if (configRequest != null) {
-            enforceConnectivityInternalPermission();
+            enforceNetworkStackPermission();
         } else {
             configRequest = new ConfigRequest.Builder().build();
         }
@@ -326,7 +326,7 @@
         enforceChangePermission();
 
         if (retryCount != 0) {
-            enforceConnectivityInternalPermission();
+            enforceNetworkStackPermission();
         }
 
         if (message != null && message.length
@@ -352,30 +352,10 @@
     }
 
     @Override
-    public int startRanging(int clientId, int sessionId, RttManager.ParcelableRttParams params) {
-        enforceAccessPermission();
-        enforceLocationPermission();
+    public void requestMacAddresses(int uid, List peerIds, IWifiAwareMacAddressProvider callback) {
+        enforceNetworkStackPermission();
 
-        // TODO: b/35676064 restricts access to this API until decide if will open.
-        enforceConnectivityInternalPermission();
-
-        int uid = getMockableCallingUid();
-        enforceClientValidity(uid, clientId);
-        if (VDBG) {
-            Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
-                    + ", parms=" + Arrays.toString(params.mParams));
-        }
-
-        if (params.mParams.length == 0) {
-            throw new IllegalArgumentException("Empty ranging parameters");
-        }
-
-        int rangingId;
-        synchronized (mLock) {
-            rangingId = mNextRangingId++;
-        }
-        mStateManager.startRanging(clientId, sessionId, params.mParams, rangingId);
-        return rangingId;
+        mStateManager.requestMacAddresses(uid, peerIds, callback);
     }
 
     @Override
@@ -424,8 +404,7 @@
                 TAG);
     }
 
-    private void enforceConnectivityInternalPermission() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
-                TAG);
+    private void enforceNetworkStackPermission() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.NETWORK_STACK, TAG);
     }
 }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
index 6ced948..89d9a90 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -21,11 +21,11 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.wifi.V1_0.NanStatusType;
-import android.net.wifi.RttManager;
 import android.net.wifi.aware.Characteristics;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
 import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
 import android.net.wifi.aware.WifiAwareManager;
@@ -48,7 +48,6 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
-import com.android.server.wifi.util.NativeUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
 
 import libcore.util.HexEncoding;
@@ -62,6 +61,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -108,7 +108,6 @@
     private static final int COMMAND_TYPE_ENQUEUE_SEND_MESSAGE = 107;
     private static final int COMMAND_TYPE_ENABLE_USAGE = 108;
     private static final int COMMAND_TYPE_DISABLE_USAGE = 109;
-    private static final int COMMAND_TYPE_START_RANGING = 110;
     private static final int COMMAND_TYPE_GET_CAPABILITIES = 111;
     private static final int COMMAND_TYPE_CREATE_ALL_DATA_PATH_INTERFACES = 112;
     private static final int COMMAND_TYPE_DELETE_ALL_DATA_PATH_INTERFACES = 113;
@@ -167,7 +166,6 @@
     private static final String MESSAGE_BUNDLE_KEY_MAC_ADDRESS = "mac_address";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE_DATA = "message_data";
     private static final String MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID = "req_instance_id";
-    private static final String MESSAGE_BUNDLE_KEY_RANGING_ID = "ranging_id";
     private static final String MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME = "message_queue_time";
     private static final String MESSAGE_BUNDLE_KEY_RETRY_COUNT = "retry_count";
     private static final String MESSAGE_BUNDLE_KEY_SUCCESS_FLAG = "success_flag";
@@ -185,6 +183,8 @@
     private static final String MESSAGE_BUNDLE_KEY_PMK = "pmk";
     private static final String MESSAGE_BUNDLE_KEY_PASSPHRASE = "passphrase";
     private static final String MESSAGE_BUNDLE_KEY_OOB = "out_of_band";
+    private static final String MESSAGE_RANGING_INDICATION = "ranging_indication";
+    private static final String MESSAGE_RANGE_MM = "range_mm";
 
     private WifiAwareNativeApi mWifiAwareNativeApi;
     private WifiAwareNativeManager mWifiAwareNativeManager;
@@ -203,7 +203,6 @@
     private volatile Capabilities mCapabilities;
     private volatile Characteristics mCharacteristics = null;
     private WifiAwareStateMachine mSm;
-    private WifiAwareRttStateManager mRtt;
     public WifiAwareDataPathStateManager mDataPathMgr;
     private PowerManager mPowerManager;
 
@@ -348,7 +347,6 @@
         mSm.setDbg(DBG);
         mSm.start();
 
-        mRtt = new WifiAwareRttStateManager();
         mDataPathMgr = new WifiAwareDataPathStateManager(this);
         mDataPathMgr.start(mContext, mSm.getHandler().getLooper(), awareMetrics,
                 permissionsWrapper);
@@ -417,6 +415,48 @@
     }
 
     /*
+     * Cross-service API: synchronized but independent of state machine
+     */
+
+    /**
+     * Translate (and return in the callback) the peerId to its MAC address representation.
+     */
+    public void requestMacAddresses(int uid, List<Integer> peerIds,
+            IWifiAwareMacAddressProvider callback) {
+        mSm.getHandler().post(() -> {
+            if (VDBG) Log.v(TAG, "requestMacAddresses: uid=" + uid + ", peerIds=" + peerIds);
+            Map<Integer, byte[]> peerIdToMacMap = new HashMap<>();
+            for (int i = 0; i < mClients.size(); ++i) {
+                WifiAwareClientState client = mClients.valueAt(i);
+                if (client.getUid() != uid) {
+                    continue;
+                }
+
+                SparseArray<WifiAwareDiscoverySessionState> sessions = client.getSessions();
+                for (int j = 0; j < sessions.size(); ++j) {
+                    WifiAwareDiscoverySessionState session = sessions.valueAt(j);
+
+                    for (int peerId : peerIds) {
+                        WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo(
+                                peerId);
+                        if (peerInfo != null) {
+                            peerIdToMacMap.put(peerId, peerInfo.mMac);
+                        }
+                    }
+                }
+            }
+
+            try {
+                if (VDBG) Log.v(TAG, "requestMacAddresses: peerIdToMacMap=" + peerIdToMacMap);
+                callback.macAddress(peerIdToMacMap);
+            } catch (RemoteException e) {
+                Log.e(TAG, "requestMacAddress (sync): exception on callback -- " + e);
+
+            }
+        });
+    }
+
+    /*
      * COMMANDS
      */
 
@@ -553,20 +593,6 @@
     }
 
     /**
-     * Place a request to range a peer on the discovery session on the state machine queue.
-     */
-    public void startRanging(int clientId, int sessionId, RttManager.RttParams[] params,
-                             int rangingId) {
-        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
-        msg.arg1 = COMMAND_TYPE_START_RANGING;
-        msg.arg2 = clientId;
-        msg.obj = params;
-        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
-        msg.getData().putInt(MESSAGE_BUNDLE_KEY_RANGING_ID, rangingId);
-        mSm.sendMessage(msg);
-    }
-
-    /**
      * Enable usage of Aware. Doesn't actually turn on Aware (form clusters) - that
      * only happens when a connection is created.
      */
@@ -920,7 +946,7 @@
      * matching service (to the one we were looking for).
      */
     public void onMatchNotification(int pubSubId, int requestorInstanceId, byte[] peerMac,
-            byte[] serviceSpecificInfo, byte[] matchFilter) {
+            byte[] serviceSpecificInfo, byte[] matchFilter, int rangingIndication, int rangeMm) {
         Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
         msg.arg1 = NOTIFICATION_TYPE_MATCH;
         msg.arg2 = pubSubId;
@@ -928,6 +954,8 @@
         msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
         msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA, serviceSpecificInfo);
         msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA, matchFilter);
+        msg.getData().putInt(MESSAGE_RANGING_INDICATION, rangingIndication);
+        msg.getData().putInt(MESSAGE_RANGE_MM, rangeMm);
         mSm.sendMessage(msg);
     }
 
@@ -1231,9 +1259,11 @@
                     byte[] serviceSpecificInfo = msg.getData()
                             .getByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA);
                     byte[] matchFilter = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA);
+                    int rangingIndication = msg.getData().getInt(MESSAGE_RANGING_INDICATION);
+                    int rangeMm = msg.getData().getInt(MESSAGE_RANGE_MM);
 
                     onMatchLocal(pubSubId, requestorInstanceId, peerMac, serviceSpecificInfo,
-                            matchFilter);
+                            matchFilter, rangingIndication, rangeMm);
                     break;
                 }
                 case NOTIFICATION_TYPE_SESSION_TERMINATED: {
@@ -1525,18 +1555,6 @@
                 case COMMAND_TYPE_DISABLE_USAGE:
                     waitForResponse = disableUsageLocal(mCurrentTransactionId);
                     break;
-                case COMMAND_TYPE_START_RANGING: {
-                    Bundle data = msg.getData();
-
-                    int clientId = msg.arg2;
-                    RttManager.RttParams[] params = (RttManager.RttParams[]) msg.obj;
-                    int sessionId = data.getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
-                    int rangingId = data.getInt(MESSAGE_BUNDLE_KEY_RANGING_ID);
-
-                    startRangingLocal(clientId, sessionId, params, rangingId);
-                    waitForResponse = false;
-                    break;
-                }
                 case COMMAND_TYPE_GET_CAPABILITIES:
                     if (mCapabilities == null) {
                         waitForResponse = mWifiAwareNativeApi.getCapabilities(
@@ -1613,8 +1631,7 @@
                     waitForResponse = endDataPathLocal(mCurrentTransactionId, msg.arg2);
                     break;
                 case COMMAND_TYPE_DELAYED_INITIALIZATION:
-                    mWifiAwareNativeManager.start();
-                    mRtt.start(mContext, mSm.getHandler().getLooper());
+                    mWifiAwareNativeManager.start(getHandler());
                     waitForResponse = false;
                     break;
                 default:
@@ -1827,9 +1844,6 @@
                 case COMMAND_TYPE_DISABLE_USAGE:
                     Log.wtf(TAG, "processTimeout: DISABLE_USAGE - shouldn't be waiting!");
                     break;
-                case COMMAND_TYPE_START_RANGING:
-                    Log.wtf(TAG, "processTimeout: START_RANGING - shouldn't be waiting!");
-                    break;
                 case COMMAND_TYPE_GET_CAPABILITIES:
                     Log.e(TAG,
                             "processTimeout: GET_CAPABILITIES timed-out - strange, will try again"
@@ -2308,49 +2322,6 @@
         return callDispatched;
     }
 
-    private void startRangingLocal(int clientId, int sessionId, RttManager.RttParams[] params,
-                                   int rangingId) {
-        if (VDBG) {
-            Log.v(TAG, "startRangingLocal: clientId=" + clientId + ", sessionId=" + sessionId
-                    + ", parms=" + Arrays.toString(params) + ", rangingId=" + rangingId);
-        }
-
-        WifiAwareClientState client = mClients.get(clientId);
-        if (client == null) {
-            Log.e(TAG, "startRangingLocal: no client exists for clientId=" + clientId);
-            return;
-        }
-
-        WifiAwareDiscoverySessionState session = client.getSession(sessionId);
-        if (session == null) {
-            Log.e(TAG, "startRangingLocal: no session exists for clientId=" + clientId
-                    + ", sessionId=" + sessionId);
-            client.onRangingFailure(rangingId, RttManager.REASON_INVALID_REQUEST,
-                    "Invalid session ID");
-            return;
-        }
-
-        for (RttManager.RttParams param : params) {
-            String peerIdStr = param.bssid;
-            try {
-                WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo(
-                        Integer.parseInt(peerIdStr));
-                if (peerInfo == null || peerInfo.mMac == null) {
-                    Log.d(TAG, "startRangingLocal: no MAC address for peer ID=" + peerIdStr);
-                    param.bssid = "";
-                } else {
-                    param.bssid = NativeUtil.macAddressFromByteArray(peerInfo.mMac);
-                }
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "startRangingLocal: invalid peer ID specification (in bssid field): '"
-                        + peerIdStr + "'");
-                param.bssid = "";
-            }
-        }
-
-        mRtt.startRanging(rangingId, client, params);
-    }
-
     private boolean initiateDataPathSetupLocal(short transactionId,
             WifiAwareNetworkSpecifier networkSpecifier, int peerId, int channelRequestType,
             int channel, byte[] peer, String interfaceName, byte[] pmk, String passphrase,
@@ -2823,13 +2794,14 @@
     }
 
     private void onMatchLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
-            byte[] serviceSpecificInfo, byte[] matchFilter) {
+            byte[] serviceSpecificInfo, byte[] matchFilter, int rangingIndication, int rangeMm) {
         if (VDBG) {
             Log.v(TAG,
                     "onMatch: pubSubId=" + pubSubId + ", requestorInstanceId=" + requestorInstanceId
                             + ", peerDiscoveryMac=" + String.valueOf(HexEncoding.encode(peerMac))
                             + ", serviceSpecificInfo=" + Arrays.toString(serviceSpecificInfo)
-                            + ", matchFilter=" + Arrays.toString(matchFilter));
+                            + ", matchFilter=" + Arrays.toString(matchFilter)
+                            + ", rangingIndication=" + rangingIndication + ", rangeMm=" + rangeMm);
         }
 
         Pair<WifiAwareClientState, WifiAwareDiscoverySessionState> data =
@@ -2839,7 +2811,8 @@
             return;
         }
 
-        data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo, matchFilter);
+        data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo, matchFilter,
+                rangingIndication, rangeMm);
     }
 
     private void onSessionTerminatedLocal(int pubSubId, boolean isPublish, int reason) {
@@ -3061,7 +3034,6 @@
         }
         pw.println("  mSettableParameters: " + mSettableParameters);
         mSm.dump(fd, pw, args);
-        mRtt.dump(fd, pw, args);
         mDataPathMgr.dump(fd, pw, args);
         mWifiAwareNativeApi.dump(fd, pw, args);
     }
diff --git a/service/java/com/android/server/wifi/hotspot2/OWNERS b/service/java/com/android/server/wifi/hotspot2/OWNERS
index bea5780..08b6c9e 100644
--- a/service/java/com/android/server/wifi/hotspot2/OWNERS
+++ b/service/java/com/android/server/wifi/hotspot2/OWNERS
@@ -1 +1,2 @@
-zqiu@google.com
+sohanirao@google.com
+etancohen@google.com
diff --git a/service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java b/service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
new file mode 100644
index 0000000..f1bf117
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 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.hotspot2;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Responsible for setup/monitor on Wi-Fi state and connection to the OSU AP.
+ */
+public class OsuNetworkConnection {
+    private static final String TAG = "OsuNetworkConnection";
+
+    private final Context mContext;
+
+    private boolean mVerboseLoggingEnabled = false;
+    private WifiManager mWifiManager;
+    private Callbacks mCallbacks;
+    private boolean mConnected = false;
+    private int mNetworkId = -1;
+    private boolean mWifiEnabled = false;
+
+    /**
+     * Callbacks on Wi-Fi connection state changes.
+     */
+    public interface Callbacks {
+        /**
+         * Invoked when network connection is established with IP connectivity.
+         *
+         * @param network {@link Network} associated with the connected network.
+         */
+        void onConnected(Network network);
+
+        /**
+         * Invoked when the targeted network is disconnected.
+         */
+        void onDisconnected();
+
+        /**
+         * Invoked when a timer tracking connection request is not reset by successfull connection.
+         */
+        void onTimeOut();
+
+        /**
+         * Invoked when Wifi is enabled.
+         */
+        void onWifiEnabled();
+
+        /**
+         * Invoked when Wifi is disabled.
+         */
+        void onWifiDisabled();
+    }
+
+    /**
+     * Create an instance of {@link NetworkConnection} for the specified Wi-Fi network.
+     * @param context The application context
+     */
+    public OsuNetworkConnection(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Called to initialize tracking of wifi state and network events by registering for the
+     * corresponding intents.
+     */
+    public void init(Handler handler) {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                    handleNetworkStateChanged(
+                            intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO),
+                            intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO));
+                } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+                    int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                            WifiManager.WIFI_STATE_UNKNOWN);
+                    handleWifiStateChanged(state);
+                }
+            }
+        };
+        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        mContext.registerReceiver(receiver, filter, null, handler);
+        mWifiEnabled = mWifiManager.isWifiEnabled();
+    }
+
+    /**
+     * Disconnect, if required in the two cases
+     * - still connected to the OSU AP
+     * - connection to OSU AP was requested and in progress
+     */
+    public void disconnectIfNeeded() {
+        if (mNetworkId < 0) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "No connection to tear down");
+            }
+            return;
+        }
+        mWifiManager.removeNetwork(mNetworkId);
+        mNetworkId = -1;
+        mConnected = false;
+        if (mCallbacks != null) {
+            mCallbacks.onDisconnected();
+        }
+    }
+
+    /**
+     * Register for network and Wifi state events
+     * @param callbacks The callbacks to be invoked on network change events
+     */
+    public void setEventCallback(Callbacks callbacks) {
+        mCallbacks = callbacks;
+    }
+
+    /**
+     * Connect to a OSU Wi-Fi network specified by the given SSID. The security type of the Wi-Fi
+     * network is either open or OSEN (OSU Server-only authenticated layer 2 Encryption Network).
+     * When network access identifier is provided, OSEN is used.
+     *
+     * @param ssid The SSID to connect to
+     * @param nai Network access identifier of the network
+     *
+     * @return boolean true if connection was successfully initiated
+     */
+    public boolean connect(WifiSsid ssid, String nai) {
+        if (mConnected) {
+            if (mVerboseLoggingEnabled) {
+                // Already connected
+                Log.v(TAG, "Connect called twice");
+            }
+            return true;
+        }
+        if (!mWifiManager.isWifiEnabled()) {
+            Log.w(TAG, "Wifi is not enabled");
+            return false;
+        }
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "\"" + ssid.toString() + "\"";
+        if (TextUtils.isEmpty(nai)) {
+            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        } else {
+            // TODO(sohanirao): Handle OSEN.
+            Log.w(TAG, "OSEN not supported");
+            return false;
+        }
+        mNetworkId = mWifiManager.addNetwork(config);
+        if (mNetworkId < 0) {
+            Log.e(TAG, "Unable to add network");
+            return false;
+        }
+        if (!mWifiManager.enableNetwork(mNetworkId, true)) {
+            Log.e(TAG, "Unable to enable network " + mNetworkId);
+            disconnectIfNeeded();
+            return false;
+        }
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Current network ID " + mNetworkId);
+        }
+        // TODO(sohanirao): setup alarm to time out the connection attempt.
+        return true;
+    }
+
+    /**
+     * Method to update logging level in this class
+     * @param verbose more than 0 enables verbose logging
+     */
+    public void enableVerboseLogging(int verbose) {
+        mVerboseLoggingEnabled = verbose > 0 ? true : false;
+    }
+
+    /**
+     * Handle network state changed events.
+     *
+     * @param networkInfo {@link NetworkInfo} indicating the current network state
+     * @param wifiInfo {@link WifiInfo} associated with the current network when connected
+     */
+    private void handleNetworkStateChanged(NetworkInfo networkInfo, WifiInfo wifiInfo) {
+        if (networkInfo == null) {
+            Log.w(TAG, "NetworkInfo not provided for network state changed event");
+            return;
+        }
+        switch (networkInfo.getDetailedState()) {
+            case CONNECTED:
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Connected event received");
+                }
+                if (wifiInfo == null) {
+                    Log.w(TAG, "WifiInfo not provided for network state changed event");
+                    return;
+                }
+                handleConnectedEvent(wifiInfo);
+                break;
+            case DISCONNECTED:
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Disconnected event received");
+                }
+                disconnectIfNeeded();
+                break;
+            default:
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Ignore uninterested state: " + networkInfo.getDetailedState());
+                }
+                break;
+        }
+    }
+
+    /**
+     * Handle network connected event.
+     *
+     * @param wifiInfo {@link WifiInfo} associated with the current connection
+     */
+    private void handleConnectedEvent(WifiInfo wifiInfo) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "handleConnectedEvent " + wifiInfo.getNetworkId());
+        }
+        if (wifiInfo.getNetworkId() != mNetworkId) {
+            disconnectIfNeeded();
+            return;
+        }
+        if (!mConnected) {
+            mConnected = true;
+            if (mCallbacks != null) {
+                mCallbacks.onConnected(mWifiManager.getCurrentNetwork());
+            }
+        }
+    }
+
+    /**
+     * Handle Wifi state change event
+     */
+    private void handleWifiStateChanged(int state) {
+        if (state == WifiManager.WIFI_STATE_DISABLED && mWifiEnabled) {
+            mWifiEnabled = false;
+            if (mCallbacks != null) mCallbacks.onWifiDisabled();
+        }
+        if (state == WifiManager.WIFI_STATE_ENABLED && !mWifiEnabled) {
+            mWifiEnabled = true;
+            if (mCallbacks != null) mCallbacks.onWifiEnabled();
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
index 5d79ba4..fec3dd8 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -33,8 +33,10 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -99,6 +101,7 @@
     private final WifiConfigManager mWifiConfigManager;
     private final CertificateVerifier mCertVerifier;
     private final WifiMetrics mWifiMetrics;
+    private final PasspointProvisioner mPasspointProvisioner;
 
     // Counter used for assigning unique identifier to each provider.
     private long mProviderIndex;
@@ -212,10 +215,27 @@
         mProviderIndex = 0;
         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData(
                 mKeyStore, mSimAccessor, new DataSourceHandler()));
+        mPasspointProvisioner = objectFactory.makePasspointProvisioner(context,
+                objectFactory.makeOsuNetworkConnection(context));
         sPasspointManager = this;
     }
 
     /**
+     * Initializes the provisioning flow with a looper
+     */
+    public void initializeProvisioner(Looper looper) {
+        mPasspointProvisioner.init(looper);
+    }
+
+    /**
+     * Enable verbose logging
+     * @param verbose more than 0 enables verbose logging
+     */
+    public void enableVerboseLogging(int verbose) {
+        mPasspointProvisioner.enableVerboseLogging(verbose);
+    }
+
+    /**
      * Add or update a Passpoint provider with the given configuration.
      *
      * Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name).
@@ -325,6 +345,44 @@
      * @return A pair of {@link PasspointProvider} and match status.
      */
     public Pair<PasspointProvider, PasspointMatch> matchProvider(ScanResult scanResult) {
+        List<Pair<PasspointProvider, PasspointMatch>> allMatches = getAllMatchedProviders(
+                scanResult);
+        if (allMatches == null) {
+            return null;
+        }
+
+        Pair<PasspointProvider, PasspointMatch> bestMatch = null;
+        for (Pair<PasspointProvider, PasspointMatch> match : allMatches) {
+            if (match.second == PasspointMatch.HomeProvider) {
+                bestMatch = match;
+                break;
+            }
+            if (match.second == PasspointMatch.RoamingProvider && bestMatch == null) {
+                bestMatch = match;
+            }
+        }
+
+        if (bestMatch != null) {
+            Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
+                    bestMatch.first.getConfig().getHomeSp().getFqdn(),
+                    bestMatch.second == PasspointMatch.HomeProvider ? "Home Provider"
+                            : "Roaming Provider"));
+        } else {
+            Log.d(TAG, "Match not found for " + scanResult.SSID);
+        }
+        return bestMatch;
+    }
+
+    /**
+     * Return a list of all providers that can provide service through the given AP.
+     *
+     * @param scanResult The scan result associated with the AP
+     * @return a list of pairs of {@link PasspointProvider} and match status.
+     */
+    public List<Pair<PasspointProvider, PasspointMatch>> getAllMatchedProviders(
+            ScanResult scanResult) {
+        List<Pair<PasspointProvider, PasspointMatch>> allMatches = new ArrayList<>();
+
         // Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0
         // Vendor Specific IE.
         InformationElementUtil.RoamingConsortium roamingConsortium =
@@ -338,7 +396,7 @@
             bssid = Utils.parseMac(scanResult.BSSID);
         } catch (IllegalArgumentException e) {
             Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
-            return null;
+            return allMatches;
         }
         ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
                 vsa.anqpDomainID);
@@ -349,30 +407,30 @@
                     roamingConsortium.anqpOICount > 0,
                     vsa.hsRelease  == NetworkDetail.HSRelease.R2);
             Log.d(TAG, "ANQP entry not found for: " + anqpKey);
-            return null;
+            return allMatches;
         }
 
-        Pair<PasspointProvider, PasspointMatch> bestMatch = null;
         for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
             PasspointProvider provider = entry.getValue();
             PasspointMatch matchStatus = provider.match(anqpEntry.getElements());
-            if (matchStatus == PasspointMatch.HomeProvider) {
-                bestMatch = Pair.create(provider, matchStatus);
-                break;
-            }
-            if (matchStatus == PasspointMatch.RoamingProvider && bestMatch == null) {
-                bestMatch = Pair.create(provider, matchStatus);
+            if (matchStatus == PasspointMatch.HomeProvider
+                    || matchStatus == PasspointMatch.RoamingProvider) {
+                allMatches.add(Pair.create(provider, matchStatus));
             }
         }
-        if (bestMatch != null) {
-            Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
-                    bestMatch.first.getConfig().getHomeSp().getFqdn(),
-                    bestMatch.second == PasspointMatch.HomeProvider ? "Home Provider"
-                            : "Roaming Provider"));
+
+        if (allMatches.size() != 0) {
+            for (Pair<PasspointProvider, PasspointMatch> match : allMatches) {
+                Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
+                        match.first.getConfig().getHomeSp().getFqdn(),
+                        match.second == PasspointMatch.HomeProvider ? "Home Provider"
+                                : "Roaming Provider"));
+            }
         } else {
-            Log.d(TAG, "Match not found for " + scanResult.SSID);
+            Log.d(TAG, "No matches not found for " + scanResult.SSID);
         }
-        return bestMatch;
+
+        return allMatches;
     }
 
     /**
@@ -497,6 +555,38 @@
     }
 
     /**
+     * Match the given WiFi AP to all installed Passpoint configurations. Return the list of all
+     * matching configurations (or an empty list if none).
+     *
+     * @param scanResult The scan result of the given AP
+     * @return List of {@link WifiConfiguration}
+     */
+    public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+        if (scanResult == null) {
+            Log.e(TAG, "Attempt to get matching config for a null ScanResult");
+            return new ArrayList<WifiConfiguration>();
+        }
+        if (!scanResult.isPasspointNetwork()) {
+            Log.e(TAG, "Attempt to get matching config for a non-Passpoint AP");
+            return new ArrayList<WifiConfiguration>();
+        }
+
+        List<Pair<PasspointProvider, PasspointMatch>> matchedProviders = getAllMatchedProviders(
+                scanResult);
+        List<WifiConfiguration> configs = new ArrayList<>();
+        for (Pair<PasspointProvider, PasspointMatch> matchedProvider : matchedProviders) {
+            WifiConfiguration config = matchedProvider.first.getWifiConfig();
+            config.SSID = ScanResultUtil.createQuotedSSID(scanResult.SSID);
+            if (matchedProvider.second == PasspointMatch.HomeProvider) {
+                config.isHomeProviderNetwork = true;
+            }
+            configs.add(config);
+        }
+
+        return configs;
+    }
+
+    /**
      * Return the list of Hosspot 2.0 OSU (Online Sign-Up) providers associated with the given
      * AP.
      *
@@ -634,4 +724,16 @@
         mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider);
         return true;
     }
+
+    /**
+     * Start the subscription provisioning flow with a provider.
+     * @param callingUid integer indicating the uid of the caller
+     * @param provider {@link OsuProvider} the provider to subscribe to
+     * @param callback {@link IProvisioningCallback} callback to update status to the caller
+     * @return boolean return value from the provisioning method
+     */
+    public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
+            IProvisioningCallback callback) {
+        return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback);
+    }
 }
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
index c41c49a..71fc47a 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi.hotspot2;
 
+import android.content.Context;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 
 import com.android.server.wifi.Clock;
@@ -95,4 +96,25 @@
     public CertificateVerifier makeCertificateVerifier() {
         return new CertificateVerifier();
     }
+
+    /**
+     * Create an instance of {@link PasspointProvisioner}.
+     *
+     * @param context
+     * @return {@link PasspointProvisioner}
+     */
+    public PasspointProvisioner makePasspointProvisioner(Context context,
+            OsuNetworkConnection osuNetworkConnection) {
+        return new PasspointProvisioner(context, osuNetworkConnection);
+    }
+
+    /**
+     * Create an instance of {@link OsuNetworkConnection}.
+     *
+     * @param context
+     * @return {@link OsuNetworkConnection}
+     */
+    public OsuNetworkConnection makeOsuNetworkConnection(Context context) {
+        return new OsuNetworkConnection(context);
+    }
 }
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
new file mode 100644
index 0000000..d3bb773
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvisioner.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 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.hotspot2;
+
+import android.content.Context;
+import android.net.Network;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.OsuProvider;
+import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Provides methods to carry out provisioning flow
+ */
+public class PasspointProvisioner {
+    private static final String TAG = "PasspointProvisioner";
+
+    private static final int PROVISIONING_STATUS = 0;
+    private static final int PROVISIONING_FAILURE = 1;
+
+    private final Context mContext;
+    private final ProvisioningStateMachine mProvisioningStateMachine;
+    private final OsuNetworkCallbacks mOsuNetworkCallbacks;
+    private final OsuNetworkConnection mOsuNetworkConnection;
+
+    private int mCallingUid;
+    private boolean mVerboseLoggingEnabled = false;
+
+    PasspointProvisioner(Context context, OsuNetworkConnection osuNetworkConnection) {
+        mContext = context;
+        mOsuNetworkConnection = osuNetworkConnection;
+        mProvisioningStateMachine = new ProvisioningStateMachine();
+        mOsuNetworkCallbacks = new OsuNetworkCallbacks();
+    }
+
+    /**
+     * Sets up for provisioning
+     * @param looper Looper on which the Provisioning state machine will run
+     */
+    public void init(Looper looper) {
+        mProvisioningStateMachine.start(new Handler(looper));
+        mOsuNetworkConnection.init(mProvisioningStateMachine.getHandler());
+    }
+
+    /**
+     * Enable verbose logging to help debug failures
+     * @param level integer indicating verbose logging enabled if > 0
+     */
+    public void enableVerboseLogging(int level) {
+        mVerboseLoggingEnabled = (level > 0) ? true : false;
+        mOsuNetworkConnection.enableVerboseLogging(level);
+    }
+
+    /**
+     * Start provisioning flow with a given provider.
+     * @param callingUid calling uid.
+     * @param provider {@link OsuProvider} to provision with.
+     * @param callback {@link IProvisioningCallback} to provide provisioning status.
+     * @return boolean value, true if provisioning was started, false otherwise.
+     *
+     * Implements HS2.0 provisioning flow with a given HS2.0 provider.
+     */
+    public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
+            IProvisioningCallback callback) {
+        mCallingUid = callingUid;
+
+        Log.v(TAG, "Provisioning started with " + provider.toString());
+
+        mProvisioningStateMachine.getHandler().post(() -> {
+            mProvisioningStateMachine.startProvisioning(provider, callback);
+        });
+
+        return true;
+    }
+
+    /**
+     * Handles the provisioning flow state transitions
+     */
+    class ProvisioningStateMachine {
+        private static final String TAG = "ProvisioningStateMachine";
+
+        private static final int DEFAULT_STATE                             = 0;
+        private static final int INITIAL_STATE                             = 1;
+        private static final int WAITING_TO_CONNECT                        = 2;
+        private static final int OSU_AP_CONNECTED                          = 3;
+        private static final int FAILED_STATE                              = 4;
+
+        private OsuProvider mOsuProvider;
+        private IProvisioningCallback mProvisioningCallback;
+        private Handler mHandler;
+        private int mState;
+
+        ProvisioningStateMachine() {
+            mState = DEFAULT_STATE;
+        }
+
+        /**
+         * Initializes and starts the state machine with a handler to handle incoming events
+         */
+        public void start(Handler handler) {
+            mHandler = handler;
+            changeState(INITIAL_STATE);
+        }
+
+        /**
+         * Returns the handler on which a runnable can be posted
+         * @return Handler State Machine's handler
+         */
+        public Handler getHandler() {
+            return mHandler;
+        }
+
+        /**
+         * Start Provisioning with the Osuprovider and invoke callbacks
+         * @param provider OsuProvider to provision with
+         * @param callback IProvisioningCallback to invoke callbacks on
+         */
+        public void startProvisioning(OsuProvider provider, IProvisioningCallback callback) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "startProvisioning received in state=" + mState);
+            }
+            if (mState != INITIAL_STATE) {
+                Log.v(TAG, "State Machine needs to be reset before starting provisioning");
+                resetStateMachine();
+            }
+            mProvisioningCallback = callback;
+            mOsuProvider = provider;
+
+            // Register for network and wifi state events during provisioning flow
+            mOsuNetworkConnection.setEventCallback(mOsuNetworkCallbacks);
+
+            if (mOsuNetworkConnection.connect(mOsuProvider.getOsuSsid(),
+                    mOsuProvider.getNetworkAccessIdentifier())) {
+                invokeProvisioningCallback(PROVISIONING_STATUS,
+                        ProvisioningCallback.OSU_STATUS_AP_CONNECTING);
+                changeState(WAITING_TO_CONNECT);
+            } else {
+                invokeProvisioningCallback(PROVISIONING_FAILURE,
+                        ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+                enterFailedState();
+            }
+        }
+
+        /**
+         * Handle Wifi Disable event
+         */
+        public void handleWifiDisabled() {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Wifi Disabled in state=" + mState);
+            }
+            if (mState == INITIAL_STATE || mState == FAILED_STATE) {
+                Log.w(TAG, "Wifi Disable unhandled in state=" + mState);
+                return;
+            }
+            invokeProvisioningCallback(PROVISIONING_FAILURE,
+                    ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+            enterFailedState();
+        }
+
+        private void resetStateMachine() {
+            // Set to null so that no callbacks are invoked during reset
+            mProvisioningCallback = null;
+            if (mState != INITIAL_STATE || mState != FAILED_STATE) {
+                // Transition through Failed state to clean up
+                enterFailedState();
+            }
+            changeState(INITIAL_STATE);
+        }
+
+        /**
+         * Connected event received
+         * @param network Network object for this connection
+         */
+        public void handleConnectedEvent(Network network) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Connected event received in state=" + mState);
+            }
+            if (mState != WAITING_TO_CONNECT) {
+                // Not waiting for a connection
+                Log.w(TAG, "Connection event unhandled in state=" + mState);
+                return;
+            }
+            invokeProvisioningCallback(PROVISIONING_STATUS,
+                    ProvisioningCallback.OSU_STATUS_AP_CONNECTED);
+            changeState(OSU_AP_CONNECTED);
+        }
+
+        /**
+         * Disconnect event received
+         */
+        public void handleDisconnect() {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Connection failed in state=" + mState);
+            }
+            if (mState == INITIAL_STATE || mState == FAILED_STATE) {
+                Log.w(TAG, "Disconnect event unhandled in state=" + mState);
+                return;
+            }
+            invokeProvisioningCallback(PROVISIONING_FAILURE,
+                    ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+            enterFailedState();
+        }
+
+        private void changeState(int nextState) {
+            if (nextState != mState) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Changing state from " + mState + " -> " + nextState);
+                }
+                mState = nextState;
+            }
+        }
+
+        private void invokeProvisioningCallback(int callbackType, int status) {
+            if (mProvisioningCallback == null) {
+                Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status
+                        + " not invoked, callback is null");
+                return;
+            }
+            try {
+                if (callbackType == PROVISIONING_STATUS) {
+                    mProvisioningCallback.onProvisioningStatus(status);
+                } else {
+                    mProvisioningCallback.onProvisioningFailure(status);
+                }
+            } catch (RemoteException e) {
+                if (callbackType == PROVISIONING_STATUS) {
+                    Log.e(TAG, "Remote Exception while posting Provisioning status " + status);
+                } else {
+                    Log.e(TAG, "Remote Exception while posting Provisioning failure " + status);
+                }
+            }
+        }
+
+        private void enterFailedState() {
+            changeState(FAILED_STATE);
+            mOsuNetworkConnection.setEventCallback(null);
+            mOsuNetworkConnection.disconnectIfNeeded();
+        }
+    }
+
+    /**
+     * Callbacks for network and wifi events
+     */
+    class OsuNetworkCallbacks implements OsuNetworkConnection.Callbacks {
+
+        OsuNetworkCallbacks() {
+        }
+
+        @Override
+        public void onConnected(Network network) {
+            Log.v(TAG, "onConnected to " + network);
+            if (network == null) {
+                mProvisioningStateMachine.getHandler().post(() -> {
+                    mProvisioningStateMachine.handleDisconnect();
+                });
+            } else {
+                mProvisioningStateMachine.getHandler().post(() -> {
+                    mProvisioningStateMachine.handleConnectedEvent(network);
+                });
+            }
+        }
+
+        @Override
+        public void onDisconnected() {
+            Log.v(TAG, "onDisconnected");
+            mProvisioningStateMachine.getHandler().post(() -> {
+                mProvisioningStateMachine.handleDisconnect();
+            });
+        }
+
+        @Override
+        public void onTimeOut() {
+            Log.v(TAG, "Timed out waiting for connection to OSU AP");
+            mProvisioningStateMachine.getHandler().post(() -> {
+                mProvisioningStateMachine.handleDisconnect();
+            });
+        }
+
+        @Override
+        public void onWifiEnabled() {
+            Log.v(TAG, "onWifiEnabled");
+        }
+
+        @Override
+        public void onWifiDisabled() {
+            Log.v(TAG, "onWifiDisabled");
+            mProvisioningStateMachine.getHandler().post(() -> {
+                mProvisioningStateMachine.handleWifiDisabled();
+            });
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index fa16253..751737f 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi.p2p;
 
+import android.annotation.NonNull;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -524,12 +525,12 @@
                     }
                     mHalDeviceManager = mWifiInjector.getHalDeviceManager();
                 }
-                mIWifiP2pIface = mHalDeviceManager.createP2pIface(() -> {
+                mIWifiP2pIface = mHalDeviceManager.createP2pIface((@NonNull String ifaceName) -> {
                     if (DBG) Log.d(TAG, "IWifiP2pIface destroyedListener");
                     synchronized (mLock) {
                         mIWifiP2pIface = null;
                     }
-                }, mP2pStateMachine.getHandler().getLooper());
+                }, mP2pStateMachine.getHandler());
             }
 
             return messenger;
diff --git a/service/java/com/android/server/wifi/rtt/RttNative.java b/service/java/com/android/server/wifi/rtt/RttNative.java
new file mode 100644
index 0000000..e085352
--- /dev/null
+++ b/service/java/com/android/server/wifi/rtt/RttNative.java
@@ -0,0 +1,327 @@
+/*
+ * 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.rtt;
+
+import android.hardware.wifi.V1_0.IWifiRttController;
+import android.hardware.wifi.V1_0.IWifiRttControllerEventCallback;
+import android.hardware.wifi.V1_0.RttBw;
+import android.hardware.wifi.V1_0.RttConfig;
+import android.hardware.wifi.V1_0.RttPeerType;
+import android.hardware.wifi.V1_0.RttPreamble;
+import android.hardware.wifi.V1_0.RttResult;
+import android.hardware.wifi.V1_0.RttType;
+import android.hardware.wifi.V1_0.WifiChannelWidthInMhz;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.ResponderConfig;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.wifi.HalDeviceManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TBD
+ */
+public class RttNative extends IWifiRttControllerEventCallback.Stub {
+    private static final String TAG = "RttNative";
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    private final RttServiceImpl mRttService;
+    private final HalDeviceManager mHalDeviceManager;
+
+    private Object mLock = new Object();
+
+    private IWifiRttController mIWifiRttController;
+
+    public RttNative(RttServiceImpl rttService, HalDeviceManager halDeviceManager) {
+        mRttService = rttService;
+        mHalDeviceManager = halDeviceManager;
+    }
+
+    /**
+     * Initialize the object - registering with the HAL device manager.
+     */
+    public void start() {
+        synchronized (mLock) {
+            mHalDeviceManager.initialize();
+            mHalDeviceManager.registerStatusListener(() -> {
+                if (VDBG) Log.d(TAG, "hdm.onStatusChanged");
+                updateController();
+            }, null);
+            updateController();
+        }
+    }
+
+    /**
+     * Returns true if Wi-Fi is ready for RTT requests, false otherwise.
+     */
+    public boolean isReady() {
+        synchronized (mLock) {
+            return mIWifiRttController != null;
+        }
+    }
+
+    private void updateController() {
+        if (VDBG) Log.v(TAG, "updateController: mIWifiRttController=" + mIWifiRttController);
+
+        // only care about isStarted (Wi-Fi started) not isReady - since if not
+        // ready then Wi-Fi will also be down.
+        synchronized (mLock) {
+            if (mHalDeviceManager.isStarted()) {
+                if (mIWifiRttController == null) {
+                    mIWifiRttController = mHalDeviceManager.createRttController();
+                    if (mIWifiRttController == null) {
+                        Log.e(TAG, "updateController: Failed creating RTT controller - but Wifi is "
+                                + "started!");
+                    } else {
+                        try {
+                            mIWifiRttController.registerEventCallback(this);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "updateController: exception registering callback: " + e);
+                            mIWifiRttController = null;
+                        }
+                    }
+                }
+            } else {
+                mIWifiRttController = null;
+            }
+
+            if (mIWifiRttController == null) {
+                mRttService.disable();
+            } else {
+                mRttService.enable();
+            }
+        }
+    }
+
+    /**
+     * Issue a range request to the HAL.
+     *
+     * @param cmdId Command ID for the request. Will be used in the corresponding
+     * {@link #onResults(int, ArrayList)}.
+     * @param request Range request.
+     * @return Success status: true for success, false for failure.
+     */
+    public boolean rangeRequest(int cmdId, RangingRequest request) {
+        if (VDBG) Log.v(TAG, "rangeRequest: cmdId=" + cmdId + ", request=" + request);
+        synchronized (mLock) {
+            if (!isReady()) {
+                Log.e(TAG, "rangeRequest: RttController is null");
+                return false;
+            }
+
+            ArrayList<RttConfig> rttConfig = convertRangingRequestToRttConfigs(request);
+            if (rttConfig == null) {
+                Log.e(TAG, "rangeRequest: invalid request parameters");
+                return false;
+            }
+
+            try {
+                WifiStatus status = mIWifiRttController.rangeRequest(cmdId, rttConfig);
+                if (status.code != WifiStatusCode.SUCCESS) {
+                    Log.e(TAG, "rangeRequest: cannot issue range request -- code=" + status.code);
+                    return false;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "rangeRequest: exception issuing range request: " + e);
+                return false;
+            }
+
+            return true;
+        }
+    }
+
+    /**
+     * Cancel an outstanding ranging request: no guarantees of execution - we will ignore any
+     * results which are returned for the canceled request.
+     *
+     * @param cmdId The cmdId issued with the original rangeRequest command.
+     * @param macAddresses A list of MAC addresses for which to cancel the operation.
+     * @return Success status: true for success, false for failure.
+     */
+    public boolean rangeCancel(int cmdId, ArrayList<byte[]> macAddresses) {
+        if (VDBG) Log.v(TAG, "rangeCancel: cmdId=" + cmdId);
+        synchronized (mLock) {
+            if (!isReady()) {
+                Log.e(TAG, "rangeCancel: RttController is null");
+                return false;
+            }
+
+            try {
+                WifiStatus status = mIWifiRttController.rangeCancel(cmdId, macAddresses);
+                if (status.code != WifiStatusCode.SUCCESS) {
+                    Log.e(TAG, "rangeCancel: cannot issue range cancel -- code=" + status.code);
+                    return false;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "rangeCancel: exception issuing range cancel: " + e);
+                return false;
+            }
+
+            return true;
+        }
+    }
+
+    /**
+     * Callback from HAL with range results.
+     *
+     * @param cmdId Command ID specified in the original request
+     * {@link #rangeRequest(int, RangingRequest)}.
+     * @param halResults A list of range results.
+     */
+    @Override
+    public void onResults(int cmdId, ArrayList<RttResult> halResults) {
+        if (VDBG) Log.v(TAG, "onResults: cmdId=" + cmdId + ", # of results=" + halResults.size());
+        List<RangingResult> results = new ArrayList<>(halResults.size());
+
+        mRttService.onRangingResults(cmdId, halResults);
+    }
+
+    private static ArrayList<RttConfig> convertRangingRequestToRttConfigs(RangingRequest request) {
+        ArrayList<RttConfig> rttConfigs = new ArrayList<>(request.mRttPeers.size());
+
+        // Skipping any configurations which have an error (printing out a message).
+        // The caller will only get results for valid configurations.
+        for (ResponderConfig responder: request.mRttPeers) {
+            RttConfig config = new RttConfig();
+
+            System.arraycopy(responder.macAddress.toByteArray(), 0, config.addr, 0,
+                    config.addr.length);
+
+            try {
+                config.type = responder.supports80211mc ? RttType.TWO_SIDED : RttType.ONE_SIDED;
+                config.peer = halRttPeerTypeFromResponderType(responder.responderType);
+                config.channel.width = halChannelWidthFromResponderChannelWidth(
+                        responder.channelWidth);
+                config.channel.centerFreq = responder.frequency;
+                config.channel.centerFreq0 = responder.centerFreq0;
+                config.channel.centerFreq1 = responder.centerFreq1;
+                config.bw = halRttChannelBandwidthFromResponderChannelWidth(responder.channelWidth);
+                config.preamble = halRttPreambleFromResponderPreamble(responder.preamble);
+
+                config.mustRequestLci = false;
+                config.mustRequestLcr = false;
+                if (config.peer == RttPeerType.NAN) {
+                    config.burstPeriod = 0;
+                    config.numBurst = 0;
+                    config.numFramesPerBurst = 5;
+                    config.numRetriesPerRttFrame = 3;
+                    config.numRetriesPerFtmr = 3;
+                    config.burstDuration = 15;
+                } else { // AP + all non-NAN requests
+                    config.burstPeriod = 0;
+                    config.numBurst = 0;
+                    config.numFramesPerBurst = 8;
+                    config.numRetriesPerRttFrame = 0;
+                    config.numRetriesPerFtmr = 0;
+                    config.burstDuration = 15;
+                }
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Invalid configuration: " + e.getMessage());
+                continue;
+            }
+
+            rttConfigs.add(config);
+        }
+
+        return rttConfigs;
+    }
+
+    static int halRttPeerTypeFromResponderType(int responderType) {
+        switch (responderType) {
+            case ResponderConfig.RESPONDER_AP:
+                return RttPeerType.AP;
+            case ResponderConfig.RESPONDER_STA:
+                return RttPeerType.STA;
+            case ResponderConfig.RESPONDER_P2P_GO:
+                return RttPeerType.P2P_GO;
+            case ResponderConfig.RESPONDER_P2P_CLIENT:
+                return RttPeerType.P2P_CLIENT;
+            case ResponderConfig.RESPONDER_AWARE:
+                return RttPeerType.NAN;
+            default:
+                throw new IllegalArgumentException(
+                        "halRttPeerTypeFromResponderType: bad " + responderType);
+        }
+    }
+
+    static int halChannelWidthFromResponderChannelWidth(int responderChannelWidth) {
+        switch (responderChannelWidth) {
+            case ResponderConfig.CHANNEL_WIDTH_20MHZ:
+                return WifiChannelWidthInMhz.WIDTH_20;
+            case ResponderConfig.CHANNEL_WIDTH_40MHZ:
+                return WifiChannelWidthInMhz.WIDTH_40;
+            case ResponderConfig.CHANNEL_WIDTH_80MHZ:
+                return WifiChannelWidthInMhz.WIDTH_80;
+            case ResponderConfig.CHANNEL_WIDTH_160MHZ:
+                return WifiChannelWidthInMhz.WIDTH_160;
+            case ResponderConfig.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+                return WifiChannelWidthInMhz.WIDTH_80P80;
+            default:
+                throw new IllegalArgumentException(
+                        "halChannelWidthFromResponderChannelWidth: bad " + responderChannelWidth);
+        }
+    }
+
+    static int halRttChannelBandwidthFromResponderChannelWidth(int responderChannelWidth) {
+        switch (responderChannelWidth) {
+            case ResponderConfig.CHANNEL_WIDTH_20MHZ:
+                return RttBw.BW_20MHZ;
+            case ResponderConfig.CHANNEL_WIDTH_40MHZ:
+                return RttBw.BW_40MHZ;
+            case ResponderConfig.CHANNEL_WIDTH_80MHZ:
+                return RttBw.BW_80MHZ;
+            case ResponderConfig.CHANNEL_WIDTH_160MHZ:
+                return RttBw.BW_160MHZ;
+            case ResponderConfig.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+                return RttBw.BW_160MHZ;
+            default:
+                throw new IllegalArgumentException(
+                        "halRttChannelBandwidthFromHalBandwidth: bad " + responderChannelWidth);
+        }
+    }
+
+    static int halRttPreambleFromResponderPreamble(int responderPreamble) {
+        switch (responderPreamble) {
+            case ResponderConfig.PREAMBLE_LEGACY:
+                return RttPreamble.LEGACY;
+            case ResponderConfig.PREAMBLE_HT:
+                return RttPreamble.HT;
+            case ResponderConfig.PREAMBLE_VHT:
+                return RttPreamble.VHT;
+            default:
+                throw new IllegalArgumentException(
+                        "halRttPreambleFromResponderPreamble: bad " + responderPreamble);
+        }
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("RttNative:");
+        pw.println("  mHalDeviceManager: " + mHalDeviceManager);
+        pw.println("  mIWifiRttController: " + mIWifiRttController);
+    }
+}
diff --git a/service/java/com/android/server/wifi/rtt/RttService.java b/service/java/com/android/server/wifi/rtt/RttService.java
new file mode 100644
index 0000000..afffbee
--- /dev/null
+++ b/service/java/com/android/server/wifi/rtt/RttService.java
@@ -0,0 +1,73 @@
+/*
+ * 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.rtt;
+
+import android.content.Context;
+import android.net.wifi.aware.IWifiAwareManager;
+import android.os.HandlerThread;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.server.SystemService;
+import com.android.server.wifi.HalDeviceManager;
+import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+
+/**
+ * TBD.
+ */
+public class RttService extends SystemService {
+    private static final String TAG = "RttService";
+    private Context mContext;
+    private RttServiceImpl mImpl;
+
+    public RttService(Context context) {
+        super(context);
+        mContext = context;
+        mImpl = new RttServiceImpl(context);
+    }
+
+    @Override
+    public void onStart() {
+        Log.i(TAG, "Registering " + Context.WIFI_RTT_RANGING_SERVICE);
+        publishBinderService(Context.WIFI_RTT_RANGING_SERVICE, mImpl);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            Log.i(TAG, "Starting " + Context.WIFI_RTT_RANGING_SERVICE);
+
+            WifiInjector wifiInjector = WifiInjector.getInstance();
+            if (wifiInjector == null) {
+                Log.e(TAG, "onBootPhase(PHASE_SYSTEM_SERVICES_READY): NULL injector!");
+                return;
+            }
+
+            HalDeviceManager halDeviceManager = wifiInjector.getHalDeviceManager();
+            HandlerThread handlerThread = wifiInjector.getRttHandlerThread();
+            WifiPermissionsUtil wifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil();
+
+            IWifiAwareManager awareBinder = (IWifiAwareManager) ServiceManager.getService(
+                    Context.WIFI_AWARE_SERVICE);
+
+            RttNative rttNative = new RttNative(mImpl, halDeviceManager);
+            mImpl.start(handlerThread.getLooper(), wifiInjector.getClock(), awareBinder, rttNative,
+                    wifiPermissionsUtil);
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/rtt/RttServiceImpl.java b/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
new file mode 100644
index 0000000..3417f74
--- /dev/null
+++ b/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -0,0 +1,885 @@
+/*
+ * 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.rtt;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.wifi.V1_0.RttResult;
+import android.hardware.wifi.V1_0.RttStatus;
+import android.net.MacAddress;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
+import android.net.wifi.aware.IWifiAwareManager;
+import android.net.wifi.rtt.IRttCallback;
+import android.net.wifi.rtt.IWifiRttManager;
+import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.RangingResultCallback;
+import android.net.wifi.rtt.ResponderConfig;
+import android.net.wifi.rtt.WifiRttManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.WakeupMessage;
+import com.android.server.wifi.Clock;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+/**
+ * Implementation of the IWifiRttManager AIDL interface and of the RttService state manager.
+ */
+public class RttServiceImpl extends IWifiRttManager.Stub {
+    private static final String TAG = "RttServiceImpl";
+    private static final boolean VDBG = false; // STOPSHIP if true
+
+    private final Context mContext;
+    private Clock mClock;
+    private IWifiAwareManager mAwareBinder;
+    private RttNative mRttNative;
+    private WifiPermissionsUtil mWifiPermissionsUtil;
+    private ActivityManager mActivityManager;
+    private PowerManager mPowerManager;
+
+    private RttServiceSynchronized mRttServiceSynchronized;
+
+    /* package */ static final String HAL_RANGING_TIMEOUT_TAG = TAG + " HAL Ranging Timeout";
+
+    private static final long HAL_RANGING_TIMEOUT_MS = 5_000; // 5 sec
+
+    // TODO: b/69323456 convert to a settable value
+    /* package */ static final long BACKGROUND_PROCESS_EXEC_GAP_MS = 1_800_000; // 30 min
+
+    // arbitrary, larger than anything reasonable
+    /* package */ static final int MAX_QUEUED_PER_UID = 20;
+
+    public RttServiceImpl(Context context) {
+        mContext = context;
+    }
+
+    /*
+     * INITIALIZATION
+     */
+
+    /**
+     * Initializes the RTT service (usually with objects from an injector).
+     *
+     * @param looper The looper on which to synchronize operations.
+     * @param clock A mockable clock.
+     * @param awareBinder The Wi-Fi Aware service (binder) if supported on the system.
+     * @param rttNative The Native interface to the HAL.
+     * @param wifiPermissionsUtil Utility for permission checks.
+     */
+    public void start(Looper looper, Clock clock, IWifiAwareManager awareBinder,
+            RttNative rttNative,
+            WifiPermissionsUtil wifiPermissionsUtil) {
+        mClock = clock;
+        mAwareBinder = awareBinder;
+        mRttNative = rttNative;
+        mWifiPermissionsUtil = wifiPermissionsUtil;
+        mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
+
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (VDBG) Log.v(TAG, "BroadcastReceiver: action=" + action);
+
+                if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
+                    if (mPowerManager.isDeviceIdleMode()) {
+                        disable();
+                    } else {
+                        enable();
+                    }
+                }
+            }
+        }, intentFilter);
+
+        mRttServiceSynchronized.mHandler.post(() -> {
+            rttNative.start();
+        });
+    }
+
+    /*
+     * ASYNCHRONOUS DOMAIN - can be called from different threads!
+     */
+
+    /**
+     * Proxy for the final native call of the parent class. Enables mocking of
+     * the function.
+     */
+    public int getMockableCallingUid() {
+        return getCallingUid();
+    }
+
+    /**
+     * Enable the API: broadcast notification
+     */
+    public void enable() {
+        if (VDBG) Log.v(TAG, "enable");
+        sendRttStateChangedBroadcast(true);
+        mRttServiceSynchronized.mHandler.post(() -> {
+            // queue should be empty at this point (but this call allows validation)
+            mRttServiceSynchronized.executeNextRangingRequestIfPossible(false);
+        });
+    }
+
+    /**
+     * Disable the API:
+     * - Clean-up (fail) pending requests
+     * - Broadcast notification
+     */
+    public void disable() {
+        if (VDBG) Log.v(TAG, "disable");
+        sendRttStateChangedBroadcast(false);
+        mRttServiceSynchronized.mHandler.post(() -> {
+            mRttServiceSynchronized.cleanUpOnDisable();
+        });
+    }
+
+    /**
+     * Binder interface API to indicate whether the API is currently available. This requires an
+     * immediate asynchronous response.
+     */
+    @Override
+    public boolean isAvailable() {
+        return mRttNative.isReady() && !mPowerManager.isDeviceIdleMode();
+    }
+
+    /**
+     * Binder interface API to start a ranging operation. Called on binder thread, operations needs
+     * to be posted to handler thread.
+     */
+    @Override
+    public void startRanging(IBinder binder, String callingPackage, WorkSource workSource,
+            RangingRequest request, IRttCallback callback) throws RemoteException {
+        if (VDBG) {
+            Log.v(TAG, "startRanging: binder=" + binder + ", callingPackage=" + callingPackage
+                    + ", workSource=" + workSource + ", request=" + request + ", callback="
+                    + callback);
+        }
+        // verify arguments
+        if (binder == null) {
+            throw new IllegalArgumentException("Binder must not be null");
+        }
+        if (request == null || request.mRttPeers == null || request.mRttPeers.size() == 0) {
+            throw new IllegalArgumentException("Request must not be null or empty");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        request.enforceValidity(mAwareBinder != null);
+
+        if (!isAvailable()) {
+            try {
+                callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+            } catch (RemoteException e) {
+                Log.e(TAG, "startRanging: disabled, callback failed -- " + e);
+            }
+            return;
+        }
+
+        final int uid = getMockableCallingUid();
+
+        // permission checks
+        enforceAccessPermission();
+        enforceChangePermission();
+        enforceLocationPermission(callingPackage, uid);
+        if (workSource != null) {
+            enforceLocationHardware();
+        }
+
+        // register for binder death
+        IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
+            @Override
+            public void binderDied() {
+                if (VDBG) Log.v(TAG, "binderDied: uid=" + uid);
+                binder.unlinkToDeath(this, 0);
+
+                mRttServiceSynchronized.mHandler.post(() -> {
+                    mRttServiceSynchronized.cleanUpClientRequests(uid, null);
+                });
+            }
+        };
+
+        try {
+            binder.linkToDeath(dr, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error on linkToDeath - " + e);
+        }
+
+
+        mRttServiceSynchronized.mHandler.post(() -> {
+            WorkSource sourceToUse = workSource;
+            if (workSource == null || workSource.size() == 0 || workSource.get(0) == 0) {
+                sourceToUse = new WorkSource(uid);
+            }
+            mRttServiceSynchronized.queueRangingRequest(uid, sourceToUse, binder, dr,
+                    callingPackage, request, callback);
+        });
+    }
+
+    @Override
+    public void cancelRanging(WorkSource workSource) throws RemoteException {
+        if (VDBG) Log.v(TAG, "cancelRanging: workSource=" + workSource);
+        enforceLocationHardware();
+
+        if (workSource == null || workSource.size() == 0 || workSource.get(0) == 0) {
+            Log.e(TAG, "cancelRanging: invalid work-source -- " + workSource);
+            return;
+        }
+
+        mRttServiceSynchronized.mHandler.post(() -> {
+            mRttServiceSynchronized.cleanUpClientRequests(0, workSource);
+        });
+    }
+
+    /**
+     * Called by HAL to report ranging results. Called on HAL thread - needs to post to local
+     * thread.
+     */
+    public void onRangingResults(int cmdId, List<RttResult> results) {
+        if (VDBG) Log.v(TAG, "onRangingResults: cmdId=" + cmdId);
+        mRttServiceSynchronized.mHandler.post(() -> {
+            mRttServiceSynchronized.onRangingResults(cmdId, results);
+        });
+    }
+
+    private void enforceAccessPermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG);
+    }
+
+    private void enforceChangePermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
+    }
+
+    private void enforceLocationPermission(String callingPackage, int uid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
+                TAG);
+    }
+
+    private void enforceLocationHardware() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.LOCATION_HARDWARE,
+                TAG);
+    }
+
+    private void sendRttStateChangedBroadcast(boolean enabled) {
+        if (VDBG) Log.v(TAG, "sendRttStateChangedBroadcast: enabled=" + enabled);
+        final Intent intent = new Intent(WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump RttService from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+        pw.println("Wi-Fi RTT Service");
+        mRttServiceSynchronized.dump(fd, pw, args);
+    }
+
+    /*
+     * SYNCHRONIZED DOMAIN
+     */
+
+    /**
+     * RTT service implementation - synchronized on a single thread. All commands should be posted
+     * to the exposed handler.
+     */
+    private class RttServiceSynchronized {
+        public Handler mHandler;
+
+        private RttNative mRttNative;
+        private int mNextCommandId = 1000;
+        private Map<Integer, RttRequesterInfo> mRttRequesterInfo = new HashMap<>();
+        private List<RttRequestInfo> mRttRequestQueue = new LinkedList<>();
+        private WakeupMessage mRangingTimeoutMessage = null;
+
+        RttServiceSynchronized(Looper looper, RttNative rttNative) {
+            mRttNative = rttNative;
+
+            mHandler = new Handler(looper);
+            mRangingTimeoutMessage = new WakeupMessage(mContext, mHandler,
+                    HAL_RANGING_TIMEOUT_TAG, () -> {
+                timeoutRangingRequest();
+            });
+        }
+
+        private void cancelRanging(RttRequestInfo rri) {
+            ArrayList<byte[]> macAddresses = new ArrayList<>();
+            for (ResponderConfig peer : rri.request.mRttPeers) {
+                macAddresses.add(peer.macAddress.toByteArray());
+            }
+
+            mRttNative.rangeCancel(rri.cmdId, macAddresses);
+        }
+
+        private void cleanUpOnDisable() {
+            if (VDBG) Log.v(TAG, "RttServiceSynchronized.cleanUpOnDisable");
+            for (RttRequestInfo rri : mRttRequestQueue) {
+                try {
+                    if (rri.dispatchedToNative) {
+                        // may not be necessary in some cases (e.g. Wi-Fi disable may already clear
+                        // up active RTT), but in other cases will be needed (doze disabling RTT
+                        // but Wi-Fi still up). Doesn't hurt - worst case will fail.
+                        cancelRanging(rri);
+                    }
+                    rri.callback.onRangingFailure(
+                            RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RttServiceSynchronized.startRanging: disabled, callback failed -- "
+                            + e);
+                }
+                rri.binder.unlinkToDeath(rri.dr, 0);
+            }
+            mRttRequestQueue.clear();
+            mRangingTimeoutMessage.cancel();
+        }
+
+        /**
+         * Remove entries related to the specified client and cancel any dispatched to HAL
+         * requests. Expected to provide either the UID or the WorkSource (the other will be 0 or
+         * null respectively).
+         *
+         * A workSource specification will be cleared from the requested workSource and the request
+         * cancelled only if there are no remaining uids in the work-source.
+         */
+        private void cleanUpClientRequests(int uid, WorkSource workSource) {
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
+                        + ", workSource=" + workSource + ", mRttRequestQueue=" + mRttRequestQueue);
+            }
+            boolean dispatchedRequestAborted = false;
+            ListIterator<RttRequestInfo> it = mRttRequestQueue.listIterator();
+            while (it.hasNext()) {
+                RttRequestInfo rri = it.next();
+
+                boolean match = rri.uid == uid; // original UID will never be 0
+                if (rri.workSource != null && workSource != null) {
+                    try {
+                        rri.workSource.remove(workSource);
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG, "Invalid WorkSource specified in the start or cancel requests: "
+                                + e);
+                    }
+                    if (rri.workSource.size() == 0) {
+                        match = true;
+                    }
+                }
+
+                if (match) {
+                    if (!rri.dispatchedToNative) {
+                        it.remove();
+                        rri.binder.unlinkToDeath(rri.dr, 0);
+                    } else {
+                        dispatchedRequestAborted = true;
+                        Log.d(TAG, "Client death - cancelling RTT operation in progress: cmdId="
+                                + rri.cmdId);
+                        mRangingTimeoutMessage.cancel();
+                        cancelRanging(rri);
+                    }
+                }
+            }
+
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
+                        + ", dispatchedRequestAborted=" + dispatchedRequestAborted
+                        + ", after cleanup - mRttRequestQueue=" + mRttRequestQueue);
+            }
+
+            if (dispatchedRequestAborted) {
+                executeNextRangingRequestIfPossible(true);
+            }
+        }
+
+        private void timeoutRangingRequest() {
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.timeoutRangingRequest mRttRequestQueue="
+                        + mRttRequestQueue);
+            }
+            if (mRttRequestQueue.size() == 0) {
+                Log.w(TAG, "RttServiceSynchronized.timeoutRangingRequest: but nothing in queue!?");
+                return;
+            }
+            RttRequestInfo rri = mRttRequestQueue.get(0);
+            if (!rri.dispatchedToNative) {
+                Log.w(TAG, "RttServiceSynchronized.timeoutRangingRequest: command not dispatched "
+                        + "to native!?");
+                return;
+            }
+            cancelRanging(rri);
+            try {
+                rri.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RttServiceSynchronized.timeoutRangingRequest: callback failed: " + e);
+            }
+            executeNextRangingRequestIfPossible(true);
+        }
+
+        private void queueRangingRequest(int uid, WorkSource workSource, IBinder binder,
+                IBinder.DeathRecipient dr, String callingPackage, RangingRequest request,
+                IRttCallback callback) {
+            if (isRequestorSpamming(workSource)) {
+                Log.w(TAG,
+                        "Work source " + workSource + " is spamming, dropping request: " + request);
+                binder.unlinkToDeath(dr, 0);
+                try {
+                    callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RttServiceSynchronized.queueRangingRequest: spamming, callback "
+                            + "failed -- " + e);
+                }
+                return;
+            }
+
+            RttRequestInfo newRequest = new RttRequestInfo();
+            newRequest.uid = uid;
+            newRequest.workSource = workSource;
+            newRequest.binder = binder;
+            newRequest.dr = dr;
+            newRequest.callingPackage = callingPackage;
+            newRequest.request = request;
+            newRequest.callback = callback;
+            mRttRequestQueue.add(newRequest);
+
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.queueRangingRequest: newRequest=" + newRequest);
+            }
+
+            executeNextRangingRequestIfPossible(false);
+        }
+
+        private boolean isRequestorSpamming(WorkSource ws) {
+            if (VDBG) Log.v(TAG, "isRequestorSpamming: ws" + ws);
+
+            SparseIntArray counts = new SparseIntArray();
+
+            for (RttRequestInfo rri : mRttRequestQueue) {
+                for (int i = 0; i < rri.workSource.size(); ++i) {
+                    int uid = rri.workSource.get(i);
+                    counts.put(uid, counts.get(uid) + 1);
+                }
+            }
+
+            for (int i = 0; i < ws.size(); ++i) {
+                if (counts.get(ws.get(i)) < MAX_QUEUED_PER_UID) {
+                    return false;
+                }
+            }
+
+            if (VDBG) {
+                Log.v(TAG, "isRequestorSpamming: ws=" + ws + ", someone is spamming: " + counts);
+            }
+            return true;
+        }
+
+        private void executeNextRangingRequestIfPossible(boolean popFirst) {
+            if (VDBG) Log.v(TAG, "executeNextRangingRequestIfPossible: popFirst=" + popFirst);
+
+            if (popFirst) {
+                if (mRttRequestQueue.size() == 0) {
+                    Log.w(TAG, "executeNextRangingRequestIfPossible: pop requested - but empty "
+                            + "queue!? Ignoring pop.");
+                } else {
+                    RttRequestInfo topOfQueueRequest = mRttRequestQueue.remove(0);
+                    topOfQueueRequest.binder.unlinkToDeath(topOfQueueRequest.dr, 0);
+                }
+            }
+
+            if (mRttRequestQueue.size() == 0) {
+                if (VDBG) Log.v(TAG, "executeNextRangingRequestIfPossible: no requests pending");
+                return;
+            }
+
+            // if top of list is in progress then do nothing
+            RttRequestInfo nextRequest = mRttRequestQueue.get(0);
+            if (nextRequest.peerHandlesTranslated || nextRequest.dispatchedToNative) {
+                if (VDBG) {
+                    Log.v(TAG, "executeNextRangingRequestIfPossible: called but a command is "
+                            + "executing. topOfQueue=" + nextRequest);
+                }
+                return;
+            }
+
+            startRanging(nextRequest);
+        }
+
+        private void startRanging(RttRequestInfo nextRequest) {
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.startRanging: nextRequest=" + nextRequest);
+            }
+
+            if (!isAvailable()) {
+                Log.d(TAG, "RttServiceSynchronized.startRanging: disabled");
+                try {
+                    nextRequest.callback.onRangingFailure(
+                            RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RttServiceSynchronized.startRanging: disabled, callback failed -- "
+                            + e);
+                    executeNextRangingRequestIfPossible(true);
+                    return;
+                }
+            }
+
+            if (processAwarePeerHandles(nextRequest)) {
+                if (VDBG) {
+                    Log.v(TAG, "RttServiceSynchronized.startRanging: deferring due to PeerHandle "
+                            + "Aware requests");
+                }
+                return;
+            }
+
+            if (!preExecThrottleCheck(nextRequest.workSource)) {
+                Log.w(TAG, "RttServiceSynchronized.startRanging: execution throttled - nextRequest="
+                        + nextRequest + ", mRttRequesterInfo=" + mRttRequesterInfo);
+                try {
+                    nextRequest.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RttServiceSynchronized.startRanging: throttled, callback failed -- "
+                            + e);
+                }
+                executeNextRangingRequestIfPossible(true);
+                return;
+            }
+
+            nextRequest.cmdId = mNextCommandId++;
+            if (mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) {
+                mRangingTimeoutMessage.schedule(
+                        mClock.getElapsedSinceBootMillis() + HAL_RANGING_TIMEOUT_MS);
+            } else {
+                Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed");
+                try {
+                    nextRequest.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RttServiceSynchronized.startRanging: HAL request failed, callback "
+                            + "failed -- " + e);
+                }
+                executeNextRangingRequestIfPossible(true);
+            }
+            nextRequest.dispatchedToNative = true;
+        }
+
+        /**
+         * Perform pre-execution throttling checks:
+         * - If all uids in ws are in background then check last execution and block if request is
+         * more frequent than permitted
+         * - If executing (i.e. permitted) then update execution time
+         *
+         * Returns true to permit execution, false to abort it.
+         */
+        private boolean preExecThrottleCheck(WorkSource ws) {
+            if (VDBG) Log.v(TAG, "preExecThrottleCheck: ws=" + ws);
+
+            // are all UIDs running in the background or is at least 1 in the foreground?
+            boolean allUidsInBackground = true;
+            for (int i = 0; i < ws.size(); ++i) {
+                int uidImportance = mActivityManager.getUidImportance(ws.get(i));
+                if (VDBG) {
+                    Log.v(TAG, "preExecThrottleCheck: uid=" + ws.get(i) + " -> importance="
+                            + uidImportance);
+                }
+                if (uidImportance
+                        <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) {
+                    allUidsInBackground = false;
+                    break;
+                }
+            }
+
+            // if all UIDs are in background then check timestamp since last execution and see if
+            // any is permitted (infrequent enough)
+            boolean allowExecution = false;
+            long mostRecentExecutionPermitted =
+                    mClock.getElapsedSinceBootMillis() - BACKGROUND_PROCESS_EXEC_GAP_MS;
+            if (allUidsInBackground) {
+                for (int i = 0; i < ws.size(); ++i) {
+                    RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i));
+                    if (info == null || info.lastRangingExecuted < mostRecentExecutionPermitted) {
+                        allowExecution = true;
+                        break;
+                    }
+                }
+            } else {
+                allowExecution = true;
+            }
+
+            // update exec time
+            if (allowExecution) {
+                for (int i = 0; i < ws.size(); ++i) {
+                    RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i));
+                    if (info == null) {
+                        info = new RttRequesterInfo();
+                        mRttRequesterInfo.put(ws.get(i), info);
+                    }
+                    info.lastRangingExecuted = mClock.getElapsedSinceBootMillis();
+                }
+            }
+
+            return allowExecution;
+        }
+
+        /**
+         * Check request for any PeerHandle Aware requests. If there are any: issue requests to
+         * translate the peer ID to a MAC address and abort current execution of the range request.
+         * The request will be re-attempted when response is received.
+         *
+         * In cases of failure: pop the current request and execute the next one. Failures:
+         * - Not able to connect to remote service (unlikely)
+         * - Request already processed: but we're missing information
+         *
+         * @return true if need to abort execution, false otherwise.
+         */
+        private boolean processAwarePeerHandles(RttRequestInfo request) {
+            List<Integer> peerIdsNeedingTranslation = new ArrayList<>();
+            for (ResponderConfig rttPeer : request.request.mRttPeers) {
+                if (rttPeer.peerHandle != null && rttPeer.macAddress == null) {
+                    peerIdsNeedingTranslation.add(rttPeer.peerHandle.peerId);
+                }
+            }
+
+            if (peerIdsNeedingTranslation.size() == 0) {
+                return false;
+            }
+
+            if (request.peerHandlesTranslated) {
+                Log.w(TAG, "processAwarePeerHandles: request=" + request
+                        + ": PeerHandles translated - but information still missing!?");
+                try {
+                    request.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "processAwarePeerHandles: onRangingResults failure -- " + e);
+                }
+                executeNextRangingRequestIfPossible(true);
+                return true; // an abort because we removed request and are executing next one
+            }
+
+            request.peerHandlesTranslated = true;
+            try {
+                mAwareBinder.requestMacAddresses(request.uid, peerIdsNeedingTranslation,
+                        new IWifiAwareMacAddressProvider.Stub() {
+                            @Override
+                            public void macAddress(Map peerIdToMacMap) {
+                                // ASYNC DOMAIN
+                                mHandler.post(() -> {
+                                    // BACK TO SYNC DOMAIN
+                                    processReceivedAwarePeerMacAddresses(request, peerIdToMacMap);
+                                });
+                            }
+                        });
+            } catch (RemoteException e1) {
+                Log.e(TAG,
+                        "processAwarePeerHandles: exception while calling requestMacAddresses -- "
+                                + e1 + ", aborting request=" + request);
+                try {
+                    request.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+                } catch (RemoteException e2) {
+                    Log.e(TAG, "processAwarePeerHandles: onRangingResults failure -- " + e2);
+                }
+                executeNextRangingRequestIfPossible(true);
+                return true; // an abort because we removed request and are executing next one
+            }
+
+            return true; // a deferral
+        }
+
+        private void processReceivedAwarePeerMacAddresses(RttRequestInfo request,
+                Map<Integer, byte[]> peerIdToMacMap) {
+            if (VDBG) {
+                Log.v(TAG, "processReceivedAwarePeerMacAddresses: request=" + request
+                        + ", peerIdToMacMap=" + peerIdToMacMap);
+            }
+
+            for (ResponderConfig rttPeer : request.request.mRttPeers) {
+                if (rttPeer.peerHandle != null && rttPeer.macAddress == null) {
+                    rttPeer.macAddress = MacAddress.fromBytes(
+                            peerIdToMacMap.get(rttPeer.peerHandle.peerId));
+                }
+            }
+
+            // run request again
+            startRanging(request);
+        }
+
+        private void onRangingResults(int cmdId, List<RttResult> results) {
+            if (mRttRequestQueue.size() == 0) {
+                Log.e(TAG, "RttServiceSynchronized.onRangingResults: no current RTT request "
+                        + "pending!?");
+                return;
+            }
+            mRangingTimeoutMessage.cancel();
+            RttRequestInfo topOfQueueRequest = mRttRequestQueue.get(0);
+
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.onRangingResults: cmdId=" + cmdId
+                        + ", topOfQueueRequest=" + topOfQueueRequest + ", results="
+                        + Arrays.toString(results.toArray()));
+            }
+
+            if (topOfQueueRequest.cmdId != cmdId) {
+                Log.e(TAG, "RttServiceSynchronized.onRangingResults: cmdId=" + cmdId
+                        + ", does not match pending RTT request cmdId=" + topOfQueueRequest.cmdId);
+                return;
+            }
+
+            boolean permissionGranted = mWifiPermissionsUtil.checkCallersLocationPermission(
+                    topOfQueueRequest.callingPackage, topOfQueueRequest.uid);
+            try {
+                if (permissionGranted) {
+                    List<RangingResult> finalResults = postProcessResults(topOfQueueRequest.request,
+                            results);
+                    if (VDBG) {
+                        Log.v(TAG, "RttServiceSynchronized.onRangingResults: finalResults="
+                                + finalResults);
+                    }
+                    topOfQueueRequest.callback.onRangingResults(finalResults);
+                } else {
+                    Log.w(TAG, "RttServiceSynchronized.onRangingResults: location permission "
+                            + "revoked - not forwarding results");
+                    topOfQueueRequest.callback.onRangingFailure(
+                            RangingResultCallback.STATUS_CODE_FAIL);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG,
+                        "RttServiceSynchronized.onRangingResults: callback exception -- " + e);
+            }
+
+            executeNextRangingRequestIfPossible(true);
+        }
+
+        /*
+         * Post process the results:
+         * - For requests without results: add FAILED results
+         * - For Aware requests using PeerHandle: replace MAC address with PeerHandle
+         * - Effectively: throws away results which don't match requests
+         */
+        private List<RangingResult> postProcessResults(RangingRequest request,
+                List<RttResult> results) {
+            Map<MacAddress, RttResult> resultEntries = new HashMap<>();
+            for (RttResult result : results) {
+                resultEntries.put(MacAddress.fromBytes(result.addr), result);
+            }
+
+            List<RangingResult> finalResults = new ArrayList<>(request.mRttPeers.size());
+
+            for (ResponderConfig peer : request.mRttPeers) {
+                RttResult resultForRequest = resultEntries.get(peer.macAddress);
+                if (resultForRequest == null) {
+                    if (VDBG) {
+                        Log.v(TAG, "postProcessResults: missing=" + peer.macAddress);
+                    }
+                    if (peer.peerHandle == null) {
+                        finalResults.add(
+                                new RangingResult(RangingResult.STATUS_FAIL, peer.macAddress, 0, 0,
+                                        0, 0));
+                    } else {
+                        finalResults.add(
+                                new RangingResult(RangingResult.STATUS_FAIL, peer.peerHandle, 0, 0,
+                                        0, 0));
+                    }
+                } else {
+                    int status = resultForRequest.status == RttStatus.SUCCESS
+                            ? RangingResult.STATUS_SUCCESS : RangingResult.STATUS_FAIL;
+                    if (peer.peerHandle == null) {
+                        finalResults.add(
+                                new RangingResult(status, peer.macAddress,
+                                        resultForRequest.distanceInMm,
+                                        resultForRequest.distanceSdInMm,
+                                        resultForRequest.rssi, resultForRequest.timeStampInUs));
+                    } else {
+                        finalResults.add(
+                                new RangingResult(status, peer.peerHandle,
+                                        resultForRequest.distanceInMm,
+                                        resultForRequest.distanceSdInMm,
+                                        resultForRequest.rssi, resultForRequest.timeStampInUs));
+                    }
+                }
+            }
+
+            return finalResults;
+        }
+
+        // dump call (asynchronous most likely)
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("  mNextCommandId: " + mNextCommandId);
+            pw.println("  mRttRequesterInfo: " + mRttRequesterInfo);
+            pw.println("  mRttRequestQueue: " + mRttRequestQueue);
+            pw.println("  mRangingTimeoutMessage: " + mRangingTimeoutMessage);
+            mRttNative.dump(fd, pw, args);
+        }
+    }
+
+    private static class RttRequestInfo {
+        public int uid;
+        public WorkSource workSource;
+        public IBinder binder;
+        public IBinder.DeathRecipient dr;
+        public String callingPackage;
+        public RangingRequest request;
+        public IRttCallback callback;
+
+        public int cmdId = 0; // uninitialized cmdId value
+        public boolean dispatchedToNative = false;
+        public boolean peerHandlesTranslated = false;
+
+        @Override
+        public String toString() {
+            return new StringBuilder("RttRequestInfo: uid=").append(uid).append(
+                    ", workSource=").append(workSource).append(", binder=").append(binder).append(
+                    ", dr=").append(dr).append(", callingPackage=").append(callingPackage).append(
+                    ", request=").append(request.toString()).append(", callback=").append(
+                    callback).append(", cmdId=").append(cmdId).append(
+                    ", peerHandlesTranslated=").append(peerHandlesTranslated).toString();
+        }
+    }
+
+    private static class RttRequesterInfo {
+        public long lastRangingExecuted;
+
+        @Override
+        public String toString() {
+            return new StringBuilder("RttRequesterInfo: lastRangingExecuted=").append(
+                    lastRangingExecuted).toString();
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/scanner/ChannelHelper.java b/service/java/com/android/server/wifi/scanner/ChannelHelper.java
index d87df07..6a01f0c 100644
--- a/service/java/com/android/server/wifi/scanner/ChannelHelper.java
+++ b/service/java/com/android/server/wifi/scanner/ChannelHelper.java
@@ -242,7 +242,7 @@
         if (scanSettings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
             return toString(scanSettings.channels);
         } else {
-            return toString(scanSettings.band);
+            return bandToString(scanSettings.band);
         }
     }
 
@@ -255,7 +255,7 @@
         if (bucketSettings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
             return toString(bucketSettings.channels, bucketSettings.num_channels);
         } else {
-            return toString(bucketSettings.band);
+            return bandToString(bucketSettings.band);
         }
     }
 
@@ -293,7 +293,10 @@
         return sb.toString();
     }
 
-    private static String toString(int band) {
+    /**
+     * Converts a WifiScanner.WIFI_BAND_* constant to a meaningful String
+     */
+    public static String bandToString(int band) {
         switch (band) {
             case WifiScanner.WIFI_BAND_UNSPECIFIED:
                 return "unspecified";
@@ -310,7 +313,6 @@
             case WifiScanner.WIFI_BAND_BOTH_WITH_DFS:
                 return "24Ghz & 5Ghz (DFS incl)";
         }
-
         return "invalid band";
     }
 }
diff --git a/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
index 7d0ccba..1478a99 100644
--- a/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/HalWifiScannerImpl.java
@@ -46,7 +46,7 @@
     public HalWifiScannerImpl(Context context, WifiNative wifiNative, WifiMonitor wifiMonitor,
                               Looper looper, Clock clock) {
         mWifiNative = wifiNative;
-        mChannelHelper = new HalChannelHelper(wifiNative);
+        mChannelHelper = new WificondChannelHelper(wifiNative);
         mWificondScannerDelegate =
                 new WificondScannerImpl(context, wifiNative, wifiMonitor, mChannelHelper,
                         looper, clock);
@@ -131,11 +131,6 @@
     }
 
     @Override
-    public boolean shouldScheduleBackgroundScanForHwPno() {
-        return mWificondScannerDelegate.shouldScheduleBackgroundScanForHwPno();
-    }
-
-    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mWificondScannerDelegate.dump(fd, pw, args);
     }
diff --git a/service/java/com/android/server/wifi/scanner/NoBandChannelHelper.java b/service/java/com/android/server/wifi/scanner/NoBandChannelHelper.java
deleted file mode 100644
index b2eeada..0000000
--- a/service/java/com/android/server/wifi/scanner/NoBandChannelHelper.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2016 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.scanner;
-
-import android.net.wifi.WifiScanner;
-import android.util.ArraySet;
-
-import com.android.server.wifi.WifiNative;
-
-import java.util.Set;
-
-/**
- * ChannelHelper that offers channel manipulation utilities when the channels in a band are not
- * known. Operations performed may simplify any band to include all channels.
- */
-public class NoBandChannelHelper extends ChannelHelper {
-
-    /**
-     * These parameters are used to estimate the scan duration.
-     * This is a guess at the number of channels the device supports for use when a ScanSettings
-     * specifies a band instead of a list of channels.
-     */
-    private static final int ALL_BAND_CHANNEL_COUNT_ESTIMATE = 36;
-
-    @Override
-    public boolean settingsContainChannel(WifiScanner.ScanSettings settings, int channel) {
-        if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
-            for (int i = 0; i < settings.channels.length; ++i) {
-                if (settings.channels[i].frequency == channel) {
-                    return true;
-                }
-            }
-            return false;
-        } else {
-            return true;
-        }
-    }
-
-    @Override
-    public WifiScanner.ChannelSpec[] getAvailableScanChannels(int band) {
-        return NO_CHANNELS; // not supported
-    }
-
-    @Override
-    public int estimateScanDuration(WifiScanner.ScanSettings settings) {
-        if (settings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
-            return settings.channels.length * SCAN_PERIOD_PER_CHANNEL_MS;
-        } else {
-            return ALL_BAND_CHANNEL_COUNT_ESTIMATE * SCAN_PERIOD_PER_CHANNEL_MS;
-        }
-    }
-
-    /**
-     * ChannelCollection that merges channels without knowing which channels are in each band. In
-     * order to do this if any band is added or the maxChannels is exceeded then all channels will
-     * be included.
-     */
-    public class NoBandChannelCollection extends ChannelCollection {
-        private final ArraySet<Integer> mChannels = new ArraySet<Integer>();
-        private boolean mAllChannels = false;
-
-        @Override
-        public void addChannel(int frequency) {
-            mChannels.add(frequency);
-        }
-
-        @Override
-        public void addBand(int band) {
-            if (band != WifiScanner.WIFI_BAND_UNSPECIFIED) {
-                mAllChannels = true;
-            }
-        }
-
-        @Override
-        public boolean containsChannel(int channel) {
-            return mAllChannels || mChannels.contains(channel);
-        }
-
-        @Override
-        public boolean containsBand(int band) {
-            if (band != WifiScanner.WIFI_BAND_UNSPECIFIED) {
-                return mAllChannels;
-            }
-            return false;
-        }
-
-        @Override
-        public boolean partiallyContainsBand(int band) {
-            // We don't need to partially collapse settings in wificond scanner because we
-            // don't have any limitation on the number of channels that can be scanned. We also
-            // don't currently keep track of bands very well in NoBandChannelHelper.
-            return false;
-        }
-
-        @Override
-        public boolean isEmpty() {
-            return !mAllChannels && mChannels.isEmpty();
-        }
-
-        @Override
-        public boolean isAllChannels() {
-            return mAllChannels;
-        }
-
-        @Override
-        public void clear() {
-            mAllChannels = false;
-            mChannels.clear();
-        }
-
-        @Override
-        public Set<Integer> getMissingChannelsFromBand(int band) {
-            // We don't need to partially collapse settings in wificond scanner because we
-            // don't have any limitation on the number of channels that can be scanned. We also
-            // don't currently keep track of bands very well in NoBandChannelHelper.
-            return new ArraySet<Integer>();
-        }
-
-        @Override
-        public Set<Integer> getContainingChannelsFromBand(int band) {
-            // We don't need to partially collapse settings in wificond scanner because we
-            // don't have any limitation on the number of channels that can be scanned. We also
-            // don't currently keep track of bands very well in NoBandChannelHelper.
-            return new ArraySet<Integer>();
-        }
-
-        @Override
-        public Set<Integer> getChannelSet() {
-            if (!isEmpty() && !mAllChannels) {
-                return mChannels;
-            } else {
-                return new ArraySet<>();
-            }
-        }
-
-        @Override
-        public void fillBucketSettings(WifiNative.BucketSettings bucketSettings, int maxChannels) {
-            if (mAllChannels || mChannels.size() > maxChannels) {
-                bucketSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
-                bucketSettings.num_channels = 0;
-                bucketSettings.channels = null;
-            } else {
-                bucketSettings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
-                bucketSettings.num_channels = mChannels.size();
-                bucketSettings.channels = new WifiNative.ChannelSettings[mChannels.size()];
-                for (int i = 0; i < mChannels.size(); ++i) {
-                    WifiNative.ChannelSettings channelSettings = new WifiNative.ChannelSettings();
-                    channelSettings.frequency = mChannels.valueAt(i);
-                    bucketSettings.channels[i] = channelSettings;
-                }
-            }
-        }
-
-        @Override
-        public Set<Integer> getScanFreqs() {
-            if (mAllChannels) {
-                return null;
-            } else {
-                return new ArraySet<Integer>(mChannels);
-            }
-        }
-    }
-
-    @Override
-    public ChannelCollection createChannelCollection() {
-        return new NoBandChannelCollection();
-    }
-}
diff --git a/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java
index 5281b3a..75b24d8 100644
--- a/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WifiScannerImpl.java
@@ -53,8 +53,8 @@
                 if (wifiNative.getBgScanCapabilities(new WifiNative.ScanCapabilities())) {
                     return new HalWifiScannerImpl(context, wifiNative, wifiMonitor, looper, clock);
                 } else {
-                    return new WificondScannerImpl(context, wifiNative, wifiMonitor, looper,
-                            clock);
+                    return new WificondScannerImpl(context, wifiNative, wifiMonitor,
+                            new WificondChannelHelper(wifiNative), looper, clock);
                 }
             }
         };
@@ -155,11 +155,5 @@
      */
     public abstract boolean isHwPnoSupported(boolean isConnectedPno);
 
-    /**
-     * This returns whether a background scan should be running for HW PNO scan or not.
-     * @return true if background scan needs to be started, false otherwise.
-     */
-    public abstract boolean shouldScheduleBackgroundScanForHwPno();
-
     protected abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 }
diff --git a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index 4b8e284..ed1bedf 100644
--- a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -75,7 +75,6 @@
     private static final String TAG = WifiScanningService.TAG;
     private static final boolean DBG = false;
 
-    private static final int MIN_PERIOD_PER_CHANNEL_MS = 200;               // DFS needs 120 ms
     private static final int UNKNOWN_PID = -1;
 
     private final LocalLog mLocalLog = new LocalLog(512);
@@ -240,10 +239,6 @@
 
     private static final int CMD_SCAN_RESULTS_AVAILABLE              = BASE + 0;
     private static final int CMD_FULL_SCAN_RESULTS                   = BASE + 1;
-    private static final int CMD_HOTLIST_AP_FOUND                    = BASE + 2;
-    private static final int CMD_HOTLIST_AP_LOST                     = BASE + 3;
-    private static final int CMD_WIFI_CHANGE_DETECTED                = BASE + 4;
-    private static final int CMD_WIFI_CHANGE_TIMEOUT                 = BASE + 5;
     private static final int CMD_DRIVER_LOADED                       = BASE + 6;
     private static final int CMD_DRIVER_UNLOADED                     = BASE + 7;
     private static final int CMD_SCAN_PAUSED                         = BASE + 8;
@@ -1341,7 +1336,6 @@
      *   -Started State
      *     -Hw Pno Scan state
      *       -Single Scan state
-     *     -Sw Pno Scan state
      *
      * These are the main state transitions:
      * 1. Start at |Default State|
@@ -1353,11 +1347,6 @@
      *        contains IE (information elements). If yes, send the results to the client, else
      *        switch to |Single Scan state| and send the result to the client when the scan result
      *        is obtained.
-     *   b.1. Switch to |Sw Pno Scan state| when the device does not supports HW PNO
-     *        (This is for older devices which do not support HW PNO and for connected PNO on
-     *         devices which support wificond based PNO)
-     *   b.2. In |Sw Pno Scan state| send the result to the client when the background scan result
-     *        is obtained
      *
      * Note: PNO scans only work for a single client today. We don't have support in HW to support
      * multiple requests at the same time, so will need non-trivial changes to support (if at all
@@ -1368,7 +1357,6 @@
         private final DefaultState mDefaultState = new DefaultState();
         private final StartedState mStartedState = new StartedState();
         private final HwPnoScanState mHwPnoScanState = new HwPnoScanState();
-        private final SwPnoScanState mSwPnoScanState = new SwPnoScanState();
         private final SingleScanState mSingleScanState = new SingleScanState();
         private InternalClientInfo mInternalClientInfo;
 
@@ -1386,7 +1374,6 @@
                 addState(mStartedState, mDefaultState);
                     addState(mHwPnoScanState, mStartedState);
                         addState(mSingleScanState, mHwPnoScanState);
-                    addState(mSwPnoScanState, mStartedState);
             // CHECKSTYLE:ON IndentationCheck
 
             setInitialState(mDefaultState);
@@ -1466,12 +1453,11 @@
                         pnoParams.setDefusable(true);
                         PnoSettings pnoSettings =
                                 pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY);
-                        // This message is handled after the transition to SwPnoScan/HwPnoScan state
-                        deferMessage(msg);
                         if (mScannerImpl.isHwPnoSupported(pnoSettings.isConnected)) {
+                            deferMessage(msg);
                             transitionTo(mHwPnoScanState);
                         } else {
-                            transitionTo(mSwPnoScanState);
+                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "not supported");
                         }
                         break;
                     case WifiScanner.CMD_STOP_PNO_SCAN:
@@ -1582,68 +1568,6 @@
             }
         }
 
-        class SwPnoScanState extends State {
-            private final ArrayList<ScanResult> mSwPnoFullScanResults = new ArrayList<>();
-
-            @Override
-            public void enter() {
-                if (DBG) localLog("SwPnoScanState");
-                mSwPnoFullScanResults.clear();
-            }
-
-            @Override
-            public void exit() {
-                removeInternalClient();
-            }
-
-            @Override
-            public boolean processMessage(Message msg) {
-                ClientInfo ci = mClients.get(msg.replyTo);
-                switch (msg.what) {
-                    case WifiScanner.CMD_START_PNO_SCAN:
-                        Bundle pnoParams = (Bundle) msg.obj;
-                        if (pnoParams == null) {
-                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null");
-                            return HANDLED;
-                        }
-                        pnoParams.setDefusable(true);
-                        PnoSettings pnoSettings =
-                                pnoParams.getParcelable(WifiScanner.PNO_PARAMS_PNO_SETTINGS_KEY);
-                        ScanSettings scanSettings =
-                                pnoParams.getParcelable(WifiScanner.PNO_PARAMS_SCAN_SETTINGS_KEY);
-                        if (addSwPnoScanRequest(ci, msg.arg2, scanSettings, pnoSettings)) {
-                            replySucceeded(msg);
-                        } else {
-                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
-                            transitionTo(mStartedState);
-                        }
-                        break;
-                    case WifiScanner.CMD_STOP_PNO_SCAN:
-                        removeSwPnoScanRequest(ci, msg.arg2);
-                        transitionTo(mStartedState);
-                        break;
-                    case WifiScanner.CMD_FULL_SCAN_RESULT:
-                        // Aggregate full scan results until we get the |CMD_SCAN_RESULT| message
-                        mSwPnoFullScanResults.add((ScanResult) msg.obj);
-                        break;
-                    case WifiScanner.CMD_SCAN_RESULT:
-                        ScanResult[] scanResults = mSwPnoFullScanResults.toArray(
-                                new ScanResult[mSwPnoFullScanResults.size()]);
-                        reportPnoNetworkFound(scanResults);
-                        mSwPnoFullScanResults.clear();
-                        break;
-                    case WifiScanner.CMD_OP_FAILED:
-                        sendPnoScanFailedToAllAndClear(
-                                WifiScanner.REASON_UNSPECIFIED, "background scan failed");
-                        transitionTo(mStartedState);
-                        break;
-                    default:
-                        return NOT_HANDLED;
-                }
-                return HANDLED;
-            }
-        }
-
         private WifiNative.PnoSettings convertSettingsToPnoNative(ScanSettings scanSettings,
                                                                   PnoSettings pnoSettings) {
             WifiNative.PnoSettings nativePnoSetting = new WifiNative.PnoSettings();
@@ -1724,10 +1648,7 @@
             }
             logScanRequest("addHwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings);
             addPnoScanRequest(ci, handler, scanSettings, pnoSettings);
-            // HW PNO is supported, check if we need a background scan running for this.
-            if (mScannerImpl.shouldScheduleBackgroundScanForHwPno()) {
-                addBackgroundScanRequest(scanSettings);
-            }
+
             return true;
         }
 
@@ -1739,34 +1660,6 @@
             }
         }
 
-        private boolean addSwPnoScanRequest(ClientInfo ci, int handler, ScanSettings scanSettings,
-                PnoSettings pnoSettings) {
-            if (ci == null) {
-                Log.d(TAG, "Failing scan request ClientInfo not found " + handler);
-                return false;
-            }
-            if (!mActivePnoScans.isEmpty()) {
-                loge("Failing scan request because there is already an active scan");
-                return false;
-            }
-            logScanRequest("addSwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings);
-            addPnoScanRequest(ci, handler, scanSettings, pnoSettings);
-            // HW PNO is not supported, we need to revert to normal background scans and
-            // report events after each scan and we need full scan results to get the IE information
-            scanSettings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
-                    | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
-            addBackgroundScanRequest(scanSettings);
-            return true;
-        }
-
-        private void removeSwPnoScanRequest(ClientInfo ci, int handler) {
-            if (ci != null) {
-                Pair<PnoSettings, ScanSettings> settings = removePnoScanRequest(ci, handler);
-                logScanRequest("removeSwPnoScanRequest", ci, handler, null,
-                        settings.second, settings.first);
-            }
-        }
-
         private void reportPnoNetworkFound(ScanResult[] results) {
             WifiScanner.ParcelableScanResults parcelableScanResults =
                     new WifiScanner.ParcelableScanResults(results);
@@ -1789,15 +1682,6 @@
             mActivePnoScans.clear();
         }
 
-        private void addBackgroundScanRequest(ScanSettings settings) {
-            if (DBG) localLog("Starting background scan");
-            if (mInternalClientInfo != null) {
-                mInternalClientInfo.sendRequestToClientHandler(
-                        WifiScanner.CMD_START_BACKGROUND_SCAN, settings,
-                        WifiStateMachine.WIFI_WORK_SOURCE);
-            }
-        }
-
         private void addSingleScanRequest(ScanSettings settings) {
             if (DBG) localLog("Starting single scan");
             if (mInternalClientInfo != null) {
@@ -2213,7 +2097,7 @@
 
     static String describeTo(StringBuilder sb, ScanSettings scanSettings) {
         sb.append("ScanSettings { ")
-          .append(" band:").append(scanSettings.band)
+          .append(" band:").append(ChannelHelper.bandToString(scanSettings.band))
           .append(" period:").append(scanSettings.periodInMs)
           .append(" reportEvents:").append(scanSettings.reportEvents)
           .append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan)
diff --git a/service/java/com/android/server/wifi/scanner/HalChannelHelper.java b/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java
similarity index 79%
rename from service/java/com/android/server/wifi/scanner/HalChannelHelper.java
rename to service/java/com/android/server/wifi/scanner/WificondChannelHelper.java
index e8b646d..7cfeb72 100644
--- a/service/java/com/android/server/wifi/scanner/HalChannelHelper.java
+++ b/service/java/com/android/server/wifi/scanner/WificondChannelHelper.java
@@ -22,15 +22,15 @@
 import com.android.server.wifi.WifiNative;
 
 /**
- * KnownBandsChannelHelper that uses band to channel mappings retrieved from the HAL.
- * Also supporting updating the channel list from the HAL on demand.
+ * KnownBandsChannelHelper that uses band to channel mappings retrieved from wificond.
+ * Also supporting updating the channel list from the wificond on demand.
  */
-public class HalChannelHelper extends KnownBandsChannelHelper {
-    private static final String TAG = "HalChannelHelper";
+public class WificondChannelHelper extends KnownBandsChannelHelper {
+    private static final String TAG = "WificondChannelHelper";
 
     private final WifiNative mWifiNative;
 
-    public HalChannelHelper(WifiNative wifiNative) {
+    public WificondChannelHelper(WifiNative wifiNative) {
         mWifiNative = wifiNative;
         final int[] emptyFreqList = new int[0];
         setBandChannels(emptyFreqList, emptyFreqList, emptyFreqList);
@@ -39,11 +39,13 @@
 
     @Override
     public void updateChannels() {
-        int[] channels24G = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
+        int[] channels24G =
+                mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ);
         if (channels24G == null) Log.e(TAG, "Failed to get channels for 2.4GHz band");
         int[] channels5G = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ);
         if (channels5G == null) Log.e(TAG, "Failed to get channels for 5GHz band");
-        int[] channelsDfs = mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
+        int[] channelsDfs =
+                mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY);
         if (channelsDfs == null) Log.e(TAG, "Failed to get channels for 5GHz DFS only band");
         if (channels24G == null || channels5G == null || channelsDfs == null) {
             Log.e(TAG, "Failed to get all channels for band, not updating band channel lists");
diff --git a/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java b/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
index ed25e0f..9afe061 100644
--- a/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -34,9 +34,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -50,7 +48,6 @@
     private static final String TAG = "WificondScannerImpl";
     private static final boolean DBG = false;
 
-    public static final String BACKGROUND_PERIOD_ALARM_TAG = TAG + " Background Scan Period";
     public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout";
     // Max number of networks that can be specified to wificond per scan request
     public static final int MAX_HIDDEN_NETWORK_IDS_PER_SCAN = 16;
@@ -68,39 +65,16 @@
 
     private final Object mSettingsLock = new Object();
 
-    // Next scan settings to apply when the previous scan completes
-    private WifiNative.ScanSettings mPendingBackgroundScanSettings = null;
-    private WifiNative.ScanEventHandler mPendingBackgroundScanEventHandler = null;
-    private WifiNative.ScanSettings mPendingSingleScanSettings = null;
-    private WifiNative.ScanEventHandler mPendingSingleScanEventHandler = null;
-
-    // Active background scan settings/state
-    private WifiNative.ScanSettings mBackgroundScanSettings = null;
-    private WifiNative.ScanEventHandler mBackgroundScanEventHandler = null;
-    private int mNextBackgroundScanPeriod = 0;
-    private int mNextBackgroundScanId = 0;
-    private boolean mBackgroundScanPeriodPending = false;
-    private boolean mBackgroundScanPaused = false;
-    private ScanBuffer mBackgroundScanBuffer = new ScanBuffer(SCAN_BUFFER_CAPACITY);
-
     private ArrayList<ScanDetail> mNativeScanResults;
     private WifiScanner.ScanData mLatestSingleScanResult =
             new WifiScanner.ScanData(0, 0, new ScanResult[0]);
 
-    // Settings for the currently running scan, null if no scan active
+    // Settings for the currently running single scan, null if no scan active
     private LastScanSettings mLastScanSettings = null;
+    // Settings for the currently running pno scan, null if no scan active
+    private LastPnoScanSettings mLastPnoScanSettings = null;
 
-    // Pno related info.
-    private WifiNative.PnoSettings mPnoSettings = null;
-    private WifiNative.PnoEventHandler mPnoEventHandler;
     private final boolean mHwPnoScanSupported;
-    private final HwPnoDebouncer mHwPnoDebouncer;
-    private final HwPnoDebouncer.Listener mHwPnoDebouncerListener = new HwPnoDebouncer.Listener() {
-        public void onPnoScanFailed() {
-            Log.e(TAG, "Pno scan failure received");
-            reportPnoScanFailure();
-        }
-    };
 
     /**
      * Duration to wait before timing out a scan.
@@ -110,14 +84,6 @@
      */
     private static final long SCAN_TIMEOUT_MS = 15000;
 
-    AlarmManager.OnAlarmListener mScanPeriodListener = new AlarmManager.OnAlarmListener() {
-            public void onAlarm() {
-                synchronized (mSettingsLock) {
-                    handleScanPeriod();
-                }
-            }
-        };
-
     AlarmManager.OnAlarmListener mScanTimeoutListener = new AlarmManager.OnAlarmListener() {
             public void onAlarm() {
                 synchronized (mSettingsLock) {
@@ -135,7 +101,6 @@
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
         mEventHandler = new Handler(looper, this);
         mClock = clock;
-        mHwPnoDebouncer = new HwPnoDebouncer(mWifiNative, mAlarmManager, mEventHandler, mClock);
 
         // Check if the device supports HW PNO scans.
         mHwPnoScanSupported = mContext.getResources().getBoolean(
@@ -149,20 +114,12 @@
                 WifiMonitor.SCAN_RESULTS_EVENT, mEventHandler);
     }
 
-    public WificondScannerImpl(Context context, WifiNative wifiNative,
-                                     WifiMonitor wifiMonitor, Looper looper, Clock clock) {
-        // TODO get channel information from wificond.
-        this(context, wifiNative, wifiMonitor, new NoBandChannelHelper(), looper, clock);
-    }
-
     @Override
     public void cleanup() {
         synchronized (mSettingsLock) {
-            mPendingSingleScanSettings = null;
-            mPendingSingleScanEventHandler = null;
             stopHwPnoScan();
-            stopBatchedScan();
             mLastScanSettings = null; // finally clear any active scan
+            mLastPnoScanSettings = null; // finally clear any active scan
         }
     }
 
@@ -189,15 +146,66 @@
                     + ",eventHandler=" + eventHandler);
             return false;
         }
-        if (mPendingSingleScanSettings != null
-                || (mLastScanSettings != null && mLastScanSettings.singleScanActive)) {
-            Log.w(TAG, "A single scan is already running");
-            return false;
-        }
         synchronized (mSettingsLock) {
-            mPendingSingleScanSettings = settings;
-            mPendingSingleScanEventHandler = eventHandler;
-            processPendingScans();
+            if (mLastScanSettings != null) {
+                Log.w(TAG, "A single scan is already running");
+                return false;
+            }
+
+            ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
+            boolean reportFullResults = false;
+
+            for (int i = 0; i < settings.num_buckets; ++i) {
+                WifiNative.BucketSettings bucketSettings = settings.buckets[i];
+                if ((bucketSettings.report_events
+                                & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
+                    reportFullResults = true;
+                }
+                allFreqs.addChannels(bucketSettings);
+            }
+
+            Set<String> hiddenNetworkSSIDSet = new HashSet<>();
+            if (settings.hiddenNetworks != null) {
+                int numHiddenNetworks =
+                        Math.min(settings.hiddenNetworks.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
+                for (int i = 0; i < numHiddenNetworks; i++) {
+                    hiddenNetworkSSIDSet.add(settings.hiddenNetworks[i].ssid);
+                }
+            }
+            mLastScanSettings = new LastScanSettings(
+                        mClock.getElapsedSinceBootMillis(),
+                        reportFullResults, allFreqs, eventHandler);
+
+            boolean success = false;
+            Set<Integer> freqs;
+            if (!allFreqs.isEmpty()) {
+                freqs = allFreqs.getScanFreqs();
+                success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
+                if (!success) {
+                    Log.e(TAG, "Failed to start scan, freqs=" + freqs);
+                }
+            } else {
+                // There is a scan request but no available channels could be scanned for.
+                // We regard it as a scan failure in this case.
+                Log.e(TAG, "Failed to start scan because there is no available channel to scan");
+            }
+            if (success) {
+                if (DBG) {
+                    Log.d(TAG, "Starting wifi scan for freqs=" + freqs);
+                }
+
+                mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                        mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
+                        TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
+            } else {
+                // indicate scan failure async
+                mEventHandler.post(new Runnable() {
+                        public void run() {
+                            reportScanFailure();
+                        }
+                    });
+            }
+
             return true;
         }
     }
@@ -210,285 +218,28 @@
     @Override
     public boolean startBatchedScan(WifiNative.ScanSettings settings,
             WifiNative.ScanEventHandler eventHandler) {
-        if (settings == null || eventHandler == null) {
-            Log.w(TAG, "Invalid arguments for startBatched: settings=" + settings
-                    + ",eventHandler=" + eventHandler);
-            return false;
-        }
-
-        if (settings.max_ap_per_scan < 0 || settings.max_ap_per_scan > MAX_APS_PER_SCAN) {
-            return false;
-        }
-        if (settings.num_buckets < 0 || settings.num_buckets > MAX_SCAN_BUCKETS) {
-            return false;
-        }
-        if (settings.report_threshold_num_scans < 0
-                || settings.report_threshold_num_scans > SCAN_BUFFER_CAPACITY) {
-            return false;
-        }
-        if (settings.report_threshold_percent < 0 || settings.report_threshold_percent > 100) {
-            return false;
-        }
-        if (settings.base_period_ms <= 0) {
-            return false;
-        }
-        for (int i = 0; i < settings.num_buckets; ++i) {
-            WifiNative.BucketSettings bucket = settings.buckets[i];
-            if (bucket.period_ms % settings.base_period_ms != 0) {
-                return false;
-            }
-        }
-
-        synchronized (mSettingsLock) {
-            stopBatchedScan();
-            if (DBG) {
-                Log.d(TAG, "Starting scan num_buckets=" + settings.num_buckets + ", base_period="
-                        + settings.base_period_ms + " ms");
-            }
-            mPendingBackgroundScanSettings = settings;
-            mPendingBackgroundScanEventHandler = eventHandler;
-            handleScanPeriod(); // Try to start scan immediately
-            return true;
-        }
+        Log.w(TAG, "startBatchedScan() is not supported");
+        return false;
     }
 
     @Override
     public void stopBatchedScan() {
-        synchronized (mSettingsLock) {
-            if (DBG) Log.d(TAG, "Stopping scan");
-            mBackgroundScanSettings = null;
-            mBackgroundScanEventHandler = null;
-            mPendingBackgroundScanSettings = null;
-            mPendingBackgroundScanEventHandler = null;
-            mBackgroundScanPaused = false;
-            mBackgroundScanPeriodPending = false;
-            unscheduleScansLocked();
-        }
-        processPendingScans();
+        Log.w(TAG, "stopBatchedScan() is not supported");
     }
 
     @Override
     public void pauseBatchedScan() {
-        synchronized (mSettingsLock) {
-            if (DBG) Log.d(TAG, "Pausing scan");
-            // if there isn't a pending scan then make the current scan pending
-            if (mPendingBackgroundScanSettings == null) {
-                mPendingBackgroundScanSettings = mBackgroundScanSettings;
-                mPendingBackgroundScanEventHandler = mBackgroundScanEventHandler;
-            }
-            mBackgroundScanSettings = null;
-            mBackgroundScanEventHandler = null;
-            mBackgroundScanPeriodPending = false;
-            mBackgroundScanPaused = true;
-
-            unscheduleScansLocked();
-
-            WifiScanner.ScanData[] results = getLatestBatchedScanResults(/* flush = */ true);
-            if (mPendingBackgroundScanEventHandler != null) {
-                mPendingBackgroundScanEventHandler.onScanPaused(results);
-            }
-        }
-        processPendingScans();
+        Log.w(TAG, "pauseBatchedScan() is not supported");
     }
 
     @Override
     public void restartBatchedScan() {
-        synchronized (mSettingsLock) {
-            if (DBG) Log.d(TAG, "Restarting scan");
-            if (mPendingBackgroundScanEventHandler != null) {
-                mPendingBackgroundScanEventHandler.onScanRestarted();
-            }
-            mBackgroundScanPaused = false;
-            handleScanPeriod();
-        }
-    }
-
-    private void unscheduleScansLocked() {
-        mAlarmManager.cancel(mScanPeriodListener);
-        if (mLastScanSettings != null) {
-            mLastScanSettings.backgroundScanActive = false;
-        }
-    }
-
-    private void handleScanPeriod() {
-        synchronized (mSettingsLock) {
-            mBackgroundScanPeriodPending = true;
-            processPendingScans();
-        }
+        Log.w(TAG, "restartBatchedScan() is not supported");
     }
 
     private void handleScanTimeout() {
         Log.e(TAG, "Timed out waiting for scan result from wificond");
         reportScanFailure();
-        processPendingScans();
-    }
-
-    private boolean isDifferentPnoScanSettings(LastScanSettings newScanSettings) {
-        return (mLastScanSettings == null || !Arrays.equals(
-                newScanSettings.pnoNetworkList, mLastScanSettings.pnoNetworkList));
-    }
-
-    private void processPendingScans() {
-        synchronized (mSettingsLock) {
-            // Wait for the active scan result to come back to reschedule other scans,
-            // unless if HW pno scan is running. Hw PNO scans are paused it if there
-            // are other pending scans,
-            if (mLastScanSettings != null && !mLastScanSettings.hwPnoScanActive) {
-                return;
-            }
-
-            ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
-            Set<String> hiddenNetworkSSIDSet = new HashSet<>();
-            final LastScanSettings newScanSettings =
-                    new LastScanSettings(mClock.getElapsedSinceBootMillis());
-
-            // Update scan settings if there is a pending scan
-            if (!mBackgroundScanPaused) {
-                if (mPendingBackgroundScanSettings != null) {
-                    mBackgroundScanSettings = mPendingBackgroundScanSettings;
-                    mBackgroundScanEventHandler = mPendingBackgroundScanEventHandler;
-                    mNextBackgroundScanPeriod = 0;
-                    mPendingBackgroundScanSettings = null;
-                    mPendingBackgroundScanEventHandler = null;
-                    mBackgroundScanPeriodPending = true;
-                }
-                if (mBackgroundScanPeriodPending && mBackgroundScanSettings != null) {
-                    int reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; // default to no batch
-                    for (int bucket_id = 0; bucket_id < mBackgroundScanSettings.num_buckets;
-                            ++bucket_id) {
-                        WifiNative.BucketSettings bucket =
-                                mBackgroundScanSettings.buckets[bucket_id];
-                        if (mNextBackgroundScanPeriod % (bucket.period_ms
-                                        / mBackgroundScanSettings.base_period_ms) == 0) {
-                            if ((bucket.report_events
-                                            & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) {
-                                reportEvents |= WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
-                            }
-                            if ((bucket.report_events
-                                            & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
-                                reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
-                            }
-                            // only no batch if all buckets specify it
-                            if ((bucket.report_events
-                                            & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
-                                reportEvents &= ~WifiScanner.REPORT_EVENT_NO_BATCH;
-                            }
-
-                            allFreqs.addChannels(bucket);
-                        }
-                    }
-                    if (!allFreqs.isEmpty()) {
-                        newScanSettings.setBackgroundScan(mNextBackgroundScanId++,
-                                mBackgroundScanSettings.max_ap_per_scan, reportEvents,
-                                mBackgroundScanSettings.report_threshold_num_scans,
-                                mBackgroundScanSettings.report_threshold_percent);
-                    }
-                    mNextBackgroundScanPeriod++;
-                    mBackgroundScanPeriodPending = false;
-                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.getElapsedSinceBootMillis()
-                                    + mBackgroundScanSettings.base_period_ms,
-                            BACKGROUND_PERIOD_ALARM_TAG, mScanPeriodListener, mEventHandler);
-                }
-            }
-
-            if (mPendingSingleScanSettings != null) {
-                boolean reportFullResults = false;
-                ChannelCollection singleScanFreqs = mChannelHelper.createChannelCollection();
-                for (int i = 0; i < mPendingSingleScanSettings.num_buckets; ++i) {
-                    WifiNative.BucketSettings bucketSettings =
-                            mPendingSingleScanSettings.buckets[i];
-                    if ((bucketSettings.report_events
-                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
-                        reportFullResults = true;
-                    }
-                    singleScanFreqs.addChannels(bucketSettings);
-                    allFreqs.addChannels(bucketSettings);
-                }
-                newScanSettings.setSingleScan(reportFullResults, singleScanFreqs,
-                        mPendingSingleScanEventHandler);
-
-                WifiNative.HiddenNetwork[] hiddenNetworks =
-                        mPendingSingleScanSettings.hiddenNetworks;
-                if (hiddenNetworks != null) {
-                    int numHiddenNetworks =
-                            Math.min(hiddenNetworks.length, MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
-                    for (int i = 0; i < numHiddenNetworks; i++) {
-                        hiddenNetworkSSIDSet.add(hiddenNetworks[i].ssid);
-                    }
-                }
-
-                mPendingSingleScanSettings = null;
-                mPendingSingleScanEventHandler = null;
-            }
-
-            if (newScanSettings.backgroundScanActive || newScanSettings.singleScanActive) {
-                boolean success = false;
-                Set<Integer> freqs;
-                if (!allFreqs.isEmpty()) {
-                    pauseHwPnoScan();
-                    freqs = allFreqs.getScanFreqs();
-                    success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
-                    if (!success) {
-                        Log.e(TAG, "Failed to start scan, freqs=" + freqs);
-                    }
-                } else {
-                    // There is a scan request but no available channels could be scanned for.
-                    // We regard it as a scan failure in this case.
-                    Log.e(TAG, "Failed to start scan because there is "
-                            + "no available channel to scan for");
-                }
-                if (success) {
-                    // TODO handle scan timeout
-                    if (DBG) {
-                        Log.d(TAG, "Starting wifi scan for freqs=" + freqs
-                                + ", background=" + newScanSettings.backgroundScanActive
-                                + ", single=" + newScanSettings.singleScanActive);
-                    }
-                    mLastScanSettings = newScanSettings;
-                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
-                            TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
-                } else {
-                    // indicate scan failure async
-                    mEventHandler.post(new Runnable() {
-                            public void run() {
-                                if (newScanSettings.singleScanEventHandler != null) {
-                                    newScanSettings.singleScanEventHandler
-                                            .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
-                                }
-                            }
-                        });
-                    // TODO(b/27769665) background scans should be failed too if scans fail enough
-                }
-            } else if (isHwPnoScanRequired()) {
-                newScanSettings.setHwPnoScan(mPnoSettings.networkList, mPnoEventHandler);
-                boolean status;
-                // If the PNO network list has changed from the previous request, ensure that
-                // we bypass the debounce logic and restart PNO scan.
-                if (isDifferentPnoScanSettings(newScanSettings)) {
-                    status = restartHwPnoScan(mPnoSettings);
-                } else {
-                    status = startHwPnoScan(mPnoSettings);
-                }
-                if (status) {
-                    mLastScanSettings = newScanSettings;
-                } else {
-                    Log.e(TAG, "Failed to start PNO scan");
-                    // indicate scan failure async
-                    mEventHandler.post(new Runnable() {
-                        public void run() {
-                            if (mPnoEventHandler != null) {
-                                mPnoEventHandler.onPnoScanFailed();
-                            }
-                            // Clean up PNO state, we don't want to continue PNO scanning.
-                            mPnoSettings = null;
-                            mPnoEventHandler = null;
-                        }
-                    });
-                }
-            }
-        }
     }
 
     @Override
@@ -498,16 +249,13 @@
                 Log.w(TAG, "Scan failed");
                 mAlarmManager.cancel(mScanTimeoutListener);
                 reportScanFailure();
-                processPendingScans();
                 break;
             case WifiMonitor.PNO_SCAN_RESULTS_EVENT:
                 pollLatestScanDataForPno();
-                processPendingScans();
                 break;
             case WifiMonitor.SCAN_RESULTS_EVENT:
                 mAlarmManager.cancel(mScanTimeoutListener);
                 pollLatestScanData();
-                processPendingScans();
                 break;
             default:
                 // ignore unknown event
@@ -522,7 +270,6 @@
                     mLastScanSettings.singleScanEventHandler
                             .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
                 }
-                // TODO(b/27769665) background scans should be failed too if scans fail enough
                 mLastScanSettings = null;
             }
         }
@@ -530,21 +277,19 @@
 
     private void reportPnoScanFailure() {
         synchronized (mSettingsLock) {
-            if (mLastScanSettings != null && mLastScanSettings.hwPnoScanActive) {
-                if (mLastScanSettings.pnoScanEventHandler != null) {
-                    mLastScanSettings.pnoScanEventHandler.onPnoScanFailed();
+            if (mLastPnoScanSettings != null) {
+                if (mLastPnoScanSettings.pnoScanEventHandler != null) {
+                    mLastPnoScanSettings.pnoScanEventHandler.onPnoScanFailed();
                 }
                 // Clean up PNO state, we don't want to continue PNO scanning.
-                mPnoSettings = null;
-                mPnoEventHandler = null;
-                mLastScanSettings = null;
+                mLastPnoScanSettings = null;
             }
         }
     }
 
     private void pollLatestScanDataForPno() {
         synchronized (mSettingsLock) {
-            if (mLastScanSettings == null) {
+            if (mLastPnoScanSettings == null) {
                  // got a scan before we started scanning or after scan was canceled
                 return;
             }
@@ -554,10 +299,8 @@
             for (int i = 0; i < mNativeScanResults.size(); ++i) {
                 ScanResult result = mNativeScanResults.get(i).getScanResult();
                 long timestamp_ms = result.timestamp / 1000; // convert us -> ms
-                if (timestamp_ms > mLastScanSettings.startTime) {
-                    if (mLastScanSettings.hwPnoScanActive) {
-                        hwPnoScanResults.add(result);
-                    }
+                if (timestamp_ms > mLastPnoScanSettings.startTime) {
+                    hwPnoScanResults.add(result);
                 } else {
                     numFilteredScanResults++;
                 }
@@ -567,25 +310,11 @@
                 Log.d(TAG, "Filtering out " + numFilteredScanResults + " pno scan results.");
             }
 
-            if (mLastScanSettings.hwPnoScanActive
-                    && mLastScanSettings.pnoScanEventHandler != null) {
+            if (mLastPnoScanSettings.pnoScanEventHandler != null) {
                 ScanResult[] pnoScanResultsArray =
                         hwPnoScanResults.toArray(new ScanResult[hwPnoScanResults.size()]);
-                mLastScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray);
+                mLastPnoScanSettings.pnoScanEventHandler.onPnoNetworkFound(pnoScanResultsArray);
             }
-            // On pno scan result event, we are expecting a mLastScanSettings for pno scan.
-            // However, if unlikey mLastScanSettings is for single scan, we need this part
-            // to protect from leaving WifiSingleScanStateMachine in a forever wait state.
-            if (mLastScanSettings.singleScanActive
-                    && mLastScanSettings.singleScanEventHandler != null) {
-                Log.w(TAG, "Polling pno scan result when single scan is active, reporting"
-                        + " single scan failure");
-                mLastScanSettings.singleScanEventHandler
-                        .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
-            }
-            // mLastScanSettings is for either single/batched scan or pno scan.
-            // We can safely set it to null when pno scan finishes.
-            mLastScanSettings = null;
         }
     }
 
@@ -607,20 +336,14 @@
                 return;
             }
 
-            if (DBG) Log.d(TAG, "Polling scan data for scan: " + mLastScanSettings.scanId);
             mNativeScanResults = mWifiNative.getScanResults();
             List<ScanResult> singleScanResults = new ArrayList<>();
-            List<ScanResult> backgroundScanResults = new ArrayList<>();
             int numFilteredScanResults = 0;
             for (int i = 0; i < mNativeScanResults.size(); ++i) {
                 ScanResult result = mNativeScanResults.get(i).getScanResult();
                 long timestamp_ms = result.timestamp / 1000; // convert us -> ms
                 if (timestamp_ms > mLastScanSettings.startTime) {
-                    if (mLastScanSettings.backgroundScanActive) {
-                        backgroundScanResults.add(result);
-                    }
-                    if (mLastScanSettings.singleScanActive
-                            && mLastScanSettings.singleScanFreqs.containsChannel(
+                    if (mLastScanSettings.singleScanFreqs.containsChannel(
                                     result.frequency)) {
                         singleScanResults.add(result);
                     }
@@ -632,51 +355,7 @@
                 Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results.");
             }
 
-            if (mLastScanSettings.backgroundScanActive) {
-                if (mBackgroundScanEventHandler != null) {
-                    if ((mLastScanSettings.reportEvents
-                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
-                        for (ScanResult scanResult : backgroundScanResults) {
-                            // TODO(b/27506257): Fill in correct bucketsScanned value
-                            mBackgroundScanEventHandler.onFullScanResult(scanResult, 0);
-                        }
-                    }
-                }
-
-                Collections.sort(backgroundScanResults, SCAN_RESULT_SORT_COMPARATOR);
-                ScanResult[] scanResultsArray = new ScanResult[Math.min(mLastScanSettings.maxAps,
-                            backgroundScanResults.size())];
-                for (int i = 0; i < scanResultsArray.length; ++i) {
-                    scanResultsArray[i] = backgroundScanResults.get(i);
-                }
-
-                if ((mLastScanSettings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
-                    // TODO(b/27506257): Fill in correct bucketsScanned value
-                    mBackgroundScanBuffer.add(new WifiScanner.ScanData(mLastScanSettings.scanId, 0,
-                                    scanResultsArray));
-                }
-
-                if (mBackgroundScanEventHandler != null) {
-                    if ((mLastScanSettings.reportEvents
-                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0
-                            || (mLastScanSettings.reportEvents
-                                    & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0
-                            || (mLastScanSettings.reportEvents
-                                    == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL
-                                    && (mBackgroundScanBuffer.size()
-                                            >= (mBackgroundScanBuffer.capacity()
-                                                    * mLastScanSettings.reportPercentThreshold
-                                                    / 100)
-                                            || mBackgroundScanBuffer.size()
-                                            >= mLastScanSettings.reportNumScansThreshold))) {
-                        mBackgroundScanEventHandler
-                                .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
-                    }
-                }
-            }
-
-            if (mLastScanSettings.singleScanActive
-                    && mLastScanSettings.singleScanEventHandler != null) {
+            if (mLastScanSettings.singleScanEventHandler != null) {
                 if (mLastScanSettings.reportSingleScanFullResults) {
                     for (ScanResult scanResult : singleScanResults) {
                         // ignore buckets scanned since there is only one bucket for a single scan
@@ -685,7 +364,7 @@
                     }
                 }
                 Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
-                mLatestSingleScanResult = new WifiScanner.ScanData(mLastScanSettings.scanId, 0, 0,
+                mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, 0,
                         isAllChannelsScanned(mLastScanSettings.singleScanFreqs),
                         singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
                 mLastScanSettings.singleScanEventHandler
@@ -699,30 +378,15 @@
 
     @Override
     public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) {
-        synchronized (mSettingsLock) {
-            WifiScanner.ScanData[] results = mBackgroundScanBuffer.get();
-            if (flush) {
-                mBackgroundScanBuffer.clear();
-            }
-            return results;
-        }
+        return null;
     }
 
     private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) {
-        return mHwPnoDebouncer.startPnoScan(pnoSettings, mHwPnoDebouncerListener);
+        return mWifiNative.startPnoScan(pnoSettings);
     }
 
     private void stopHwPnoScan() {
-        mHwPnoDebouncer.stopPnoScan();
-    }
-
-    private void pauseHwPnoScan() {
-        mHwPnoDebouncer.forceStopPnoScan();
-    }
-
-    private boolean restartHwPnoScan(WifiNative.PnoSettings pnoSettings) {
-        mHwPnoDebouncer.forceStopPnoScan();
-        return mHwPnoDebouncer.startPnoScan(pnoSettings, mHwPnoDebouncerListener);
+        mWifiNative.stopPnoScan();
     }
 
     /**
@@ -734,24 +398,27 @@
         return (!isConnectedPno & mHwPnoScanSupported);
     }
 
-    private boolean isHwPnoScanRequired() {
-        if (mPnoSettings == null) return false;
-        return isHwPnoScanRequired(mPnoSettings.isConnected);
-    }
-
     @Override
     public boolean setHwPnoList(WifiNative.PnoSettings settings,
             WifiNative.PnoEventHandler eventHandler) {
         synchronized (mSettingsLock) {
-            if (mPnoSettings != null) {
+            if (mLastPnoScanSettings != null) {
                 Log.w(TAG, "Already running a PNO scan");
                 return false;
             }
-            mPnoEventHandler = eventHandler;
-            mPnoSettings = settings;
+            if (!isHwPnoScanRequired(settings.isConnected)) {
+                return false;
+            }
 
-            // For wificond based PNO, we start the scan immediately when we set pno list.
-            processPendingScans();
+            if (startHwPnoScan(settings)) {
+                mLastPnoScanSettings = new LastPnoScanSettings(
+                            mClock.getElapsedSinceBootMillis(),
+                            settings.networkList, eventHandler);
+
+            } else {
+                Log.e(TAG, "Failed to start PNO scan");
+                reportPnoScanFailure();
+            }
             return true;
         }
     }
@@ -759,12 +426,11 @@
     @Override
     public boolean resetHwPnoList() {
         synchronized (mSettingsLock) {
-            if (mPnoSettings == null) {
+            if (mLastPnoScanSettings == null) {
                 Log.w(TAG, "No PNO scan running");
                 return false;
             }
-            mPnoEventHandler = null;
-            mPnoSettings = null;
+            mLastPnoScanSettings = null;
             // For wificond based PNO, we stop the scan immediately when we reset pno list.
             stopHwPnoScan();
             return true;
@@ -778,11 +444,6 @@
     }
 
     @Override
-    public boolean shouldScheduleBackgroundScanForHwPno() {
-        return false;
-    }
-
-    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mSettingsLock) {
             pw.println("Latest native scan results:");
@@ -817,260 +478,36 @@
     }
 
     private static class LastScanSettings {
-        public long startTime;
-
-        LastScanSettings(long startTime) {
-            this.startTime = startTime;
-        }
-
-        // Background settings
-        public boolean backgroundScanActive = false;
-        public int scanId;
-        public int maxAps;
-        public int reportEvents;
-        public int reportNumScansThreshold;
-        public int reportPercentThreshold;
-
-        public void setBackgroundScan(int scanId, int maxAps, int reportEvents,
-                int reportNumScansThreshold, int reportPercentThreshold) {
-            this.backgroundScanActive = true;
-            this.scanId = scanId;
-            this.maxAps = maxAps;
-            this.reportEvents = reportEvents;
-            this.reportNumScansThreshold = reportNumScansThreshold;
-            this.reportPercentThreshold = reportPercentThreshold;
-        }
-
-        // Single scan settings
-        public boolean singleScanActive = false;
-        public boolean reportSingleScanFullResults;
-        public ChannelCollection singleScanFreqs;
-        public WifiNative.ScanEventHandler singleScanEventHandler;
-
-        public void setSingleScan(boolean reportSingleScanFullResults,
+        LastScanSettings(long startTime,
+                boolean reportSingleScanFullResults,
                 ChannelCollection singleScanFreqs,
                 WifiNative.ScanEventHandler singleScanEventHandler) {
-            singleScanActive = true;
+            this.startTime = startTime;
             this.reportSingleScanFullResults = reportSingleScanFullResults;
             this.singleScanFreqs = singleScanFreqs;
             this.singleScanEventHandler = singleScanEventHandler;
         }
 
-        public boolean hwPnoScanActive = false;
-        public WifiNative.PnoNetwork[] pnoNetworkList;
-        public WifiNative.PnoEventHandler pnoScanEventHandler;
+        public long startTime;
+        public boolean reportSingleScanFullResults;
+        public ChannelCollection singleScanFreqs;
+        public WifiNative.ScanEventHandler singleScanEventHandler;
 
-        public void setHwPnoScan(
+    }
+
+    private static class LastPnoScanSettings {
+        LastPnoScanSettings(long startTime,
                 WifiNative.PnoNetwork[] pnoNetworkList,
                 WifiNative.PnoEventHandler pnoScanEventHandler) {
-            hwPnoScanActive = true;
+            this.startTime = startTime;
             this.pnoNetworkList = pnoNetworkList;
             this.pnoScanEventHandler = pnoScanEventHandler;
         }
+
+        public long startTime;
+        public WifiNative.PnoNetwork[] pnoNetworkList;
+        public WifiNative.PnoEventHandler pnoScanEventHandler;
+
     }
 
-
-    private static class ScanBuffer {
-        private final ArrayDeque<WifiScanner.ScanData> mBuffer;
-        private int mCapacity;
-
-        ScanBuffer(int capacity) {
-            mCapacity = capacity;
-            mBuffer = new ArrayDeque<>(mCapacity);
-        }
-
-        public int size() {
-            return mBuffer.size();
-        }
-
-        public int capacity() {
-            return mCapacity;
-        }
-
-        public boolean isFull() {
-            return size() == mCapacity;
-        }
-
-        public void add(WifiScanner.ScanData scanData) {
-            if (isFull()) {
-                mBuffer.pollFirst();
-            }
-            mBuffer.offerLast(scanData);
-        }
-
-        public void clear() {
-            mBuffer.clear();
-        }
-
-        public WifiScanner.ScanData[] get() {
-            return mBuffer.toArray(new WifiScanner.ScanData[mBuffer.size()]);
-        }
-    }
-
-    /**
-     * HW PNO Debouncer is used to debounce PNO requests. This guards against toggling the PNO
-     * state too often which is not handled very well by some drivers.
-     * Note: This is not thread safe!
-     */
-    public static class HwPnoDebouncer {
-        public static final String PNO_DEBOUNCER_ALARM_TAG = TAG + "Pno Monitor";
-        private static final int MINIMUM_PNO_GAP_MS = 5 * 1000;
-
-        private final WifiNative mWifiNative;
-        private final AlarmManager mAlarmManager;
-        private final Handler mEventHandler;
-        private final Clock mClock;
-        private long mLastPnoChangeTimeStamp = -1L;
-        private boolean mExpectedPnoState = false;
-        private boolean mCurrentPnoState = false;;
-        private boolean mWaitForTimer = false;
-        private Listener mListener;
-        private WifiNative.PnoSettings mPnoSettings;
-
-        /**
-         * Interface used to indicate PNO scan notifications.
-         */
-        public interface Listener {
-            /**
-             * Used to indicate a delayed PNO scan request failure.
-             */
-            void onPnoScanFailed();
-        }
-
-        public HwPnoDebouncer(WifiNative wifiNative, AlarmManager alarmManager,
-                Handler eventHandler, Clock clock) {
-            mWifiNative = wifiNative;
-            mAlarmManager = alarmManager;
-            mEventHandler = eventHandler;
-            mClock = clock;
-        }
-
-        /**
-         * Enable PNO state in wificond
-         */
-        private boolean startPnoScanInternal() {
-            if (mCurrentPnoState) {
-                if (DBG) Log.d(TAG, "PNO state is already enable");
-                return true;
-            }
-            if (mPnoSettings == null) {
-                Log.e(TAG, "PNO state change to enable failed, no available Pno settings");
-                return false;
-            }
-            mLastPnoChangeTimeStamp = mClock.getElapsedSinceBootMillis();
-            Log.d(TAG, "Remove all networks from supplicant before starting PNO scan");
-            mWifiNative.removeAllNetworks();
-            if (mWifiNative.startPnoScan(mPnoSettings)) {
-                Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to enable");
-                mCurrentPnoState = true;
-                return true;
-            } else {
-                Log.e(TAG, "PNO state change to enable failed");
-                mCurrentPnoState = false;
-            }
-            return false;
-        }
-
-        /**
-         * Disable PNO state in wificond
-         */
-        private boolean stopPnoScanInternal() {
-            if (!mCurrentPnoState) {
-                if (DBG) Log.d(TAG, "PNO state is already disable");
-                return true;
-            }
-            mLastPnoChangeTimeStamp = mClock.getElapsedSinceBootMillis();
-            if (mWifiNative.stopPnoScan()) {
-                Log.d(TAG, "Changed PNO state from " + mCurrentPnoState + " to disable");
-                mCurrentPnoState = false;
-                return true;
-            } else {
-                Log.e(TAG, "PNO state change to disable failed");
-                mCurrentPnoState = false;
-            }
-            return false;
-        }
-
-        private final AlarmManager.OnAlarmListener mAlarmListener =
-                new AlarmManager.OnAlarmListener() {
-            public void onAlarm() {
-                if (DBG) Log.d(TAG, "PNO timer expired, expected state " + mExpectedPnoState);
-                if (mExpectedPnoState) {
-                    if (!startPnoScanInternal()) {
-                        if (mListener != null) {
-                            mListener.onPnoScanFailed();
-                        }
-                    }
-                } else {
-                    stopPnoScanInternal();
-                }
-                mWaitForTimer = false;
-            }
-        };
-
-        /**
-         * Enable/Disable PNO state. This method will debounce PNO scan requests.
-         * @param enable boolean indicating whether PNO is being enabled or disabled.
-         */
-        private boolean setPnoState(boolean enable) {
-            boolean isSuccess = true;
-            mExpectedPnoState = enable;
-            if (!mWaitForTimer) {
-                long timeDifference = mClock.getElapsedSinceBootMillis() - mLastPnoChangeTimeStamp;
-                if (timeDifference >= MINIMUM_PNO_GAP_MS) {
-                    if (enable) {
-                        isSuccess = startPnoScanInternal();
-                    } else {
-                        isSuccess = stopPnoScanInternal();
-                    }
-                } else {
-                    long alarmTimeout = MINIMUM_PNO_GAP_MS - timeDifference;
-                    Log.d(TAG, "Start PNO timer with delay " + alarmTimeout);
-                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.getElapsedSinceBootMillis() + alarmTimeout,
-                            PNO_DEBOUNCER_ALARM_TAG,
-                            mAlarmListener, mEventHandler);
-                    mWaitForTimer = true;
-                }
-            }
-            return isSuccess;
-        }
-
-        /**
-         * Start PNO scan
-         */
-        public boolean startPnoScan(WifiNative.PnoSettings pnoSettings, Listener listener) {
-            if (DBG) Log.d(TAG, "Starting PNO scan");
-            mListener = listener;
-            mPnoSettings = pnoSettings;
-            if (!setPnoState(true)) {
-                mListener = null;
-                return false;
-            }
-            return true;
-        }
-
-        /**
-         * Stop PNO scan
-         */
-        public void stopPnoScan() {
-            if (DBG) Log.d(TAG, "Stopping PNO scan");
-            setPnoState(false);
-            mListener = null;
-        }
-
-        /**
-         * Force stop PNO scanning. This method will bypass the debounce logic and stop PNO
-         * scan immediately.
-         */
-        public void forceStopPnoScan() {
-            if (DBG) Log.d(TAG, "Force stopping Pno scan");
-            // Cancel the debounce timer and stop PNO scan.
-            if (mWaitForTimer) {
-                mAlarmManager.cancel(mAlarmListener);
-                mWaitForTimer = false;
-            }
-            stopPnoScanInternal();
-        }
-    }
 }
diff --git a/service/java/com/android/server/wifi/util/ApConfigUtil.java b/service/java/com/android/server/wifi/util/ApConfigUtil.java
index 7bbbc03..b1a4948 100644
--- a/service/java/com/android/server/wifi/util/ApConfigUtil.java
+++ b/service/java/com/android/server/wifi/util/ApConfigUtil.java
@@ -133,15 +133,9 @@
                     config.apBand, allowed2GChannels,
                     wifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ));
             if (config.apChannel == -1) {
-                if (wifiNative.isGetChannelsForBandSupported()) {
-                    /* We're not able to get channel when it is supported by HAL. */
-                    Log.e(TAG, "Failed to get available channel.");
-                    return ERROR_NO_CHANNEL;
-                }
-
-                /* Use the default for HAL without get channel support. */
-                config.apBand = DEFAULT_AP_BAND;
-                config.apChannel = DEFAULT_AP_CHANNEL;
+                /* We're not able to get channel from wificond. */
+                Log.e(TAG, "Failed to get available channel.");
+                return ERROR_NO_CHANNEL;
             }
         }
 
diff --git a/service/java/com/android/server/wifi/util/InformationElementUtil.java b/service/java/com/android/server/wifi/util/InformationElementUtil.java
index c8f9ca3..14912b5 100644
--- a/service/java/com/android/server/wifi/util/InformationElementUtil.java
+++ b/service/java/com/android/server/wifi/util/InformationElementUtil.java
@@ -247,6 +247,10 @@
                         "Bad Interworking element length: " + ie.bytes.length);
             }
 
+            if (ie.bytes.length == 3 || ie.bytes.length == 9) {
+                int venueInfo = (int) ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 2);
+            }
+
             if (ie.bytes.length == 7 || ie.bytes.length == 9) {
                 hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
             }
diff --git a/service/java/com/android/server/wifi/util/KalmanFilter.java b/service/java/com/android/server/wifi/util/KalmanFilter.java
new file mode 100644
index 0000000..b961ed8
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/KalmanFilter.java
@@ -0,0 +1,62 @@
+/*
+ * 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.util;
+
+/**
+ * Utility providiing a basic Kalman filter
+ *
+ * For background, see https://en.wikipedia.org/wiki/Kalman_filter
+ */
+public class KalmanFilter {
+    public Matrix mF; // stateTransition
+    public Matrix mQ; // processNoiseCovariance
+    public Matrix mH; // observationModel
+    public Matrix mR; // observationNoiseCovariance
+    public Matrix mP; // aPosterioriErrorCovariance
+    public Matrix mx; // stateEstimate
+
+    /**
+     * Performs the prediction phase of the filter, using the state estimate to produce
+     * a new estimate for the current timestep.
+     */
+    public void predict() {
+        mx = mF.dot(mx);
+        mP = mF.dot(mP).dotTranspose(mF).plus(mQ);
+    }
+
+    /**
+     * Updates the state estimate to incorporate the new observation z.
+     */
+    public void update(Matrix z) {
+        Matrix y = z.minus(mH.dot(mx));
+        Matrix tS = mH.dot(mP).dotTranspose(mH).plus(mR);
+        Matrix tK = mP.dotTranspose(mH).dot(tS.inverse());
+        mx = mx.plus(tK.dot(y));
+        mP = mP.minus(tK.dot(mH).dot(mP));
+    }
+
+    @Override
+    public String toString() {
+        return "{F: " + mF
+                + " Q: " + mQ
+                + " H: " + mH
+                + " R: " + mR
+                + " P: " + mP
+                + " x: " + mx
+                + "}";
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/Matrix.java b/service/java/com/android/server/wifi/util/Matrix.java
index bdf147e..afd9de6 100644
--- a/service/java/com/android/server/wifi/util/Matrix.java
+++ b/service/java/com/android/server/wifi/util/Matrix.java
@@ -152,6 +152,34 @@
     }
 
     /**
+     * Forms a scalar product
+     *
+     * @param scalar is the value to multiply by
+     * @return newly allocated matrix representing the product this and scalar
+     */
+    public Matrix times(double scalar) {
+        return times(scalar, new Matrix(n, m));
+    }
+
+    /**
+     * Forms a scalar product
+     *
+     * @param scalar is the value to multiply by
+     * @param result is space to hold the result
+     * @return result, filled with the matrix difference
+     * @throws IllegalArgumentException if shapes differ
+     */
+    public Matrix times(double scalar, Matrix result) {
+        if (!(this.n == result.n && this.m == result.m)) {
+            throw new IllegalArgumentException();
+        }
+        for (int i = 0; i < mem.length; i++) {
+            result.mem[i] = this.mem[i] * scalar;
+        }
+        return result;
+    }
+
+    /**
      * Forms the matrix product of two matrices, this and that
      *
      * @param that is the other matrix
@@ -291,6 +319,42 @@
         }
         return result;
     }
+    /**
+     * Forms the matrix product with the transpose of a second matrix
+     *
+     * @param that is the other matrix
+     * @return newly allocated matrix representing the matrix product of this and that.transpose()
+     * @throws IllegalArgumentException if shapes are not conformant
+     */
+    public Matrix dotTranspose(Matrix that) {
+        return dotTranspose(that, new Matrix(this.n, that.n));
+    }
+
+    /**
+     * Forms the matrix product with the transpose of a second matrix
+     * <p>
+     * Caller supplies an object to contain the result, as well as scratch space
+     *
+     * @param that is the other matrix
+     * @param result is space to hold the result
+     * @return result, filled with the matrix product of this and that.transpose()
+     * @throws IllegalArgumentException if shapes are not conformant
+     */
+    public Matrix dotTranspose(Matrix that, Matrix result) {
+        if (!(this.n == result.n && this.m == that.m && that.n == result.m)) {
+            throw new IllegalArgumentException();
+        }
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < that.n; j++) {
+                double s = 0.0;
+                for (int k = 0; k < m; k++) {
+                    s += this.get(i, k) * that.get(j, k);
+                }
+                result.put(i, j, s);
+            }
+        }
+        return result;
+    }
 
     /**
      * Tests for equality
diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
index 52c9618..4ae7d13 100644
--- a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
+++ b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
@@ -17,23 +17,15 @@
 package com.android.server.wifi.util;
 
 import android.Manifest;
-import android.annotation.Nullable;
 import android.app.AppOpsManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.net.ConnectivityManager;
 import android.net.NetworkScoreManager;
-import android.net.NetworkScorerAppData;
-import android.net.wifi.WifiConfiguration;
-import android.os.Binder;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.provider.Settings;
-import android.text.TextUtils;
 
-import com.android.server.wifi.FrameworkFacade;
 import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiLog;
 import com.android.server.wifi.WifiSettingsStore;
@@ -52,7 +44,6 @@
     private final UserManager mUserManager;
     private final WifiSettingsStore mSettingsStore;
     private final NetworkScoreManager mNetworkScoreManager;
-    private final FrameworkFacade mFrameworkFacade;
     private WifiLog mLog;
 
     public WifiPermissionsUtil(WifiPermissionsWrapper wifiPermissionsWrapper,
@@ -65,7 +56,6 @@
         mSettingsStore = settingsStore;
         mLog = wifiInjector.makeLog(TAG);
         mNetworkScoreManager = networkScoreManager;
-        mFrameworkFacade = wifiInjector.getFrameworkFacade();
     }
 
     /**
@@ -101,16 +91,6 @@
     }
 
     /**
-     * Check and enforce tether change permission.
-     *
-     * @param context Context object of the caller.
-     */
-    public void enforceTetherChangePermission(Context context) {
-        String pkgName = context.getOpPackageName();
-        ConnectivityManager.enforceTetherChangePermission(context, pkgName);
-    }
-
-    /**
      * Check and enforce Location permission.
      *
      * @param pkgName PackageName of the application requesting access
@@ -176,79 +156,8 @@
         }
         // If the User or profile is current, permission is granted
         // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
-        if (!canAccessUserProfile(uid)) {
-            mLog.tC("Denied: Profile not permitted");
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * API to determine if the caller has permissions to get a {@link android.net.wifi.WifiInfo}
-     * instance containing the SSID and BSSID.
-     *
-     *
-     * @param currentConfig the currently connected WiFi config
-     * @param pkgName package name of the application requesting access
-     * @param uid The uid of the package
-     * @param minVersion Minimum app API Version number to enforce location permission
-     * @return boolean true if the SSID/BSSID can be sent to the user, false if they
-     *         should be hidden/removed.
-     */
-    public boolean canAccessFullConnectionInfo(@Nullable WifiConfiguration currentConfig,
-            String pkgName, int uid, int minVersion) throws SecurityException {
-        mAppOps.checkPackage(uid, pkgName);
-
-        // The User or profile must be current or the uid must
-        // have INTERACT_ACROSS_USERS_FULL permission.
-        if (!canAccessUserProfile(uid)) {
-            mLog.tC("Denied: Profile not permitted");
-            return false;
-        }
-
-        // If the caller has scan result access then they can also see the full connection info.
-        // Otherwise the caller must be the active use open wifi package and the current config
-        // must be for an open network.
-        return canAccessScanResults(pkgName, uid, minVersion)
-                || isUseOpenWifiPackageWithConnectionInfoAccess(currentConfig, pkgName);
-
-    }
-
-    /**
-     * Returns true if the given WiFi config is for an open network and the package is the active
-     * use open wifi app.
-     */
-    private boolean isUseOpenWifiPackageWithConnectionInfoAccess(
-            @Nullable WifiConfiguration currentConfig, String pkgName) {
-
-        // Access is only granted for open networks.
-        if (currentConfig == null) {
-            mLog.tC("Denied: WifiConfiguration is NULL.");
-            return false;
-        }
-
-        // Access is only granted for open networks.
-        if (!currentConfig.isOpenNetwork()) {
-            mLog.tC("Denied: The current config is not for an open network.");
-            return false;
-        }
-
-        // The USE_OPEN_WIFI_PACKAGE can access the full connection info details without
-        // scan result access.
-        if (!isUseOpenWifiPackage(pkgName)) {
-            mLog.tC("Denied: caller is not the current USE_OPEN_WIFI_PACKAGE");
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns true if the User or profile is current or the
-     * uid has the INTERACT_ACROSS_USERS_FULL permission.
-     */
-    private boolean canAccessUserProfile(int uid) {
         if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) {
+            mLog.tC("Denied: Profile not permitted");
             return false;
         }
         return true;
@@ -271,43 +180,6 @@
     }
 
     /**
-     * Returns true if the given package is equal to the setting keyed by
-     * {@link Settings.Global#USE_OPEN_WIFI_PACKAGE} and the NetworkScoreManager
-     * has the package name set as the use open wifi package.
-     */
-    private boolean isUseOpenWifiPackage(String packageName) {
-        if (TextUtils.isEmpty(packageName)) {
-            return false;
-        }
-
-        // When the setting is enabled it's set to the package name of the use open wifi app.
-        final String useOpenWifiPkg =
-                mFrameworkFacade.getStringSetting(mContext, Settings.Global.USE_OPEN_WIFI_PACKAGE);
-        if (packageName.equals(useOpenWifiPkg)) {
-            // If the package name matches the setting then also confirm that the scorer is
-            // active and the package matches the expected use open wifi package from the scorer's
-            // perspective. The scorer can be active when the use open wifi feature is off so we
-            // can't rely on this check alone.
-            // TODO(b/67278755): Refactor this into an API similar to isCallerActiveScorer()
-            final NetworkScorerAppData appData;
-            final long token = Binder.clearCallingIdentity();
-            try {
-                appData = mNetworkScoreManager.getActiveScorer();
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-            if (appData != null) {
-                final ComponentName enableUseOpenWifiActivity =
-                        appData.getEnableUseOpenWifiActivity();
-                return enableUseOpenWifiActivity != null
-                        && packageName.equals(enableUseOpenWifiActivity.getPackageName());
-            }
-        }
-
-        return false;
-    }
-
-    /**
      * Returns true if Wifi scan operation is allowed for this caller
      * and package.
      */
@@ -329,24 +201,19 @@
      * current user.
      */
     private boolean isCurrentProfile(int uid) {
-        final long token = Binder.clearCallingIdentity();
-        try {
-            int currentUser = mWifiPermissionsWrapper.getCurrentUser();
-            int callingUserId = mWifiPermissionsWrapper.getCallingUserId(uid);
-            if (callingUserId == currentUser) {
-                return true;
-            } else {
-                List<UserInfo> userProfiles = mUserManager.getProfiles(currentUser);
-                for (UserInfo user : userProfiles) {
-                    if (user.id == callingUserId) {
-                        return true;
-                    }
+        int currentUser = mWifiPermissionsWrapper.getCurrentUser();
+        int callingUserId = mWifiPermissionsWrapper.getCallingUserId(uid);
+        if (callingUserId == currentUser) {
+            return true;
+        } else {
+            List<UserInfo> userProfiles = mUserManager.getProfiles(currentUser);
+            for (UserInfo user : userProfiles) {
+                if (user.id == callingUserId) {
+                    return true;
                 }
             }
-            return false;
-        } finally {
-            Binder.restoreCallingIdentity(token);
         }
+        return false;
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java b/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java
index 6fde01e..84aacdf 100644
--- a/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java
+++ b/service/java/com/android/server/wifi/util/WifiPermissionsWrapper.java
@@ -108,4 +108,16 @@
         return AppGlobals.getPackageManager().checkUidPermission(
                 Manifest.permission.CHANGE_WIFI_STATE, uid);
     }
+
+    /**
+     * Determines if the caller has local mac address permission.
+     *
+     * @param uid to check the permission for
+     * @return int representation of success or denied
+     * @throws RemoteException
+     */
+    public int getLocalMacAddressPermission(int uid) throws RemoteException {
+        return AppGlobals.getPackageManager().checkUidPermission(
+                Manifest.permission.LOCAL_MAC_ADDRESS, uid);
+    }
 }
diff --git a/tests/wifitests/Android.mk b/tests/wifitests/Android.mk
index 1e64ddb..b5c5844 100644
--- a/tests/wifitests/Android.mk
+++ b/tests/wifitests/Android.mk
@@ -68,7 +68,9 @@
 	android.test.runner \
 	wifi-service \
 	services \
-	android.hidl.manager-V1.0-java
+	android.hidl.manager-V1.0-java \
+	android.test.base \
+	android.test.mock
 
 # These must be explicitly included because they are not normally accessible
 # from apps.
@@ -105,6 +107,7 @@
 	libwifi-system \
 	libui \
 	libunwind \
+	libunwindstack \
 	libutils \
 	libvndksupport \
 
diff --git a/tests/wifitests/AndroidTest.xml b/tests/wifitests/AndroidTest.xml
index 4fe21aa..61a9233 100644
--- a/tests/wifitests/AndroidTest.xml
+++ b/tests/wifitests/AndroidTest.xml
@@ -22,6 +22,6 @@
     <option name="test-tag" value="FrameworksWifiTests" />
     <test class="com.android.tradefed.testtype.InstrumentationTest" >
         <option name="package" value="com.android.server.wifi.test" />
-        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+        <option name="runner" value="com.android.server.wifi.CustomTestRunner" />
     </test>
 </configuration>
diff --git a/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java b/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
index 7a25e17..4290ada 100644
--- a/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
@@ -51,10 +51,13 @@
 import android.hardware.wifi.V1_0.WifiStatusCode;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.Handler;
 import android.os.IHwBinder;
 import android.os.test.TestLooper;
 import android.util.Log;
 
+import com.android.server.wifi.HalDeviceManager.InterfaceDestroyedListener;
+
 import org.hamcrest.core.IsNull;
 import org.junit.After;
 import org.junit.Before;
@@ -82,7 +85,9 @@
     @Mock IServiceManager mServiceManagerMock;
     @Mock IWifi mWifiMock;
     @Mock HalDeviceManager.ManagerStatusListener mManagerStatusListenerMock;
+    @Mock private Clock mClock;
     private TestLooper mTestLooper;
+    private Handler mHandler;
     private ArgumentCaptor<IHwBinder.DeathRecipient> mDeathRecipientCaptor =
             ArgumentCaptor.forClass(IHwBinder.DeathRecipient.class);
     private ArgumentCaptor<IServiceNotification.Stub> mServiceNotificationCaptor =
@@ -95,6 +100,10 @@
     private WifiStatus mStatusFail;
 
     private class HalDeviceManagerSpy extends HalDeviceManager {
+        HalDeviceManagerSpy() {
+            super(mClock);
+        }
+
         @Override
         protected IWifi getWifiServiceMockable() {
             return mWifiMock;
@@ -111,6 +120,7 @@
         MockitoAnnotations.initMocks(this);
 
         mTestLooper = new TestLooper();
+        mHandler = new Handler(mTestLooper.getLooper());
 
         // initialize dummy status objects
         mStatusOk = getStatus(WifiStatusCode.SUCCESS);
@@ -141,6 +151,10 @@
         dumpDut("after: ");
     }
 
+    //////////////////////////////////////////////////////////////////////////////////////
+    // Chip Independent Tests
+    //////////////////////////////////////////////////////////////////////////////////////
+
     /**
      * Test basic startup flow:
      * - IServiceManager registrations
@@ -196,9 +210,9 @@
                 HalDeviceManager.ManagerStatusListener.class);
         HalDeviceManager.ManagerStatusListener callback2 = mock(
                 HalDeviceManager.ManagerStatusListener.class);
-        mDut.registerStatusListener(callback2, mTestLooper.getLooper());
-        mDut.registerStatusListener(callback1, mTestLooper.getLooper());
-        mDut.registerStatusListener(callback2, mTestLooper.getLooper());
+        mDut.registerStatusListener(callback2, mHandler);
+        mDut.registerStatusListener(callback1, mHandler);
+        mDut.registerStatusListener(callback2, mHandler);
 
         // startup
         executeAndValidateStartupSequence();
@@ -274,555 +288,28 @@
     }
 
     /**
-     * Validate creation of STA interface from blank start-up. The remove interface.
-     */
-    @Test
-    public void testCreateStaInterfaceNoInitMode() throws Exception {
-        final String name = "sta0";
-
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        HalDeviceManager.InterfaceDestroyedListener idl = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        IWifiStaIface iface = (IWifiStaIface) validateInterfaceSequence(chipMock,
-                false, // chipModeValid
-                -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
-                name, // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                idl, // destroyedListener
-                iafrl // availableListener
-        );
-        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
-
-        // act: remove interface
-        mDut.removeIface(iface);
-        mTestLooper.dispatchAll();
-
-        // verify: callback triggered
-        mInOrder.verify(chipMock.chip).removeStaIface(name);
-        verify(idl).onDestroyed();
-        verify(iafrl).onAvailableForRequest();
-
-        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
-    }
-
-    /**
-     * Validate creation of AP interface from blank start-up. The remove interface.
-     */
-    @Test
-    public void testCreateApInterfaceNoInitMode() throws Exception {
-        final String name = "ap0";
-
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        HalDeviceManager.InterfaceDestroyedListener idl = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
-                false, // chipModeValid
-                -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.AP, // ifaceTypeToCreate
-                name, // ifaceName
-                BaselineChip.AP_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                idl, // destroyedListener
-                iafrl // availableListener
-        );
-        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
-
-        // act: remove interface
-        mDut.removeIface(iface);
-        mTestLooper.dispatchAll();
-
-        // verify: callback triggered
-        mInOrder.verify(chipMock.chip).removeApIface(name);
-        verify(idl).onDestroyed();
-        verify(iafrl).onAvailableForRequest();
-
-        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
-    }
-
-    /**
-     * Validate creation of P2P interface from blank start-up. The remove interface.
-     */
-    @Test
-    public void testCreateP2pInterfaceNoInitMode() throws Exception {
-        final String name = "p2p0";
-
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        HalDeviceManager.InterfaceDestroyedListener idl = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        IWifiP2pIface iface = (IWifiP2pIface) validateInterfaceSequence(chipMock,
-                false, // chipModeValid
-                -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.P2P, // ifaceTypeToCreate
-                name, // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                idl, // destroyedListener
-                iafrl // availableListener
-        );
-        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
-
-        // act: remove interface
-        mDut.removeIface(iface);
-        mTestLooper.dispatchAll();
-
-        // verify: callback triggered
-        mInOrder.verify(chipMock.chip).removeP2pIface(name);
-        verify(idl).onDestroyed();
-        verify(iafrl).onAvailableForRequest();
-
-        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
-    }
-
-    /**
-     * Validate creation of NAN interface from blank start-up. The remove interface.
-     */
-    @Test
-    public void testCreateNanInterfaceNoInitMode() throws Exception {
-        final String name = "nan0";
-
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        HalDeviceManager.InterfaceDestroyedListener idl = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        IWifiNanIface iface = (IWifiNanIface) validateInterfaceSequence(chipMock,
-                false, // chipModeValid
-                -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.NAN, // ifaceTypeToCreate
-                name, // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                idl, // destroyedListener
-                iafrl // availableListener
-        );
-        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
-
-        // act: remove interface
-        mDut.removeIface(iface);
-        mTestLooper.dispatchAll();
-
-        // verify: callback triggered
-        mInOrder.verify(chipMock.chip).removeNanIface(name);
-        verify(idl).onDestroyed();
-        verify(iafrl).onAvailableForRequest();
-
-        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
-    }
-
-    /**
-     * Validate creation of AP interface when in STA mode - but with no interface created. Expect
-     * a change in chip mode.
-     */
-    @Test
-    public void testCreateApWithStaModeUp() throws Exception {
-        final String name = "ap0";
-
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        HalDeviceManager.InterfaceDestroyedListener idl = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.AP, // ifaceTypeToCreate
-                name, // ifaceName
-                BaselineChip.AP_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                idl, // destroyedListener
-                iafrl // availableListener
-        );
-        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
-
-        // act: stop Wi-Fi
-        mDut.stop();
-        mTestLooper.dispatchAll();
-
-        // verify: callback triggered
-        verify(idl).onDestroyed();
-        verify(mManagerStatusListenerMock, times(2)).onStatusChanged();
-
-        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
-    }
-
-    /**
-     * Validate creation of AP interface when in AP mode - but with no interface created. Expect
-     * no change in chip mode.
-     */
-    @Test
-    public void testCreateApWithApModeUp() throws Exception {
-        final String name = "ap0";
-
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        HalDeviceManager.InterfaceDestroyedListener idl = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                BaselineChip.AP_CHIP_MODE_ID, // chipModeId
-                IfaceType.AP, // ifaceTypeToCreate
-                name, // ifaceName
-                BaselineChip.AP_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                idl, // destroyedListener
-                iafrl // availableListener
-        );
-        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
-
-        // act: stop Wi-Fi
-        mDut.stop();
-        mTestLooper.dispatchAll();
-
-        // verify: callback triggered
-        verify(idl).onDestroyed();
-        verify(mManagerStatusListenerMock, times(2)).onStatusChanged();
-
-        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
-    }
-
-    /**
-     * Validate AP up/down creation of AP interface when a STA already created. Expect:
-     * - STA created
-     * - P2P created
-     * - When AP requested:
-     *   - STA & P2P torn down
-     *   - AP created
-     * - P2P creation refused
-     * - Request STA: will tear down AP
-     * - When AP destroyed:
-     *   - Get p2p available listener callback
-     *   - Can create P2P when requested
-     * - Create P2P
-     * - Request NAN: will get refused
-     * - Tear down P2P:
-     *    - should get nan available listener callback
-     *    - Can create NAN when requested
-     */
-    @Test
-    public void testCreateSameAndDiffPriorities() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener2 = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-
-        HalDeviceManager.InterfaceDestroyedListener apDestroyedListener = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener apAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        HalDeviceManager.InterfaceDestroyedListener p2pDestroyedListener = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        HalDeviceManager.InterfaceDestroyedListener p2pDestroyedListener2 = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-
-        HalDeviceManager.InterfaceDestroyedListener nanDestroyedListener = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        // Request STA
-        IWifiIface staIface = validateInterfaceSequence(chipMock,
-                false, // chipModeValid
-                -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
-                "sta0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                staDestroyedListener, // destroyedListener
-                staAvailListener // availableListener
-        );
-        collector.checkThat("allocated STA interface", staIface, IsNull.notNullValue());
-
-        // register additional InterfaceDestroyedListeners - including a duplicate (verify that
-        // only called once!)
-        mDut.registerDestroyedListener(staIface, staDestroyedListener2, mTestLooper.getLooper());
-        mDut.registerDestroyedListener(staIface, staDestroyedListener, mTestLooper.getLooper());
-
-        // Request P2P
-        IWifiIface p2pIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.P2P, // ifaceTypeToCreate
-                "p2p0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                p2pDestroyedListener, // destroyedListener
-                p2pAvailListener // availableListener
-        );
-        collector.checkThat("allocated P2P interface", p2pIface, IsNull.notNullValue());
-
-        // Request AP
-        IWifiIface apIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.AP, // ifaceTypeToCreate
-                "ap0", // ifaceName
-                BaselineChip.AP_CHIP_MODE_ID, // finalChipMode
-                new IWifiIface[]{staIface, p2pIface}, // tearDownList
-                apDestroyedListener, // destroyedListener
-                apAvailListener, // availableListener
-                // destroyedInterfacesDestroyedListeners...
-                staDestroyedListener, staDestroyedListener2, p2pDestroyedListener
-        );
-        collector.checkThat("allocated AP interface", apIface, IsNull.notNullValue());
-
-        // Request P2P: expect failure
-        p2pIface = mDut.createP2pIface(p2pDestroyedListener, mTestLooper.getLooper());
-        collector.checkThat("P2P can't be created", p2pIface, IsNull.nullValue());
-
-        // Request STA: expect success
-        staIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                BaselineChip.AP_CHIP_MODE_ID, // chipModeId
-                IfaceType.STA, // ifaceTypeToCreate
-                "sta0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                staDestroyedListener, // destroyedListener
-                staAvailListener, // availableListener
-                apDestroyedListener // destroyedInterfacesDestroyedListeners...
-        );
-        collector.checkThat("allocated STA interface", staIface, IsNull.notNullValue());
-
-        mTestLooper.dispatchAll();
-        verify(apDestroyedListener).onDestroyed();
-
-        // Request P2P: expect success now
-        p2pIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.P2P, // ifaceTypeToCreate
-                "p2p0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                p2pDestroyedListener2, // destroyedListener
-                p2pAvailListener // availableListener
-        );
-
-        // Request NAN: should fail
-        IWifiIface nanIface = mDut.createNanIface(nanDestroyedListener, mTestLooper.getLooper());
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
-                mTestLooper.getLooper());
-        collector.checkThat("NAN can't be created", nanIface, IsNull.nullValue());
-
-        // Tear down P2P
-        mDut.removeIface(p2pIface);
-        mTestLooper.dispatchAll();
-
-        verify(chipMock.chip, times(2)).removeP2pIface("p2p0");
-        verify(p2pDestroyedListener2).onDestroyed();
-
-        // Should now be able to request and get NAN
-        nanIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
-                "nan0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                nanDestroyedListener, // destroyedListener
-                nanAvailListener // availableListener
-        );
-        collector.checkThat("allocated NAN interface", nanIface, IsNull.notNullValue());
-
-        // available callback verification
-        verify(staAvailListener).onAvailableForRequest();
-        verify(apAvailListener, times(4)).onAvailableForRequest();
-        verify(p2pAvailListener, times(3)).onAvailableForRequest();
-        verify(nanAvailListener).onAvailableForRequest();
-
-        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
-                staDestroyedListener2, apDestroyedListener, apAvailListener, p2pDestroyedListener,
-                nanDestroyedListener, nanAvailListener, p2pDestroyedListener2);
-    }
-
-    /**
-     * Validate P2P and NAN interactions. Expect:
-     * - STA created
-     * - NAN created
-     * - When P2P requested:
-     *   - NAN torn down
-     *   - P2P created
-     * - NAN creation refused
-     * - When P2P destroyed:
-     *   - get nan available listener
-     *   - Can create NAN when requested
-     */
-    @Test
-    public void testP2pAndNanInteractions() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        HalDeviceManager.InterfaceDestroyedListener nanDestroyedListener = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        HalDeviceManager.InterfaceDestroyedListener p2pDestroyedListener = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = null;
-
-        // Request STA
-        IWifiIface staIface = validateInterfaceSequence(chipMock,
-                false, // chipModeValid
-                -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
-                "sta0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                staDestroyedListener, // destroyedListener
-                staAvailListener // availableListener
-        );
-
-        // Request NAN
-        IWifiIface nanIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
-                "nan0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                nanDestroyedListener, // destroyedListener
-                nanAvailListener // availableListener
-        );
-
-        // Request P2P
-        IWifiIface p2pIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.P2P, // ifaceTypeToCreate
-                "p2p0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                new IWifiIface[]{nanIface}, // tearDownList
-                p2pDestroyedListener, // destroyedListener
-                p2pAvailListener, // availableListener
-                nanDestroyedListener // destroyedInterfacesDestroyedListeners...
-        );
-
-        // Request NAN: expect failure
-        nanIface = mDut.createNanIface(nanDestroyedListener, mTestLooper.getLooper());
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
-                mTestLooper.getLooper());
-        collector.checkThat("NAN can't be created", nanIface, IsNull.nullValue());
-
-        // Destroy P2P interface
-        boolean status = mDut.removeIface(p2pIface);
-        mInOrder.verify(chipMock.chip).removeP2pIface("p2p0");
-        collector.checkThat("P2P removal success", status, equalTo(true));
-
-        mTestLooper.dispatchAll();
-        verify(p2pDestroyedListener).onDestroyed();
-        verify(nanAvailListener).onAvailableForRequest();
-
-        // Request NAN: expect success now
-        nanIface = validateInterfaceSequence(chipMock,
-                true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
-                IfaceType.NAN, // ifaceTypeToCreate
-                "nan0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                nanDestroyedListener, // destroyedListener
-                nanAvailListener // availableListener
-        );
-
-        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
-                nanDestroyedListener, nanAvailListener, p2pDestroyedListener);
-    }
-
-    /**
      * Validates that when (for some reason) the cache is out-of-sync with the actual chip status
      * then Wi-Fi is shut-down.
+     *
+     * Uses TestChipV1 - but nothing specific to its configuration. The test validates internal
+     * HDM behavior.
      */
     @Test
     public void testCacheMismatchError() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
 
-        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
+        InterfaceDestroyedListener staDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
         HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
                 HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
-        HalDeviceManager.InterfaceDestroyedListener nanDestroyedListener = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
+        InterfaceDestroyedListener nanDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
         HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
                 HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
@@ -832,7 +319,7 @@
                 -1000, // chipModeId (only used if chipModeValid is true)
                 IfaceType.STA, // ifaceTypeToCreate
                 "sta0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 staDestroyedListener, // destroyedListener
                 staAvailListener // availableListener
@@ -841,10 +328,10 @@
         // Request NAN
         IWifiIface nanIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
                 IfaceType.NAN, // ifaceTypeToCreate
                 "nan0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 nanDestroyedListener, // destroyedListener
                 nanAvailListener // availableListener
@@ -854,74 +341,35 @@
         chipMock.interfaceNames.get(IfaceType.STA).remove("sta0");
 
         // now try to request another NAN
-        nanIface = mDut.createNanIface(nanDestroyedListener, mTestLooper.getLooper());
+        IWifiIface nanIface2 = mDut.createNanIface(nanDestroyedListener, mHandler);
         mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
-                mTestLooper.getLooper());
-        collector.checkThat("NAN can't be created", nanIface, IsNull.nullValue());
+                mHandler);
+        collector.checkThat("NAN can't be created", nanIface2, IsNull.nullValue());
 
         // verify that Wi-Fi is shut-down: should also get all onDestroyed messages that are
         // registered (even if they seem out-of-sync to chip)
         mTestLooper.dispatchAll();
         verify(mWifiMock, times(2)).stop();
         verify(mManagerStatusListenerMock, times(2)).onStatusChanged();
-        verify(staDestroyedListener).onDestroyed();
-        verify(nanDestroyedListener).onDestroyed();
+        verify(staDestroyedListener).onDestroyed(getName(staIface));
+        verify(nanDestroyedListener).onDestroyed(getName(nanIface));
 
         verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
                 nanDestroyedListener, nanAvailListener);
     }
 
     /**
-     * Validates that trying to allocate a STA and then another STA fails. Only one STA at a time
-     * is permitted (by baseline chip).
-     */
-    @Test
-    public void testDuplicateStaRequests() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener1 = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener1 = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener2 = mock(
-                HalDeviceManager.InterfaceDestroyedListener.class);
-
-        // get STA interface
-        IWifiIface staIface1 = validateInterfaceSequence(chipMock,
-                false, // chipModeValid
-                -1000, // chipModeId (only used if chipModeValid is true)
-                IfaceType.STA, // ifaceTypeToCreate
-                "sta0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
-                null, // tearDownList
-                staDestroyedListener1, // destroyedListener
-                staAvailListener1 // availableListener
-        );
-        collector.checkThat("STA created", staIface1, IsNull.notNullValue());
-
-        // get STA interface again
-        IWifiIface staIface2 = mDut.createStaIface(staDestroyedListener2, mTestLooper.getLooper());
-        collector.checkThat("STA created", staIface2, IsNull.nullValue());
-
-        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener1,
-                staAvailListener1, staDestroyedListener2);
-    }
-
-    /**
      * Validates that a duplicate registration of the same InterfaceAvailableForRequestListener
      * listener will result in a single callback.
      *
      * Also validates that get an immediate call on registration if available.
+     *
+     * Uses TestChipV1 - but nothing specific to its configuration. The test validates internal
+     * HDM behavior.
      */
     @Test
     public void testDuplicateAvailableRegistrations() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
                 mManagerStatusListenerMock);
@@ -937,7 +385,7 @@
                 -1000, // chipModeId (only used if chipModeValid is true)
                 IfaceType.STA, // ifaceTypeToCreate
                 "sta0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 null, // destroyedListener
                 null // availableListener
@@ -946,9 +394,9 @@
 
         // act: register the same listener twice
         mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
-                mTestLooper.getLooper());
+                mHandler);
         mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
-                mTestLooper.getLooper());
+                mHandler);
         mTestLooper.dispatchAll();
 
         // remove STA interface -> should trigger callbacks
@@ -962,56 +410,6 @@
     }
 
     /**
-     * Validate that the getSupportedIfaceTypes API works when requesting for all chips.
-     */
-    @Test
-    public void testGetSupportedIfaceTypesAll() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        // try API
-        Set<Integer> results = mDut.getSupportedIfaceTypes();
-
-        // verify results
-        Set<Integer> correctResults = new HashSet<>();
-        correctResults.add(IfaceType.AP);
-        correctResults.add(IfaceType.STA);
-        correctResults.add(IfaceType.P2P);
-        correctResults.add(IfaceType.NAN);
-
-        assertEquals(correctResults, results);
-    }
-
-    /**
-     * Validate that the getSupportedIfaceTypes API works when requesting for a specific chip.
-     */
-    @Test
-    public void testGetSupportedIfaceTypesOneChip() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        // try API
-        Set<Integer> results = mDut.getSupportedIfaceTypes(chipMock.chip);
-
-        // verify results
-        Set<Integer> correctResults = new HashSet<>();
-        correctResults.add(IfaceType.AP);
-        correctResults.add(IfaceType.STA);
-        correctResults.add(IfaceType.P2P);
-        correctResults.add(IfaceType.NAN);
-
-        assertEquals(correctResults, results);
-    }
-
-    /**
      * Validate that when no chip info is found an empty list is returned.
      */
     @Test
@@ -1025,15 +423,18 @@
 
     /**
      * Test start HAL can retry upon failure.
+     *
+     * Uses TestChipV1 - but nothing specific to its configuration. The test validates internal
+     * HDM behavior.
      */
     @Test
     public void testStartHalRetryUponNotAvailableFailure() throws Exception {
         // Override the stubbing for mWifiMock in before().
         when(mWifiMock.start())
-            .thenReturn(getStatus(WifiStatusCode.ERROR_NOT_AVAILABLE))
-            .thenReturn(mStatusOk);
+                .thenReturn(getStatus(WifiStatusCode.ERROR_NOT_AVAILABLE))
+                .thenReturn(mStatusOk);
 
-        BaselineChip chipMock = new BaselineChip();
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
                 mManagerStatusListenerMock);
@@ -1043,13 +444,16 @@
 
     /**
      * Test start HAL fails after multiple retry failures.
+     *
+     * Uses TestChipV1 - but nothing specific to its configuration. The test validates internal
+     * HDM behavior.
      */
     @Test
     public void testStartHalRetryFailUponMultipleNotAvailableFailures() throws Exception {
         // Override the stubbing for mWifiMock in before().
         when(mWifiMock.start()).thenReturn(getStatus(WifiStatusCode.ERROR_NOT_AVAILABLE));
 
-        BaselineChip chipMock = new BaselineChip();
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip);
         executeAndValidateInitializationSequence();
@@ -1058,13 +462,16 @@
 
     /**
      * Test start HAL fails after multiple retry failures.
+     *
+     * Uses TestChipV1 - but nothing specific to its configuration. The test validates internal
+     * HDM behavior.
      */
     @Test
     public void testStartHalRetryFailUponTrueFailure() throws Exception {
         // Override the stubbing for mWifiMock in before().
         when(mWifiMock.start()).thenReturn(getStatus(WifiStatusCode.ERROR_UNKNOWN));
 
-        BaselineChip chipMock = new BaselineChip();
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip);
         executeAndValidateInitializationSequence();
@@ -1096,7 +503,1168 @@
         assertFalse(mDut.isSupported());
     }
 
+    //////////////////////////////////////////////////////////////////////////////////////
+    // Chip Specific Tests - but should work on all chips!
+    // (i.e. add copies for each test chip)
+    //////////////////////////////////////////////////////////////////////////////////////
+
+    // TestChipV1
+
+    /**
+     * Validate creation of STA interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateStaInterfaceNoInitModeTestChipV1() throws Exception {
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), IfaceType.STA, "sta0",
+                TestChipV1.STA_CHIP_MODE_ID, 1);
+    }
+
+    /**
+     * Validate creation of AP interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateApInterfaceNoInitModeTestChipV1() throws Exception {
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), IfaceType.AP, "ap0",
+                TestChipV1.AP_CHIP_MODE_ID, 1);
+    }
+
+    /**
+     * Validate creation of P2P interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateP2pInterfaceNoInitModeTestChipV1() throws Exception {
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), IfaceType.P2P, "p2p0",
+                TestChipV1.STA_CHIP_MODE_ID, 1);
+    }
+
+    /**
+     * Validate creation of NAN interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateNanInterfaceNoInitModeTestChipV1() throws Exception {
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV1(), IfaceType.NAN, "nan0",
+                TestChipV1.STA_CHIP_MODE_ID, 1);
+    }
+
+    // TestChipV2
+
+    /**
+     * Validate creation of STA interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateStaInterfaceNoInitModeTestChipV2() throws Exception {
+        // Note: we expected 2 available callbacks since we now have 2 STAs possible. So
+        // we get callback 1 after creating the first STA (since we can create another STA),
+        // and we get callback 2 after destroying the first STA (since we can create another STA -
+        // as expected).
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV2(), IfaceType.STA, "sta0",
+                TestChipV2.CHIP_MODE_ID, 2);
+    }
+
+    /**
+     * Validate creation of AP interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateApInterfaceNoInitModeTestChipV2() throws Exception {
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV2(), IfaceType.AP, "ap0",
+                TestChipV2.CHIP_MODE_ID, 1);
+    }
+
+    /**
+     * Validate creation of P2P interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateP2pInterfaceNoInitModeTestChipV2() throws Exception {
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV2(), IfaceType.P2P, "p2p0",
+                TestChipV2.CHIP_MODE_ID, 1);
+    }
+
+    /**
+     * Validate creation of NAN interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateNanInterfaceNoInitModeTestChipV2() throws Exception {
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV2(), IfaceType.NAN, "nan0",
+                TestChipV2.CHIP_MODE_ID, 1);
+    }
+
+    // TestChipV3
+
+    /**
+     * Validate creation of STA interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateStaInterfaceNoInitModeTestChipV3() throws Exception {
+        // Note: we expected 2 available callbacks since we now have 2 STAs possible. So
+        // we get callback 1 after creating the first STA (since we can create another STA),
+        // and we get callback 2 after destroying the first STA (since we can create another STA -
+        // as expected).
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV3(), IfaceType.STA, "sta0",
+                TestChipV3.CHIP_MODE_ID, 2);
+    }
+
+    /**
+     * Validate creation of AP interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateApInterfaceNoInitModeTestChipV3() throws Exception {
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV3(), IfaceType.AP, "ap0",
+                TestChipV3.CHIP_MODE_ID, 1);
+    }
+
+    /**
+     * Validate creation of P2P interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateP2pInterfaceNoInitModeTestChipV3() throws Exception {
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV3(), IfaceType.P2P, "p2p0",
+                TestChipV3.CHIP_MODE_ID, 1);
+    }
+
+    /**
+     * Validate creation of NAN interface from blank start-up. The remove interface.
+     */
+    @Test
+    public void testCreateNanInterfaceNoInitModeTestChipV3() throws Exception {
+        runCreateSingleXxxInterfaceNoInitMode(new TestChipV3(), IfaceType.NAN, "nan0",
+                TestChipV3.CHIP_MODE_ID, 1);
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////
+    // TestChipV1 Specific Tests
+    //////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Validate creation of AP interface when in STA mode - but with no interface created. Expect
+     * a change in chip mode.
+     */
+    @Test
+    public void testCreateApWithStaModeUpTestChipV1() throws Exception {
+        final String name = "ap0";
+
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        InterfaceDestroyedListener idl = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.AP, // ifaceTypeToCreate
+                name, // ifaceName
+                TestChipV1.AP_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                idl, // destroyedListener
+                iafrl // availableListener
+        );
+        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
+
+        // act: stop Wi-Fi
+        mDut.stop();
+        mTestLooper.dispatchAll();
+
+        // verify: callback triggered
+        verify(idl).onDestroyed(getName(iface));
+        verify(mManagerStatusListenerMock, times(2)).onStatusChanged();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+    }
+
+    /**
+     * Validate creation of AP interface when in STA mode with a single STA iface created.
+     * Expect a change in chip mode.
+     */
+    @Test
+    public void testCreateApWithStIfaceUpTestChipV1UsingNoHandlerListeners() throws Exception {
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+
+        InterfaceDestroyedListener staIdl = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener staIafrl = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+        InterfaceDestroyedListener apIdl = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener apIafrl = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock, staIdl, staIafrl, apIdl, apIafrl);
+        executeAndValidateInitializationSequence();
+
+        // Register listener & start Wi-Fi
+        mDut.registerStatusListener(mManagerStatusListenerMock, null);
+        assertTrue(mDut.start());
+        mInOrder.verify(mManagerStatusListenerMock).onStatusChanged();
+
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staIafrl, null);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.AP, apIafrl, null);
+
+        mInOrder.verify(staIafrl).onAvailableForRequest();
+        mInOrder.verify(apIafrl).onAvailableForRequest();
+
+        // Create STA Iface first.
+        IWifiStaIface staIface = mock(IWifiStaIface.class);
+        doAnswer(new GetNameAnswer("wlan0")).when(staIface).getName(
+                any(IWifiIface.getNameCallback.class));
+        doAnswer(new GetTypeAnswer(IfaceType.STA)).when(staIface).getType(
+                any(IWifiIface.getTypeCallback.class));
+        doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, staIface)).when(
+                chipMock.chip).createStaIface(any(IWifiChip.createStaIfaceCallback.class));
+        assertEquals(staIface, mDut.createStaIface(staIdl, null));
+
+        mInOrder.verify(chipMock.chip).configureChip(TestChipV1.STA_CHIP_MODE_ID);
+        mInOrder.verify(apIafrl).onAvailableForRequest();
+
+        // Now Create AP Iface.
+        IWifiApIface apIface = mock(IWifiApIface.class);
+        doAnswer(new GetNameAnswer("wlan0")).when(apIface).getName(
+                any(IWifiIface.getNameCallback.class));
+        doAnswer(new GetTypeAnswer(IfaceType.AP)).when(apIface).getType(
+                any(IWifiIface.getTypeCallback.class));
+        doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, apIface)).when(
+                chipMock.chip).createApIface(any(IWifiChip.createApIfaceCallback.class));
+        assertEquals(apIface, mDut.createApIface(apIdl, null));
+
+        mInOrder.verify(chipMock.chip).removeStaIface(getName(staIface));
+        mInOrder.verify(staIdl).onDestroyed(getName(staIface));
+        mInOrder.verify(chipMock.chip).configureChip(TestChipV1.AP_CHIP_MODE_ID);
+        mInOrder.verify(staIafrl).onAvailableForRequest();
+
+        // Stop Wi-Fi
+        mDut.stop();
+
+        mInOrder.verify(mWifiMock).stop();
+        mInOrder.verify(mManagerStatusListenerMock).onStatusChanged();
+        mInOrder.verify(apIdl).onDestroyed(getName(apIface));
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staIdl, staIafrl, apIdl, apIafrl);
+    }
+
+    /**
+     * Validate creation of AP interface when in AP mode - but with no interface created. Expect
+     * no change in chip mode.
+     */
+    @Test
+    public void testCreateApWithApModeUpTestChipV1() throws Exception {
+        final String name = "ap0";
+
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        InterfaceDestroyedListener idl = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV1.AP_CHIP_MODE_ID, // chipModeId
+                IfaceType.AP, // ifaceTypeToCreate
+                name, // ifaceName
+                TestChipV1.AP_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                idl, // destroyedListener
+                iafrl // availableListener
+        );
+        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
+
+        // act: stop Wi-Fi
+        mDut.stop();
+        mTestLooper.dispatchAll();
+
+        // verify: callback triggered
+        verify(idl).onDestroyed(getName(iface));
+        verify(mManagerStatusListenerMock, times(2)).onStatusChanged();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+    }
+
+    /**
+     * Validate AP up/down creation of AP interface when a STA already created. Expect:
+     * - STA created
+     * - P2P created
+     * - When AP requested:
+     *   - STA & P2P torn down
+     *   - AP created
+     * - P2P creation refused
+     * - Request STA: will tear down AP
+     * - When AP destroyed:
+     *   - Get p2p available listener callback
+     *   - Can create P2P when requested
+     * - Create P2P
+     * - Request NAN: will get refused
+     * - Tear down P2P:
+     *    - should get nan available listener callback
+     *    - Can create NAN when requested
+     */
+    @Test
+    public void testCreateSameAndDiffPrioritiesTestChipV1() throws Exception {
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        InterfaceDestroyedListener staDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener staDestroyedListener2 = mock(
+                InterfaceDestroyedListener.class);
+
+        InterfaceDestroyedListener apDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener apAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener p2pDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener p2pDestroyedListener2 = mock(
+                InterfaceDestroyedListener.class);
+
+        InterfaceDestroyedListener nanDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InOrder inOrderStaAvail = inOrder(staAvailListener);
+        InOrder inOrderApAvail = inOrder(apAvailListener);
+        InOrder inOrderP2pAvail = inOrder(p2pAvailListener);
+        InOrder inOrderNanAvail = inOrder(nanAvailListener);
+
+        // register listeners for interface availability
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
+                mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.AP, apAvailListener, mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.P2P, p2pAvailListener,
+                mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
+                mHandler);
+        mTestLooper.dispatchAll();
+
+        inOrderStaAvail.verify(staAvailListener).onAvailableForRequest();
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        inOrderNanAvail.verify(nanAvailListener).onAvailableForRequest();
+
+        // Request STA
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener, // destroyedListener
+                null // availableListener
+        );
+        collector.checkThat("allocated STA interface", staIface, IsNull.notNullValue());
+
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        inOrderNanAvail.verify(nanAvailListener).onAvailableForRequest();
+
+        // request STA2: should fail
+        IWifiIface staIface2 = mDut.createStaIface(null, null);
+        collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
+
+        // register additional InterfaceDestroyedListeners - including a duplicate (verify that
+        // only called once!)
+        mDut.registerDestroyedListener(staIface, staDestroyedListener2, mHandler);
+        mDut.registerDestroyedListener(staIface, staDestroyedListener, mHandler);
+
+        // Request P2P
+        IWifiIface p2pIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.P2P, // ifaceTypeToCreate
+                "p2p0", // ifaceName
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                p2pDestroyedListener, // destroyedListener
+                null // availableListener
+        );
+        collector.checkThat("allocated P2P interface", p2pIface, IsNull.notNullValue());
+
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+
+        // Request AP
+        IWifiIface apIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.AP, // ifaceTypeToCreate
+                "ap0", // ifaceName
+                TestChipV1.AP_CHIP_MODE_ID, // finalChipMode
+                new IWifiIface[]{staIface, p2pIface}, // tearDownList
+                apDestroyedListener, // destroyedListener
+                null, // availableListener
+                // destroyedInterfacesDestroyedListeners...
+                new InterfaceDestroyedListenerWithIfaceName(
+                        getName(staIface), staDestroyedListener),
+                new InterfaceDestroyedListenerWithIfaceName(
+                        getName(staIface), staDestroyedListener2),
+                new InterfaceDestroyedListenerWithIfaceName(
+                        getName(p2pIface), p2pDestroyedListener)
+        );
+        collector.checkThat("allocated AP interface", apIface, IsNull.notNullValue());
+
+        inOrderStaAvail.verify(staAvailListener).onAvailableForRequest();
+
+        // request AP2: should fail
+        IWifiIface apIface2 = mDut.createApIface(null, null);
+        collector.checkThat("AP2 should not be created", apIface2, IsNull.nullValue());
+
+        // Request P2P: expect failure
+        p2pIface = mDut.createP2pIface(p2pDestroyedListener, mHandler);
+        collector.checkThat("P2P can't be created", p2pIface, IsNull.nullValue());
+
+        // Request STA: expect success
+        staIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV1.AP_CHIP_MODE_ID, // chipModeId
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener, // destroyedListener
+                null, // availableListener
+                // destroyedInterfacesDestroyedListeners...
+                new InterfaceDestroyedListenerWithIfaceName(
+                        getName(apIface), apDestroyedListener)
+        );
+        collector.checkThat("allocated STA interface", staIface, IsNull.notNullValue());
+
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        inOrderNanAvail.verify(nanAvailListener).onAvailableForRequest();
+
+        mTestLooper.dispatchAll();
+        verify(apDestroyedListener).onDestroyed(getName(apIface));
+
+        // Request P2P: expect success now
+        p2pIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.P2P, // ifaceTypeToCreate
+                "p2p0", // ifaceName
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                p2pDestroyedListener2, // destroyedListener
+                null // availableListener
+        );
+
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+
+        // Request NAN: should fail
+        IWifiIface nanIface = mDut.createNanIface(nanDestroyedListener, mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
+                mHandler);
+        collector.checkThat("NAN can't be created", nanIface, IsNull.nullValue());
+
+        // Tear down P2P
+        mDut.removeIface(p2pIface);
+        mTestLooper.dispatchAll();
+
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        inOrderNanAvail.verify(nanAvailListener).onAvailableForRequest();
+        verify(chipMock.chip, times(2)).removeP2pIface("p2p0");
+        verify(p2pDestroyedListener2).onDestroyed(getName(p2pIface));
+
+        // Should now be able to request and get NAN
+        nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
+                IfaceType.NAN, // ifaceTypeToCreate
+                "nan0", // ifaceName
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                nanAvailListener // availableListener
+        );
+        collector.checkThat("allocated NAN interface", nanIface, IsNull.notNullValue());
+
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
+                staDestroyedListener2, apDestroyedListener, apAvailListener, p2pDestroyedListener,
+                nanDestroyedListener, nanAvailListener, p2pDestroyedListener2);
+    }
+
+    /**
+     * Validate P2P and NAN interactions. Expect:
+     * - STA created
+     * - NAN created
+     * - When P2P requested:
+     *   - NAN torn down
+     *   - P2P created
+     * - NAN creation refused
+     * - When P2P destroyed:
+     *   - get nan available listener
+     *   - Can create NAN when requested
+     */
+    @Test
+    public void testP2pAndNanInteractionsTestChipV1() throws Exception {
+        // staAvailCallbacks=0: there is no second STA so will never get available callback after
+        // first is created.
+        runP2pAndNanExclusiveInteractionsTestChip(new TestChipV1(), 0, TestChipV1.STA_CHIP_MODE_ID);
+    }
+
+    /**
+     * Validates that trying to allocate a STA and then another STA fails. Only one STA at a time
+     * is permitted (by TestChipV1 chip).
+     */
+    @Test
+    public void testDuplicateStaRequestsTestChipV1() throws Exception {
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        InterfaceDestroyedListener staDestroyedListener1 = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener1 = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener staDestroyedListener2 = mock(
+                InterfaceDestroyedListener.class);
+
+        // get STA interface
+        IWifiIface staIface1 = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener1, // destroyedListener
+                staAvailListener1 // availableListener
+        );
+        collector.checkThat("STA created", staIface1, IsNull.notNullValue());
+
+        // get STA interface again
+        IWifiIface staIface2 = mDut.createStaIface(staDestroyedListener2, mHandler);
+        collector.checkThat("STA created", staIface2, IsNull.nullValue());
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener1,
+                staAvailListener1, staDestroyedListener2);
+    }
+
+    /**
+     * Validate that the getSupportedIfaceTypes API works when requesting for all chips.
+     */
+    @Test
+    public void testGetSupportedIfaceTypesAllTestChipV1() throws Exception {
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // try API
+        Set<Integer> results = mDut.getSupportedIfaceTypes();
+
+        // verify results
+        Set<Integer> correctResults = new HashSet<>();
+        correctResults.add(IfaceType.AP);
+        correctResults.add(IfaceType.STA);
+        correctResults.add(IfaceType.P2P);
+        correctResults.add(IfaceType.NAN);
+
+        assertEquals(correctResults, results);
+    }
+
+    /**
+     * Validate that the getSupportedIfaceTypes API works when requesting for a specific chip.
+     */
+    @Test
+    public void testGetSupportedIfaceTypesOneChipTestChipV1() throws Exception {
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // try API
+        Set<Integer> results = mDut.getSupportedIfaceTypes(chipMock.chip);
+
+        // verify results
+        Set<Integer> correctResults = new HashSet<>();
+        correctResults.add(IfaceType.AP);
+        correctResults.add(IfaceType.STA);
+        correctResults.add(IfaceType.P2P);
+        correctResults.add(IfaceType.NAN);
+
+        assertEquals(correctResults, results);
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////
+    // TestChipV2 Specific Tests
+    //////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Validate a flow sequence for test chip 2:
+     * - create STA
+     * - create P2P
+     * - request NAN: failure
+     * - create AP
+     * - create STA: will get refused
+     * - create AP: will get refused
+     * - tear down AP
+     * - create STA
+     * - create STA: will get refused
+     * - create AP: should get created and the last created STA should get destroyed
+     * - tear down P2P
+     * - create NAN
+     */
+    @Test
+    public void testInterfaceCreationFlowTestChipV2() throws Exception {
+        TestChipV2 chipMock = new TestChipV2();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        InterfaceDestroyedListener staDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        InterfaceDestroyedListener staDestroyedListener2 = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener apDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener apAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener p2pDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener nanDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InOrder inOrderStaAvail = inOrder(staAvailListener);
+        InOrder inOrderApAvail = inOrder(apAvailListener);
+        InOrder inOrderP2pAvail = inOrder(p2pAvailListener);
+        InOrder inOrderNanAvail = inOrder(nanAvailListener);
+
+        // register listeners for interface availability
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
+                mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.AP, apAvailListener, mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.P2P, p2pAvailListener,
+                mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
+                mHandler);
+        mTestLooper.dispatchAll();
+
+        inOrderStaAvail.verify(staAvailListener).onAvailableForRequest();
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        inOrderNanAvail.verify(nanAvailListener).onAvailableForRequest();
+
+        // create STA
+        when(mClock.getUptimeSinceBootMillis()).thenReturn(15L);
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                TestChipV2.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener, // destroyedListener
+                null // availableListener (already registered)
+        );
+        collector.checkThat("STA interface wasn't created", staIface, IsNull.notNullValue());
+
+        inOrderStaAvail.verify(staAvailListener).onAvailableForRequest();
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        inOrderNanAvail.verify(nanAvailListener).onAvailableForRequest();
+
+        // create P2P
+        IWifiIface p2pIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV2.CHIP_MODE_ID, // chipModeId
+                IfaceType.P2P, // ifaceTypeToCreate
+                "p2p0", // ifaceName
+                TestChipV2.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                p2pDestroyedListener, // destroyedListener
+                null // availableListener (already registered)
+        );
+        collector.checkThat("P2P interface wasn't created", p2pIface, IsNull.notNullValue());
+
+        inOrderStaAvail.verify(staAvailListener).onAvailableForRequest();
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+
+        // request NAN: should fail
+        IWifiIface nanIface = mDut.createNanIface(null, null);
+        collector.checkThat("NAN should not be created", nanIface, IsNull.nullValue());
+
+        // create AP
+        IWifiIface apIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV2.CHIP_MODE_ID, // chipModeId
+                IfaceType.AP, // ifaceTypeToCreate
+                "ap0", // ifaceName
+                TestChipV2.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                apDestroyedListener, // destroyedListener
+                null // availableListener (already registered)
+        );
+        collector.checkThat("AP interface wasn't created", apIface, IsNull.notNullValue());
+
+        // request STA2: should fail
+        IWifiIface staIface2 = mDut.createStaIface(null, null);
+        collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
+
+        // request AP2: should fail
+        IWifiIface apIface2 = mDut.createApIface(null, null);
+        collector.checkThat("AP2 should not be created", apIface2, IsNull.nullValue());
+
+        // tear down AP
+        mDut.removeIface(apIface);
+        mTestLooper.dispatchAll();
+
+        inOrderStaAvail.verify(staAvailListener).onAvailableForRequest();
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        verify(chipMock.chip).removeApIface("ap0");
+        verify(apDestroyedListener).onDestroyed(getName(apIface));
+
+        // create STA2: using a later clock
+        when(mClock.getUptimeSinceBootMillis()).thenReturn(20L);
+        staIface2 = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV2.CHIP_MODE_ID, // chipModeId
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta1", // ifaceName
+                TestChipV2.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener2, // destroyedListener
+                null // availableListener (already registered)
+        );
+        collector.checkThat("STA 2 interface wasn't created", staIface2, IsNull.notNullValue());
+
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+
+        // request STA3: should fail
+        IWifiIface staIface3 = mDut.createStaIface(null, null);
+        collector.checkThat("STA3 should not be created", staIface3, IsNull.nullValue());
+
+        // create AP - this will destroy the last STA created, i.e. STA2
+        apIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV2.CHIP_MODE_ID, // chipModeId
+                IfaceType.AP, // ifaceTypeToCreate
+                "ap0", // ifaceName
+                TestChipV2.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                apDestroyedListener, // destroyedListener
+                null, // availableListener (already registered),
+                // destroyedInterfacesDestroyedListeners...
+                new InterfaceDestroyedListenerWithIfaceName(
+                        getName(staIface2), staDestroyedListener2)
+        );
+        collector.checkThat("AP interface wasn't created", apIface, IsNull.notNullValue());
+
+        // tear down P2P
+        mDut.removeIface(p2pIface);
+        mTestLooper.dispatchAll();
+
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        inOrderNanAvail.verify(nanAvailListener).onAvailableForRequest();
+        verify(chipMock.chip).removeP2pIface("p2p0");
+        verify(p2pDestroyedListener).onDestroyed(getName(p2pIface));
+
+        // create NAN
+        nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV2.CHIP_MODE_ID, // chipModeId
+                IfaceType.NAN, // ifaceTypeToCreate
+                "nan0", // ifaceName
+                TestChipV2.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                null // availableListener (already registered)
+        );
+        collector.checkThat("NAN interface wasn't created", nanIface, IsNull.notNullValue());
+
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener,
+                staDestroyedListener2, apDestroyedListener, p2pDestroyedListener,
+                nanDestroyedListener, staAvailListener, apAvailListener, p2pAvailListener,
+                nanAvailListener, staAvailListener, apAvailListener, p2pAvailListener,
+                nanAvailListener);
+    }
+
+    /**
+     * Validate P2P and NAN interactions. Expect:
+     * - STA created
+     * - NAN created
+     * - When P2P requested:
+     *   - NAN torn down
+     *   - P2P created
+     * - NAN creation refused
+     * - When P2P destroyed:
+     *   - get nan available listener
+     *   - Can create NAN when requested
+     */
+    @Test
+    public void testP2pAndNanInteractionsTestChipV2() throws Exception {
+        // staAvailCallbacks=5: after every substantial change will get a callback since second
+        // STA is always available.
+        runP2pAndNanExclusiveInteractionsTestChip(new TestChipV2(), 5, TestChipV2.CHIP_MODE_ID);
+    }
+
+    /**
+     * Validate that the getSupportedIfaceTypes API works when requesting for all chips.
+     */
+    @Test
+    public void testGetSupportedIfaceTypesAllTestChipV2() throws Exception {
+        TestChipV2 chipMock = new TestChipV2();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // try API
+        Set<Integer> results = mDut.getSupportedIfaceTypes();
+
+        // verify results
+        Set<Integer> correctResults = new HashSet<>();
+        correctResults.add(IfaceType.AP);
+        correctResults.add(IfaceType.STA);
+        correctResults.add(IfaceType.P2P);
+        correctResults.add(IfaceType.NAN);
+
+        assertEquals(correctResults, results);
+    }
+
+    /**
+     * Validate that the getSupportedIfaceTypes API works when requesting for a specific chip.
+     */
+    @Test
+    public void testGetSupportedIfaceTypesOneChipTestChipV2() throws Exception {
+        TestChipV2 chipMock = new TestChipV2();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // try API
+        Set<Integer> results = mDut.getSupportedIfaceTypes(chipMock.chip);
+
+        // verify results
+        Set<Integer> correctResults = new HashSet<>();
+        correctResults.add(IfaceType.AP);
+        correctResults.add(IfaceType.STA);
+        correctResults.add(IfaceType.P2P);
+        correctResults.add(IfaceType.NAN);
+
+        assertEquals(correctResults, results);
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////
+    // TestChipV3 Specific Tests
+    //////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Validate a flow sequence for test chip 3:
+     * - create STA
+     * - create P2P
+     * - request NAN: failure
+     * - create AP: should tear down P2P first
+     * - create STA: will get refused
+     * - create AP: will get refused
+     * - request P2P: failure
+     * - tear down AP
+     * - create STA
+     * - create STA: will get refused
+     * - create NAN: should tear down last created STA
+     * - create STA: will get refused
+     */
+    @Test
+    public void testInterfaceCreationFlowTestChipV3() throws Exception {
+        TestChipV3 chipMock = new TestChipV3();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        InterfaceDestroyedListener staDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        InterfaceDestroyedListener staDestroyedListener2 = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener apDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener apAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener p2pDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener nanDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InOrder inOrderStaAvail = inOrder(staAvailListener);
+        InOrder inOrderApAvail = inOrder(apAvailListener);
+        InOrder inOrderP2pAvail = inOrder(p2pAvailListener);
+        InOrder inOrderNanAvail = inOrder(nanAvailListener);
+
+        // register listeners for interface availability
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
+                mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.AP, apAvailListener, mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.P2P, p2pAvailListener,
+                mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
+                mHandler);
+        mTestLooper.dispatchAll();
+
+        inOrderStaAvail.verify(staAvailListener).onAvailableForRequest();
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        inOrderNanAvail.verify(nanAvailListener).onAvailableForRequest();
+
+        // create STA
+        when(mClock.getUptimeSinceBootMillis()).thenReturn(15L);
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                TestChipV3.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener, // destroyedListener
+                null // availableListener (already registered)
+        );
+        collector.checkThat("STA interface wasn't created", staIface, IsNull.notNullValue());
+
+        inOrderStaAvail.verify(staAvailListener).onAvailableForRequest();
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        inOrderNanAvail.verify(nanAvailListener).onAvailableForRequest();
+
+        // create P2P
+        IWifiIface p2pIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV3.CHIP_MODE_ID, // chipModeId
+                IfaceType.P2P, // ifaceTypeToCreate
+                "p2p0", // ifaceName
+                TestChipV3.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                p2pDestroyedListener, // destroyedListener
+                null // availableListener (already registered)
+        );
+        collector.checkThat("P2P interface wasn't created", p2pIface, IsNull.notNullValue());
+
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+
+        // request NAN: should fail
+        IWifiIface nanIface = mDut.createNanIface(null, null);
+        collector.checkThat("NAN should not be created", nanIface, IsNull.nullValue());
+
+        // create AP: will destroy P2P
+        IWifiIface apIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV3.CHIP_MODE_ID, // chipModeId
+                IfaceType.AP, // ifaceTypeToCreate
+                "ap0", // ifaceName
+                TestChipV3.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                apDestroyedListener, // destroyedListener
+                null, // availableListener (already registered)
+                new InterfaceDestroyedListenerWithIfaceName("p2p0", p2pDestroyedListener)
+        );
+        collector.checkThat("AP interface wasn't created", apIface, IsNull.notNullValue());
+        verify(chipMock.chip).removeP2pIface("p2p0");
+
+        // request STA2: should fail
+        IWifiIface staIface2 = mDut.createStaIface(null, null);
+        collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
+
+        // request AP2: should fail
+        IWifiIface apIface2 = mDut.createApIface(null, null);
+        collector.checkThat("AP2 should not be created", apIface2, IsNull.nullValue());
+
+        // request P2P: should fail
+        p2pIface = mDut.createP2pIface(null, null);
+        collector.checkThat("P2P should not be created", p2pIface, IsNull.nullValue());
+
+        // tear down AP
+        mDut.removeIface(apIface);
+        mTestLooper.dispatchAll();
+
+        inOrderStaAvail.verify(staAvailListener).onAvailableForRequest();
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        inOrderNanAvail.verify(nanAvailListener).onAvailableForRequest();
+        verify(chipMock.chip).removeApIface("ap0");
+        verify(apDestroyedListener).onDestroyed(getName(apIface));
+
+        // create STA2: using a later clock
+        when(mClock.getUptimeSinceBootMillis()).thenReturn(20L);
+        staIface2 = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV3.CHIP_MODE_ID, // chipModeId
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta1", // ifaceName
+                TestChipV3.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener2, // destroyedListener
+                null // availableListener (already registered)
+        );
+        collector.checkThat("STA 2 interface wasn't created", staIface2, IsNull.notNullValue());
+
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        inOrderNanAvail.verify(nanAvailListener).onAvailableForRequest();
+
+        // request STA3: should fail
+        IWifiIface staIface3 = mDut.createStaIface(null, null);
+        collector.checkThat("STA3 should not be created", staIface3, IsNull.nullValue());
+
+        // create NAN: should destroy the last created STA (STA2)
+        nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                TestChipV3.CHIP_MODE_ID, // chipModeId
+                IfaceType.NAN, // ifaceTypeToCreate
+                "nan0", // ifaceName
+                TestChipV3.CHIP_MODE_ID, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                null, // availableListener (already registered)
+                new InterfaceDestroyedListenerWithIfaceName(
+                        getName(staIface2), staDestroyedListener2)
+        );
+        collector.checkThat("NAN interface wasn't created", nanIface, IsNull.notNullValue());
+
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
+        verify(chipMock.chip).removeStaIface("sta1");
+        verify(staDestroyedListener2).onDestroyed(getName(staIface2));
+
+        // request STA2: should fail
+        staIface2 = mDut.createStaIface(null, null);
+        collector.checkThat("STA2 should not be created", staIface2, IsNull.nullValue());
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener,
+                staDestroyedListener2, apDestroyedListener, p2pDestroyedListener,
+                nanDestroyedListener, staAvailListener, apAvailListener, p2pAvailListener,
+                nanAvailListener, staAvailListener, apAvailListener, p2pAvailListener,
+                nanAvailListener);
+    }
+
+    /**
+     * Validate P2P and NAN interactions. Expect:
+     * - STA created
+     * - NAN created
+     * - When P2P requested:
+     *   - NAN torn down
+     *   - P2P created
+     * - NAN creation refused
+     * - When P2P destroyed:
+     *   - get nan available listener
+     *   - Can create NAN when requested
+     */
+    @Test
+    public void testP2pAndNanInteractionsTestChipV3() throws Exception {
+        // staAvailCallbacks=2: only get callback (for second STA) when P2P or NAN are down.
+        runP2pAndNanExclusiveInteractionsTestChip(new TestChipV3(), 2, TestChipV3.CHIP_MODE_ID);
+    }
+
+    /**
+     * Validate that the getSupportedIfaceTypes API works when requesting for all chips.
+     */
+    @Test
+    public void testGetSupportedIfaceTypesAllTestChipV3() throws Exception {
+        TestChipV3 chipMock = new TestChipV3();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // try API
+        Set<Integer> results = mDut.getSupportedIfaceTypes();
+
+        // verify results
+        Set<Integer> correctResults = new HashSet<>();
+        correctResults.add(IfaceType.AP);
+        correctResults.add(IfaceType.STA);
+        correctResults.add(IfaceType.P2P);
+        correctResults.add(IfaceType.NAN);
+
+        assertEquals(correctResults, results);
+    }
+
+    /**
+     * Validate that the getSupportedIfaceTypes API works when requesting for a specific chip.
+     */
+    @Test
+    public void testGetSupportedIfaceTypesOneChipTestChipV3() throws Exception {
+        TestChipV3 chipMock = new TestChipV3();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        // try API
+        Set<Integer> results = mDut.getSupportedIfaceTypes(chipMock.chip);
+
+        // verify results
+        Set<Integer> correctResults = new HashSet<>();
+        correctResults.add(IfaceType.AP);
+        correctResults.add(IfaceType.STA);
+        correctResults.add(IfaceType.P2P);
+        correctResults.add(IfaceType.NAN);
+
+        assertEquals(correctResults, results);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////
     // utilities
+    ///////////////////////////////////////////////////////////////////////////////////////
     private void dumpDut(String prefix) {
         StringWriter sw = new StringWriter();
         mDut.dump(null, new PrintWriter(sw), null);
@@ -1140,7 +1708,7 @@
     private void executeAndValidateStartupSequence(int numAttempts, boolean success)
             throws Exception {
         // act: register listener & start Wi-Fi
-        mDut.registerStatusListener(mManagerStatusListenerMock, mTestLooper.getLooper());
+        mDut.registerStatusListener(mManagerStatusListenerMock, mHandler);
         collector.checkThat(mDut.start(), equalTo(success));
 
         // verify
@@ -1156,12 +1724,175 @@
         }
     }
 
+    private void runCreateSingleXxxInterfaceNoInitMode(ChipMockBase chipMock, int ifaceTypeToCreate,
+            String ifaceName, int finalChipMode, int expectedAvailableCalls) throws Exception {
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        InterfaceDestroyedListener idl = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        IWifiIface iface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                ifaceTypeToCreate,
+                ifaceName,
+                finalChipMode,
+                null, // tearDownList
+                idl, // destroyedListener
+                iafrl // availableListener
+        );
+        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
+
+        // act: remove interface
+        mDut.removeIface(iface);
+        mTestLooper.dispatchAll();
+
+        // verify: callback triggered
+        switch (ifaceTypeToCreate) {
+            case IfaceType.STA:
+                mInOrder.verify(chipMock.chip).removeStaIface(ifaceName);
+                break;
+            case IfaceType.AP:
+                mInOrder.verify(chipMock.chip).removeApIface(ifaceName);
+                break;
+            case IfaceType.P2P:
+                mInOrder.verify(chipMock.chip).removeP2pIface(ifaceName);
+                break;
+            case IfaceType.NAN:
+                mInOrder.verify(chipMock.chip).removeNanIface(ifaceName);
+                break;
+        }
+
+        verify(idl).onDestroyed(ifaceName);
+        verify(iafrl, times(expectedAvailableCalls)).onAvailableForRequest();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+    }
+
+    /**
+     * Validate P2P and NAN interactions. Expect:
+     * - STA created
+     * - NAN created
+     * - When P2P requested:
+     *   - NAN torn down
+     *   - P2P created
+     * - NAN creation refused
+     * - When P2P destroyed:
+     *   - get nan available listener
+     *   - Can create NAN when requested
+     *
+     * Relevant for any chip which supports STA + NAN || P2P (or a richer combination - but bottom
+     * line of NAN and P2P being exclusive).
+     */
+    public void runP2pAndNanExclusiveInteractionsTestChip(ChipMockBase chipMock,
+            int staAvailCallbacks, int onlyChipMode) throws Exception {
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        InterfaceDestroyedListener staDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener nanDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener nanAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        InterfaceDestroyedListener p2pDestroyedListener = mock(
+                InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener p2pAvailListener = null;
+
+        // Request STA
+        IWifiIface staIface = validateInterfaceSequence(chipMock,
+                false, // chipModeValid
+                -1000, // chipModeId (only used if chipModeValid is true)
+                IfaceType.STA, // ifaceTypeToCreate
+                "sta0", // ifaceName
+                onlyChipMode, // finalChipMode
+                null, // tearDownList
+                staDestroyedListener, // destroyedListener
+                staAvailListener // availableListener
+        );
+
+        // Request NAN
+        IWifiIface nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                onlyChipMode, // chipModeId
+                IfaceType.NAN, // ifaceTypeToCreate
+                "nan0", // ifaceName
+                onlyChipMode, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                nanAvailListener // availableListener
+        );
+
+        // Request P2P
+        IWifiIface p2pIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                onlyChipMode, // chipModeId
+                IfaceType.P2P, // ifaceTypeToCreate
+                "p2p0", // ifaceName
+                onlyChipMode, // finalChipMode
+                new IWifiIface[]{nanIface}, // tearDownList
+                p2pDestroyedListener, // destroyedListener
+                p2pAvailListener, // availableListener
+                // destroyedInterfacesDestroyedListeners...
+                new InterfaceDestroyedListenerWithIfaceName(
+                        getName(nanIface), nanDestroyedListener)
+        );
+
+        // Request NAN: expect failure
+        nanIface = mDut.createNanIface(nanDestroyedListener, mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
+                mHandler);
+        collector.checkThat("NAN can't be created", nanIface, IsNull.nullValue());
+
+        // Destroy P2P interface
+        boolean status = mDut.removeIface(p2pIface);
+        mInOrder.verify(chipMock.chip).removeP2pIface("p2p0");
+        collector.checkThat("P2P removal success", status, equalTo(true));
+
+        mTestLooper.dispatchAll();
+        verify(p2pDestroyedListener).onDestroyed(getName(p2pIface));
+        verify(nanAvailListener).onAvailableForRequest();
+
+        // Request NAN: expect success now
+        nanIface = validateInterfaceSequence(chipMock,
+                true, // chipModeValid
+                onlyChipMode, // chipModeId
+                IfaceType.NAN, // ifaceTypeToCreate
+                "nan0", // ifaceName
+                onlyChipMode, // finalChipMode
+                null, // tearDownList
+                nanDestroyedListener, // destroyedListener
+                nanAvailListener // availableListener
+        );
+
+        if (staAvailCallbacks != 0) {
+            // if there are duplicate STAs then expect an available callback for each step above
+            verify(staAvailListener, times(staAvailCallbacks)).onAvailableForRequest();
+        }
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
+                nanDestroyedListener, nanAvailListener, p2pDestroyedListener);
+    }
+
     private IWifiIface validateInterfaceSequence(ChipMockBase chipMock,
             boolean chipModeValid, int chipModeId,
             int ifaceTypeToCreate, String ifaceName, int finalChipMode, IWifiIface[] tearDownList,
-            HalDeviceManager.InterfaceDestroyedListener destroyedListener,
+            InterfaceDestroyedListener destroyedListener,
             HalDeviceManager.InterfaceAvailableForRequestListener availableListener,
-            HalDeviceManager.InterfaceDestroyedListener... destroyedInterfacesDestroyedListeners)
+            InterfaceDestroyedListenerWithIfaceName...destroyedInterfacesDestroyedListeners)
             throws Exception {
         // configure chip mode response
         chipMock.chipModeValid = chipModeValid;
@@ -1181,7 +1912,7 @@
                 doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
                         chipMock.chip).createStaIface(any(IWifiChip.createStaIfaceCallback.class));
 
-                mDut.createStaIface(destroyedListener, mTestLooper.getLooper());
+                mDut.createStaIface(destroyedListener, mHandler);
                 break;
             case IfaceType.AP:
                 iface = mock(IWifiApIface.class);
@@ -1192,7 +1923,7 @@
                 doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
                         chipMock.chip).createApIface(any(IWifiChip.createApIfaceCallback.class));
 
-                mDut.createApIface(destroyedListener, mTestLooper.getLooper());
+                mDut.createApIface(destroyedListener, mHandler);
                 break;
             case IfaceType.P2P:
                 iface = mock(IWifiP2pIface.class);
@@ -1203,7 +1934,7 @@
                 doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
                         chipMock.chip).createP2pIface(any(IWifiChip.createP2pIfaceCallback.class));
 
-                mDut.createP2pIface(destroyedListener, mTestLooper.getLooper());
+                mDut.createP2pIface(destroyedListener, mHandler);
                 break;
             case IfaceType.NAN:
                 iface = mock(IWifiNanIface.class);
@@ -1214,12 +1945,12 @@
                 doAnswer(new CreateXxxIfaceAnswer(chipMock, mStatusOk, iface)).when(
                         chipMock.chip).createNanIface(any(IWifiChip.createNanIfaceCallback.class));
 
-                mDut.createNanIface(destroyedListener, mTestLooper.getLooper());
+                mDut.createNanIface(destroyedListener, mHandler);
                 break;
         }
         if (availableListener != null) {
             mDut.registerInterfaceAvailableForRequestListener(ifaceTypeToCreate, availableListener,
-                    mTestLooper.getLooper());
+                    mHandler);
         }
 
         // validate: optional tear down of interfaces
@@ -1272,9 +2003,8 @@
         // verify: callbacks on deleted interfaces
         mTestLooper.dispatchAll();
         for (int i = 0; i < destroyedInterfacesDestroyedListeners.length; ++i) {
-            verify(destroyedInterfacesDestroyedListeners[i]).onDestroyed();
+            destroyedInterfacesDestroyedListeners[i].validate();
         }
-
         return iface;
     }
 
@@ -1300,6 +2030,21 @@
         return status;
     }
 
+    private static class InterfaceDestroyedListenerWithIfaceName {
+        private final String mIfaceName;
+        @Mock private final InterfaceDestroyedListener mListener;
+
+        InterfaceDestroyedListenerWithIfaceName(
+                String ifaceName, InterfaceDestroyedListener listener) {
+            mIfaceName = ifaceName;
+            mListener = listener;
+        }
+
+        public void validate() {
+            verify(mListener).onDestroyed(mIfaceName);
+        }
+    }
+
     private static class Mutable<E> {
         public E value;
 
@@ -1598,10 +2343,10 @@
         }
     }
 
-    // emulate baseline/legacy config:
-    // mode: STA + NAN || P2P
-    // mode: NAN
-    private class BaselineChip extends ChipMockBase {
+    // test chip configuration V1:
+    // mode: STA + (NAN || P2P)
+    // mode: AP
+    private class TestChipV1 extends ChipMockBase {
         static final int STA_CHIP_MODE_ID = 0;
         static final int AP_CHIP_MODE_ID = 1;
 
@@ -1659,4 +2404,128 @@
                     .getAvailableModes(any(IWifiChip.getAvailableModesCallback.class));
         }
     }
+
+    // test chip configuration V2:
+    // mode: STA + (STA || AP) + (NAN || P2P)
+    private class TestChipV2 extends ChipMockBase {
+        // only mode (different number from any in TestChipV1 so can catch test errors)
+        static final int CHIP_MODE_ID = 5;
+
+        void initialize() throws Exception {
+            super.initialize();
+
+            // chip Id configuration
+            ArrayList<Integer> chipIds;
+            chipId = 12;
+            chipIds = new ArrayList<>();
+            chipIds.add(chipId);
+            doAnswer(new GetChipIdsAnswer(mStatusOk, chipIds)).when(mWifiMock).getChipIds(
+                    any(IWifi.getChipIdsCallback.class));
+
+            doAnswer(new GetChipAnswer(mStatusOk, chip)).when(mWifiMock).getChip(eq(12),
+                    any(IWifi.getChipCallback.class));
+
+            // initialize dummy chip modes
+            IWifiChip.ChipMode cm;
+            IWifiChip.ChipIfaceCombination cic;
+            IWifiChip.ChipIfaceCombinationLimit cicl;
+
+            //   Mode 0 (only one): 1xSTA + 1x{STA,AP} + 1x{P2P,NAN}
+            availableModes = new ArrayList<>();
+            cm = new IWifiChip.ChipMode();
+            cm.id = CHIP_MODE_ID;
+
+            cic = new IWifiChip.ChipIfaceCombination();
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.STA);
+            cic.limits.add(cicl);
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.STA);
+            cicl.types.add(IfaceType.AP);
+            cic.limits.add(cicl);
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.P2P);
+            cicl.types.add(IfaceType.NAN);
+            cic.limits.add(cicl);
+            cm.availableCombinations.add(cic);
+            availableModes.add(cm);
+
+            doAnswer(new GetAvailableModesAnswer(this)).when(chip)
+                    .getAvailableModes(any(IWifiChip.getAvailableModesCallback.class));
+        }
+    }
+
+    // test chip configuration V3:
+    // mode:
+    //    STA + (STA || AP)
+    //    STA + (NAN || P2P)
+    private class TestChipV3 extends ChipMockBase {
+        // only mode (different number from any in other TestChips so can catch test errors)
+        static final int CHIP_MODE_ID = 7;
+
+        void initialize() throws Exception {
+            super.initialize();
+
+            // chip Id configuration
+            ArrayList<Integer> chipIds;
+            chipId = 15;
+            chipIds = new ArrayList<>();
+            chipIds.add(chipId);
+            doAnswer(new GetChipIdsAnswer(mStatusOk, chipIds)).when(mWifiMock).getChipIds(
+                    any(IWifi.getChipIdsCallback.class));
+
+            doAnswer(new GetChipAnswer(mStatusOk, chip)).when(mWifiMock).getChip(eq(15),
+                    any(IWifi.getChipCallback.class));
+
+            // initialize dummy chip modes
+            IWifiChip.ChipMode cm;
+            IWifiChip.ChipIfaceCombination cic;
+            IWifiChip.ChipIfaceCombinationLimit cicl;
+
+            //   Mode 0 (only one): 1xSTA + 1x{STA,AP}, 1xSTA + 1x{P2P,NAN}
+            availableModes = new ArrayList<>();
+            cm = new IWifiChip.ChipMode();
+            cm.id = CHIP_MODE_ID;
+
+            cic = new IWifiChip.ChipIfaceCombination();
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.STA);
+            cic.limits.add(cicl);
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.STA);
+            cicl.types.add(IfaceType.AP);
+            cic.limits.add(cicl);
+
+            cm.availableCombinations.add(cic);
+
+            cic = new IWifiChip.ChipIfaceCombination();
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.STA);
+            cic.limits.add(cicl);
+
+            cicl = new IWifiChip.ChipIfaceCombinationLimit();
+            cicl.maxIfaces = 1;
+            cicl.types.add(IfaceType.P2P);
+            cicl.types.add(IfaceType.NAN);
+            cic.limits.add(cicl);
+
+            cm.availableCombinations.add(cic);
+            availableModes.add(cm);
+
+            doAnswer(new GetAvailableModesAnswer(this)).when(chip)
+                    .getAvailableModes(any(IWifiChip.getAvailableModesCallback.class));
+        }
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/PasspointProvisioningTestUtil.java b/tests/wifitests/src/com/android/server/wifi/PasspointProvisioningTestUtil.java
new file mode 100644
index 0000000..c78135d
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/PasspointProvisioningTestUtil.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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.hotspot2;
+
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.net.wifi.WifiSsid;
+import android.net.wifi.hotspot2.OsuProvider;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Helper for creating and populating WifiConfigurations in unit tests.
+ */
+public class PasspointProvisioningTestUtil {
+    /**
+     * These are constants used to generate predefined OsuProvider.
+     */
+    public static final WifiSsid TEST_SSID =
+            WifiSsid.createFromByteArray("TEST SSID".getBytes(StandardCharsets.UTF_8));
+    public static final String TEST_FRIENDLY_NAME = "Friendly Name";
+    public static final String TEST_SERVICE_DESCRIPTION = "Dummy Service";
+    public static final Uri TEST_SERVER_URI = Uri.parse("https://test.com");
+    public static final String TEST_NAI = "test.access.com";
+    public static final List<Integer> TEST_METHOD_LIST =
+            Arrays.asList(OsuProvider.METHOD_SOAP_XML_SPP);
+    public static final Icon TEST_ICON = Icon.createWithData(new byte[10], 0, 10);
+
+    /**
+     * Construct a {@link android.net.wifi.hotspot2.OsuProvider}.
+     * @param openOsuAP indicates if the OSU AP belongs to an open or OSEN network
+     * @return the constructed {@link android.net.wifi.hotspot2.OsuProvider}
+     */
+    public static OsuProvider generateOsuProvider(boolean openOsuAP) {
+        if (openOsuAP) {
+            return new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAME, TEST_SERVICE_DESCRIPTION,
+                    TEST_SERVER_URI, null, TEST_METHOD_LIST, TEST_ICON);
+        } else {
+            return new OsuProvider(TEST_SSID, TEST_FRIENDLY_NAME, TEST_SERVICE_DESCRIPTION,
+                    TEST_SERVER_URI, TEST_NAI, TEST_METHOD_LIST, TEST_ICON);
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
index 892b597..0c35904 100644
--- a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -16,25 +16,41 @@
 
 package com.android.server.wifi;
 
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK;
+import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
 
+import static com.android.server.wifi.LocalOnlyHotspotRequestInfo.HOTSPOT_NO_ERROR;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.*;
+
+import android.app.test.TestAlarmManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.net.InterfaceConfiguration;
+import android.net.Uri;
 import android.net.wifi.IApInterface;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
 import android.os.INetworkManagementService;
+import android.os.UserHandle;
 import android.os.test.TestLooper;
+import android.provider.Settings;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.internal.R;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.net.BaseNetworkObserver;
 
 import org.junit.Before;
@@ -44,9 +60,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 
 /** Unit tests for {@link SoftApManager}. */
@@ -57,28 +73,37 @@
 
     private static final String DEFAULT_SSID = "DefaultTestSSID";
     private static final String TEST_SSID = "TestSSID";
+    private static final String TEST_PASSWORD = "TestPassword";
     private static final String TEST_COUNTRY_CODE = "TestCountry";
     private static final Integer[] ALLOWED_2G_CHANNELS = {1, 2, 3, 4};
     private static final String TEST_INTERFACE_NAME = "testif0";
+    private static final int TEST_NUM_CONNECTED_CLIENTS = 4;
 
     private final ArrayList<Integer> mAllowed2GChannels =
             new ArrayList<>(Arrays.asList(ALLOWED_2G_CHANNELS));
 
     private final WifiConfiguration mDefaultApConfig = createDefaultApConfig();
 
-    TestLooper mLooper;
+    private ContentObserver mContentObserver;
+    private TestLooper mLooper;
+    private TestAlarmManager mAlarmManager;
+
+    @Mock Context mContext;
+    @Mock Resources mResources;
     @Mock WifiNative mWifiNative;
     @Mock SoftApManager.Listener mListener;
     @Mock InterfaceConfiguration mInterfaceConfiguration;
-    @Mock IBinder mApInterfaceBinder;
+    @Mock FrameworkFacade mFrameworkFacade;
     @Mock IApInterface mApInterface;
     @Mock INetworkManagementService mNmService;
     @Mock WifiApConfigStore mWifiApConfigStore;
     @Mock WifiMetrics mWifiMetrics;
-    final ArgumentCaptor<DeathRecipient> mDeathListenerCaptor =
-            ArgumentCaptor.forClass(DeathRecipient.class);
+    final ArgumentCaptor<WifiNative.WificondDeathEventHandler> mDeathListenerCaptor =
+            ArgumentCaptor.forClass(WifiNative.WificondDeathEventHandler.class);
     final ArgumentCaptor<BaseNetworkObserver> mNetworkObserverCaptor =
             ArgumentCaptor.forClass(BaseNetworkObserver.class);
+    final ArgumentCaptor<WifiNative.SoftApListener> mSoftApListenerCaptor =
+            ArgumentCaptor.forClass(WifiNative.SoftApListener.class);
 
     SoftApManager mSoftApManager;
 
@@ -88,12 +113,18 @@
         MockitoAnnotations.initMocks(this);
         mLooper = new TestLooper();
 
-        when(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
-        when(mApInterface.startHostapd()).thenReturn(true);
-        when(mApInterface.stopHostapd()).thenReturn(true);
-        when(mApInterface.writeHostapdConfig(
-                any(), anyBoolean(), anyInt(), anyInt(), any())).thenReturn(true);
-        when(mApInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
+        when(mWifiNative.startSoftAp(any(), any())).thenReturn(true);
+        when(mWifiNative.stopSoftAp()).thenReturn(true);
+        when(mWifiNative.registerWificondDeathHandler(any())).thenReturn(true);
+
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(1);
+        mAlarmManager = new TestAlarmManager();
+        when(mContext.getSystemService(Context.ALARM_SERVICE))
+                .thenReturn(mAlarmManager.getAlarmManager());
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getInteger(R.integer.config_wifi_framework_soft_ap_timeout_delay))
+                .thenReturn(600000);
     }
 
     private WifiConfiguration createDefaultApConfig() {
@@ -102,18 +133,18 @@
         return defaultConfig;
     }
 
-    private SoftApManager createSoftApManager(WifiConfiguration config) throws Exception {
-        when(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
-        when(mApInterface.startHostapd()).thenReturn(true);
-        when(mApInterface.stopHostapd()).thenReturn(true);
-        if (config == null) {
+    private SoftApManager createSoftApManager(SoftApModeConfiguration config) throws Exception {
+        if (config.getWifiConfiguration() == null) {
             when(mWifiApConfigStore.getApConfiguration()).thenReturn(mDefaultApConfig);
         }
-        SoftApManager newSoftApManager = new SoftApManager(mLooper.getLooper(),
+        SoftApManager newSoftApManager = new SoftApManager(mContext,
+                                                           mLooper.getLooper(),
+                                                           mFrameworkFacade,
                                                            mWifiNative,
                                                            TEST_COUNTRY_CODE,
                                                            mListener,
                                                            mApInterface,
+                                                           TEST_INTERFACE_NAME,
                                                            mNmService,
                                                            mWifiApConfigStore,
                                                            config,
@@ -125,7 +156,9 @@
     /** Verifies startSoftAp will use default config if AP configuration is not provided. */
     @Test
     public void startSoftApWithoutConfig() throws Exception {
-        startSoftApAndVerifyEnabled(null);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
     }
 
     /** Verifies startSoftAp will use provided config and start AP. */
@@ -134,10 +167,11 @@
         WifiConfiguration config = new WifiConfiguration();
         config.apBand = WifiConfiguration.AP_BAND_2GHZ;
         config.SSID = TEST_SSID;
-        startSoftApAndVerifyEnabled(config);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config);
+        startSoftApAndVerifyEnabled(apConfig);
     }
 
-
     /**
      * Verifies startSoftAp will start with the hiddenSSID param set when it is set to true in the
      * supplied config.
@@ -148,101 +182,482 @@
         config.apBand = WifiConfiguration.AP_BAND_2GHZ;
         config.SSID = TEST_SSID;
         config.hiddenSSID = true;
-        startSoftApAndVerifyEnabled(config);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config);
+        startSoftApAndVerifyEnabled(apConfig);
+    }
+
+    /**
+     * Verifies startSoftAp will start with the password param set in the
+     * supplied config.
+     */
+    @Test
+    public void startSoftApWithPassphraseInConfig() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+        config.apBand = WifiConfiguration.AP_BAND_5GHZ;
+        config.SSID = TEST_SSID;
+        config.allowedKeyManagement.set(WPA_PSK);
+        config.preSharedKey = TEST_PASSWORD;
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config);
+        startSoftApAndVerifyEnabled(apConfig);
     }
 
     /** Tests softap startup if default config fails to load. **/
     @Test
     public void startSoftApDefaultConfigFailedToLoad() throws Exception {
-        when(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
-        when(mApInterface.startHostapd()).thenReturn(true);
-        when(mApInterface.stopHostapd()).thenReturn(true);
         when(mWifiApConfigStore.getApConfiguration()).thenReturn(null);
-        SoftApManager newSoftApManager = new SoftApManager(mLooper.getLooper(),
+        SoftApModeConfiguration nullApConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        SoftApManager newSoftApManager = new SoftApManager(mContext,
+                                                           mLooper.getLooper(),
+                                                           mFrameworkFacade,
                                                            mWifiNative,
                                                            TEST_COUNTRY_CODE,
                                                            mListener,
                                                            mApInterface,
+                                                           TEST_INTERFACE_NAME,
                                                            mNmService,
                                                            mWifiApConfigStore,
-                                                           null,
+                                                           nullApConfig,
                                                            mWifiMetrics);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
         verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_GENERAL);
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(),
+                eq(UserHandle.ALL));
+
+        List<Intent> capturedIntents = intentCaptor.getAllValues();
+        checkApStateChangedBroadcast(capturedIntents.get(0), WIFI_AP_STATE_ENABLING,
+                WIFI_AP_STATE_DISABLED, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
+                nullApConfig.getTargetMode());
+        checkApStateChangedBroadcast(capturedIntents.get(1), WIFI_AP_STATE_FAILED,
+                WIFI_AP_STATE_ENABLING, WifiManager.SAP_START_FAILURE_GENERAL, TEST_INTERFACE_NAME,
+                nullApConfig.getTargetMode());
+    }
+
+    /**
+     * Tests that the generic error is propagated and properly reported when starting softap and the
+     * specified channel cannot be used.
+     */
+    @Test
+    public void startSoftApFailGeneralErrorForConfigChannel() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+        config.apBand = WifiConfiguration.AP_BAND_5GHZ;
+        config.SSID = TEST_SSID;
+        SoftApModeConfiguration softApConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config);
+
+        when(mWifiNative.isHalStarted()).thenReturn(true);
+
+        SoftApManager newSoftApManager = new SoftApManager(mContext,
+                                                           mLooper.getLooper(),
+                                                           mFrameworkFacade,
+                                                           mWifiNative,
+                                                           null,
+                                                           mListener,
+                                                           mApInterface,
+                                                           TEST_INTERFACE_NAME,
+                                                           mNmService,
+                                                           mWifiApConfigStore,
+                                                           softApConfig,
+                                                           mWifiMetrics);
+        mLooper.dispatchAll();
+        newSoftApManager.start();
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(),
+                eq(UserHandle.ALL));
+
+        List<Intent> capturedIntents = intentCaptor.getAllValues();
+        checkApStateChangedBroadcast(capturedIntents.get(0), WIFI_AP_STATE_ENABLING,
+                WIFI_AP_STATE_DISABLED, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
+                softApConfig.getTargetMode());
+        checkApStateChangedBroadcast(capturedIntents.get(1), WIFI_AP_STATE_FAILED,
+                WIFI_AP_STATE_ENABLING, WifiManager.SAP_START_FAILURE_GENERAL, TEST_INTERFACE_NAME,
+                softApConfig.getTargetMode());
+    }
+
+    /**
+     * Tests that the NO_CHANNEL error is propagated and properly reported when starting softap and
+     * a valid channel cannot be determined.
+     */
+    @Test
+    public void startSoftApFailNoChannel() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+        config.apBand = -2;
+        config.apChannel = 0;
+        config.SSID = TEST_SSID;
+        SoftApModeConfiguration softApConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config);
+
+        when(mWifiNative.isHalStarted()).thenReturn(true);
+
+        SoftApManager newSoftApManager = new SoftApManager(mContext,
+                                                           mLooper.getLooper(),
+                                                           mFrameworkFacade,
+                                                           mWifiNative,
+                                                           TEST_COUNTRY_CODE,
+                                                           mListener,
+                                                           mApInterface,
+                                                           TEST_INTERFACE_NAME,
+                                                           mNmService,
+                                                           mWifiApConfigStore,
+                                                           softApConfig,
+                                                           mWifiMetrics);
+        mLooper.dispatchAll();
+        newSoftApManager.start();
+        mLooper.dispatchAll();
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(),
+                eq(UserHandle.ALL));
+
+        List<Intent> capturedIntents = intentCaptor.getAllValues();
+        checkApStateChangedBroadcast(capturedIntents.get(0), WIFI_AP_STATE_ENABLING,
+                WIFI_AP_STATE_DISABLED, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
+                softApConfig.getTargetMode());
+        checkApStateChangedBroadcast(capturedIntents.get(1), WIFI_AP_STATE_FAILED,
+                WIFI_AP_STATE_ENABLING, WifiManager.SAP_START_FAILURE_NO_CHANNEL,
+                TEST_INTERFACE_NAME, softApConfig.getTargetMode());
+    }
+
+    /**
+     * Tests startup when Ap Interface fails to start successfully.
+     */
+    @Test
+    public void startSoftApApInterfaceFailedToStart() throws Exception {
+        when(mWifiNative.startSoftAp(any(), any())).thenReturn(false);
+        SoftApModeConfiguration softApModeConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, mDefaultApConfig);
+
+        SoftApManager newSoftApManager = new SoftApManager(mContext,
+                                                           mLooper.getLooper(),
+                                                           mFrameworkFacade,
+                                                           mWifiNative,
+                                                           TEST_COUNTRY_CODE,
+                                                           mListener,
+                                                           mApInterface,
+                                                           TEST_INTERFACE_NAME,
+                                                           mNmService,
+                                                           mWifiApConfigStore,
+                                                           softApModeConfig,
+                                                           mWifiMetrics);
+
+        mLooper.dispatchAll();
+        newSoftApManager.start();
+        mLooper.dispatchAll();
+        verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
+                WifiManager.SAP_START_FAILURE_GENERAL);
     }
 
-    /** Tests the handling of stop command when soft AP is not started. */
+    /**
+     * Tests the handling of stop command when soft AP is not started.
+     */
     @Test
     public void stopWhenNotStarted() throws Exception {
-        mSoftApManager = createSoftApManager(null);
+        mSoftApManager = createSoftApManager(
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mSoftApManager.stop();
         mLooper.dispatchAll();
         /* Verify no state changes. */
         verify(mListener, never()).onStateChanged(anyInt(), anyInt());
+        verify(mContext, never()).sendStickyBroadcastAsUser(any(), any());
     }
 
-    /** Tests the handling of stop command when soft AP is started. */
+    /**
+     * Tests the handling of stop command when soft AP is started.
+     */
     @Test
     public void stopWhenStarted() throws Exception {
-        startSoftApAndVerifyEnabled(null);
+        SoftApModeConfiguration softApModeConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(softApModeConfig);
+
+        // reset to clear verified Intents for ap state change updates
+        reset(mContext);
 
         InOrder order = inOrder(mListener);
 
         mSoftApManager.stop();
         mLooper.dispatchAll();
 
-        verify(mApInterface).stopHostapd();
+        verify(mWifiNative).stopSoftAp();
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLING, 0);
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(),
+                eq(UserHandle.ALL));
+
+        List<Intent> capturedIntents = intentCaptor.getAllValues();
+        checkApStateChangedBroadcast(capturedIntents.get(0), WIFI_AP_STATE_DISABLING,
+                WIFI_AP_STATE_ENABLED, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
+                softApModeConfig.getTargetMode());
+        checkApStateChangedBroadcast(capturedIntents.get(1), WIFI_AP_STATE_DISABLED,
+                WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
+                softApModeConfig.getTargetMode());
     }
 
+    /**
+     * Verify that SoftAp mode shuts down if wificond dies.
+     */
     @Test
     public void handlesWificondInterfaceDeath() throws Exception {
-        startSoftApAndVerifyEnabled(null);
+        SoftApModeConfiguration softApModeConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(softApModeConfig);
 
-        mDeathListenerCaptor.getValue().binderDied();
+        // reset to clear verified Intents for ap state change updates
+        reset(mContext);
+
+        mDeathListenerCaptor.getValue().onDeath();
         mLooper.dispatchAll();
         InOrder order = inOrder(mListener);
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLING, 0);
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
                 WifiManager.SAP_START_FAILURE_GENERAL);
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(),
+                eq(UserHandle.ALL));
+
+        List<Intent> capturedIntents = intentCaptor.getAllValues();
+        checkApStateChangedBroadcast(capturedIntents.get(0), WIFI_AP_STATE_DISABLING,
+                WIFI_AP_STATE_ENABLED, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
+                softApModeConfig.getTargetMode());
+        checkApStateChangedBroadcast(capturedIntents.get(1), WIFI_AP_STATE_FAILED,
+                WIFI_AP_STATE_DISABLING, WifiManager.SAP_START_FAILURE_GENERAL, TEST_INTERFACE_NAME,
+                softApModeConfig.getTargetMode());
+    }
+
+    @Test
+    public void updatesNumAssociatedStations() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(
+                TEST_NUM_CONNECTED_CLIENTS);
+        mLooper.dispatchAll();
+        verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(TEST_NUM_CONNECTED_CLIENTS,
+                apConfig.getTargetMode());
+    }
+
+    @Test
+    public void handlesInvalidNumAssociatedStations() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(
+                TEST_NUM_CONNECTED_CLIENTS);
+        /* Invalid values should be ignored */
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(-1);
+        mLooper.dispatchAll();
+        verify(mWifiMetrics, times(1)).addSoftApNumAssociatedStationsChangedEvent(
+                TEST_NUM_CONNECTED_CLIENTS, apConfig.getTargetMode());
+    }
+
+    @Test
+    public void schedulesTimeoutTimerOnStart() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        // Verify timer is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+    }
+
+    @Test
+    public void cancelsTimeoutTimerOnStop() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+        mSoftApManager.stop();
+        mLooper.dispatchAll();
+
+        // Verify timer is canceled
+        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+    }
+
+    @Test
+    public void cancelsTimeoutTimerOnNewClientsConnect() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(
+                TEST_NUM_CONNECTED_CLIENTS);
+        mLooper.dispatchAll();
+
+        // Verify timer is canceled
+        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+    }
+
+    @Test
+    public void schedulesTimeoutTimerWhenAllClientsDisconnect() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(
+                TEST_NUM_CONNECTED_CLIENTS);
+        mLooper.dispatchAll();
+        // Verify timer is canceled at this point
+        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(0);
+        mLooper.dispatchAll();
+        // Verify timer is scheduled again
+        verify(mAlarmManager.getAlarmManager(), times(2)).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+    }
+
+    @Test
+    public void stopsSoftApOnTimeoutMessage() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        mAlarmManager.dispatch(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).stopSoftAp();
+    }
+
+    @Test
+    public void cancelsTimeoutTimerOnTimeoutToggleChangeWhenNoClients() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(0);
+        mContentObserver.onChange(false);
+        mLooper.dispatchAll();
+
+        // Verify timer is canceled
+        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+    }
+
+    @Test
+    public void schedulesTimeoutTimerOnTimeoutToggleChangeWhenNoClients() throws Exception {
+        // start with timeout toggle disabled
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(0);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(1);
+        mContentObserver.onChange(false);
+        mLooper.dispatchAll();
+
+        // Verify timer is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+    }
+
+    @Test
+    public void doesNotScheduleTimeoutTimerOnStartWhenTimeoutIsDisabled() throws Exception {
+        // start with timeout toggle disabled
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(0);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        // Verify timer is not scheduled
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+    }
+
+    @Test
+    public void doesNotScheduleTimeoutTimerWhenAllClientsDisconnectButTimeoutIsDisabled()
+            throws Exception {
+        // start with timeout toggle disabled
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(0);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+        // add some clients
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(
+                TEST_NUM_CONNECTED_CLIENTS);
+        mLooper.dispatchAll();
+        // remove all clients
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(0);
+        mLooper.dispatchAll();
+        // Verify timer is not scheduled
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+    }
+
+    @Test
+    public void unregistersSettingsObserverOnStop() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+        mSoftApManager.stop();
+        mLooper.dispatchAll();
+
+        verify(mFrameworkFacade).unregisterContentObserver(eq(mContext), eq(mContentObserver));
     }
 
     /** Starts soft AP and verifies that it is enabled successfully. */
-    protected void startSoftApAndVerifyEnabled(WifiConfiguration config) throws Exception {
-        String expectedSSID;
-        boolean expectedHiddenSsid;
-        InOrder order = inOrder(mListener, mApInterfaceBinder, mApInterface, mNmService);
+    protected void startSoftApAndVerifyEnabled(
+            SoftApModeConfiguration softApConfig) throws Exception {
+        WifiConfiguration expectedConfig;
+        InOrder order =
+                inOrder(mListener, mApInterface, mWifiNative, mNmService);
 
         when(mWifiNative.isHalStarted()).thenReturn(false);
         when(mWifiNative.setCountryCodeHal(TEST_COUNTRY_CODE.toUpperCase(Locale.ROOT)))
                 .thenReturn(true);
 
-        mSoftApManager = createSoftApManager(config);
+        mSoftApManager = createSoftApManager(softApConfig);
+        WifiConfiguration config = softApConfig.getWifiConfiguration();
         if (config == null) {
             when(mWifiApConfigStore.getApConfiguration()).thenReturn(mDefaultApConfig);
-            expectedSSID = mDefaultApConfig.SSID;
-            expectedHiddenSsid = mDefaultApConfig.hiddenSSID;
+            expectedConfig = new WifiConfiguration(mDefaultApConfig);
         } else {
-            expectedSSID = config.SSID;
-            expectedHiddenSsid = config.hiddenSSID;
+            expectedConfig = new WifiConfiguration(config);
         }
 
+        ArgumentCaptor<ContentObserver> observerCaptor = ArgumentCaptor.forClass(
+                ContentObserver.class);
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
         mSoftApManager.start();
         mLooper.dispatchAll();
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
-        order.verify(mApInterfaceBinder).linkToDeath(mDeathListenerCaptor.capture(), eq(0));
+        order.verify(mWifiNative).registerWificondDeathHandler(mDeathListenerCaptor.capture());
         order.verify(mNmService).registerObserver(mNetworkObserverCaptor.capture());
-        order.verify(mApInterface).writeHostapdConfig(
-                eq(expectedSSID.getBytes(StandardCharsets.UTF_8)), eq(expectedHiddenSsid),
-                anyInt(), anyInt(), any());
-        order.verify(mApInterface).startHostapd();
+        ArgumentCaptor<WifiConfiguration> configCaptor =
+                ArgumentCaptor.forClass(WifiConfiguration.class);
+        order.verify(mWifiNative).startSoftAp(
+                configCaptor.capture(), mSoftApListenerCaptor.capture());
+        WifiConfigurationTestUtil.assertConfigurationEqual(expectedConfig, configCaptor.getValue());
         mNetworkObserverCaptor.getValue().interfaceLinkStateChanged(TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLED, 0);
+        verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(),
+                                                             eq(UserHandle.ALL));
+        List<Intent> capturedIntents = intentCaptor.getAllValues();
+        checkApStateChangedBroadcast(capturedIntents.get(0), WIFI_AP_STATE_ENABLING,
+                WIFI_AP_STATE_DISABLED, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
+                softApConfig.getTargetMode());
+        checkApStateChangedBroadcast(capturedIntents.get(1), WIFI_AP_STATE_ENABLED,
+                WIFI_AP_STATE_ENABLING, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
+                softApConfig.getTargetMode());
+        verify(mWifiMetrics).addSoftApUpChangedEvent(true, softApConfig.mTargetMode);
+        verify(mFrameworkFacade).registerContentObserver(eq(mContext), any(Uri.class), eq(true),
+                observerCaptor.capture());
+        mContentObserver = observerCaptor.getValue();
     }
 
     /** Verifies that soft AP was not disabled. */
@@ -250,4 +665,19 @@
         verify(mListener, never()).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLING, 0);
         verify(mListener, never()).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
     }
+
+    private void checkApStateChangedBroadcast(Intent intent, int expectedCurrentState,
+                                              int expectedPrevState, int expectedErrorCode,
+                                              String expectedIfaceName, int expectedMode) {
+        int currentState = intent.getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED);
+        int prevState = intent.getIntExtra(EXTRA_PREVIOUS_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED);
+        int errorCode = intent.getIntExtra(EXTRA_WIFI_AP_FAILURE_REASON, HOTSPOT_NO_ERROR);
+        String ifaceName = intent.getStringExtra(EXTRA_WIFI_AP_INTERFACE_NAME);
+        int mode = intent.getIntExtra(EXTRA_WIFI_AP_MODE, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
+        assertEquals(expectedCurrentState, currentState);
+        assertEquals(expectedPrevState, prevState);
+        assertEquals(expectedErrorCode, errorCode);
+        assertEquals(expectedIfaceName, ifaceName);
+        assertEquals(expectedMode, mode);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java b/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java
index 2deef52..0303d9a 100644
--- a/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SupplicantStaIfaceHalTest.java
@@ -23,15 +23,7 @@
 import static org.mockito.Matchers.anyShort;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 import android.app.test.MockAnswerUtil;
 import android.content.Context;
@@ -54,6 +46,7 @@
 import android.net.wifi.WifiSsid;
 import android.os.IHwBinder;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.SparseArray;
 
 import com.android.server.wifi.hotspot2.AnqpEvent;
@@ -61,6 +54,8 @@
 import com.android.server.wifi.hotspot2.WnmData;
 import com.android.server.wifi.util.NativeUtil;
 
+import libcore.util.NonNull;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -90,7 +85,8 @@
     private static final String SUPPLICANT_SSID = NETWORK_ID_TO_SSID.get(SUPPLICANT_NETWORK_ID);
     private static final int ROAM_NETWORK_ID = 4;
     private static final String BSSID = "fa:45:23:23:12:12";
-    private static final String WLAN_IFACE_NAME = "wlan0";
+    private static final String WLAN0_IFACE_NAME = "wlan0";
+    private static final String WLAN1_IFACE_NAME = "wlan1";
     private static final String P2P_IFACE_NAME = "p2p0";
     private static final String ICON_FILE_NAME  = "blahblah";
     private static final int ICON_FILE_SIZE = 72;
@@ -103,9 +99,11 @@
     @Mock Context mContext;
     @Mock WifiMonitor mWifiMonitor;
     @Mock SupplicantStaNetworkHal mSupplicantStaNetworkMock;
+    @Mock WifiNative.SupplicantDeathEventHandler mSupplicantHalDeathHandler;
     SupplicantStatus mStatusSuccess;
     SupplicantStatus mStatusFailure;
-    ISupplicant.IfaceInfo mStaIface;
+    ISupplicant.IfaceInfo mStaIface0;
+    ISupplicant.IfaceInfo mStaIface1;
     ISupplicant.IfaceInfo mP2pIface;
     ArrayList<ISupplicant.IfaceInfo> mIfaceInfoList;
     ISupplicantStaIfaceCallback mISupplicantStaIfaceCallback;
@@ -142,6 +140,7 @@
 
         @Override
         protected SupplicantStaNetworkHal getStaNetworkMockable(
+                @NonNull String ifaceName,
                 ISupplicantStaNetwork iSupplicantStaNetwork) {
             return mSupplicantStaNetworkMock;
         }
@@ -152,11 +151,13 @@
         MockitoAnnotations.initMocks(this);
         mStatusSuccess = createSupplicantStatus(SupplicantStatusCode.SUCCESS);
         mStatusFailure = createSupplicantStatus(SupplicantStatusCode.FAILURE_UNKNOWN);
-        mStaIface = createIfaceInfo(IfaceType.STA, WLAN_IFACE_NAME);
+        mStaIface0 = createIfaceInfo(IfaceType.STA, WLAN0_IFACE_NAME);
+        mStaIface1 = createIfaceInfo(IfaceType.STA, WLAN1_IFACE_NAME);
         mP2pIface = createIfaceInfo(IfaceType.P2P, P2P_IFACE_NAME);
 
         mIfaceInfoList = new ArrayList<>();
-        mIfaceInfoList.add(mStaIface);
+        mIfaceInfoList.add(mStaIface0);
+        mIfaceInfoList.add(mStaIface1);
         mIfaceInfoList.add(mP2pIface);
 
         when(mServiceManagerMock.linkToDeath(any(IHwBinder.DeathRecipient.class),
@@ -215,6 +216,50 @@
         executeAndValidateInitializationSequence(false, false, false, true);
     }
 
+
+    /**
+     * Sunny day scenario for SupplicantStaIfaceHal initialization
+     * Asserts successful initialization of second interface
+     */
+    @Test
+    public void testSetupTwoInterfaces() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false, false);
+        assertTrue(mDut.setupIface(WLAN1_IFACE_NAME));
+    }
+
+    /**
+     * Ensures that we do not allow operations on an interface until it's setup.
+     */
+    @Test
+    public void testEnsureOperationFailsUntilSetupInterfaces() throws Exception {
+        executeAndValidateInitializationSequence(false, false, false, false);
+
+        // Ensure that the cancel wps operation is failed because wlan1 interface is not yet setup.
+        assertFalse(mDut.cancelWps(WLAN1_IFACE_NAME));
+        verify(mISupplicantStaIfaceMock, never()).cancelWps();
+
+        // Now setup the wlan1 interface and Ensure that the cancel wps operation is successful.
+        assertTrue(mDut.setupIface(WLAN1_IFACE_NAME));
+        when(mISupplicantStaIfaceMock.cancelWps()).thenReturn(mStatusSuccess);
+        assertTrue(mDut.cancelWps(WLAN1_IFACE_NAME));
+        verify(mISupplicantStaIfaceMock).cancelWps();
+    }
+
+    /**
+     * Sunny day scenario for SupplicantStaIfaceHal interface teardown.
+     * Asserts successful initialization of second interface
+     */
+    @Test
+    public void testTeardownTwoInterfaces() throws Exception {
+        testSetupTwoInterfaces();
+        assertTrue(mDut.teardownIface(WLAN0_IFACE_NAME));
+        assertTrue(mDut.teardownIface(WLAN1_IFACE_NAME));
+
+        // Ensure that the cancel wps operation is failed because there are no interfaces setup.
+        assertFalse(mDut.cancelWps(WLAN0_IFACE_NAME));
+        verify(mISupplicantStaIfaceMock, never()).cancelWps();
+    }
+
     /**
      * Tests the loading of networks using {@link SupplicantStaNetworkHal}.
      * Fills up only the SSID field of configs and uses it as a configKey as well.
@@ -250,7 +295,7 @@
 
         Map<String, WifiConfiguration> configs = new HashMap<>();
         SparseArray<Map<String, String>> extras = new SparseArray<>();
-        assertTrue(mDut.loadNetworks(configs, extras));
+        assertTrue(mDut.loadNetworks(WLAN0_IFACE_NAME, configs, extras));
 
         assertEquals(3, configs.size());
         assertEquals(3, extras.size());
@@ -316,7 +361,7 @@
 
         Map<String, WifiConfiguration> configs = new HashMap<>();
         SparseArray<Map<String, String>> extras = new SparseArray<>();
-        assertTrue(mDut.loadNetworks(configs, extras));
+        assertTrue(mDut.loadNetworks(WLAN0_IFACE_NAME, configs, extras));
 
         assertEquals(2, configs.size());
         assertEquals(2, extras.size());
@@ -355,7 +400,7 @@
 
         Map<String, WifiConfiguration> configs = new HashMap<>();
         SparseArray<Map<String, String>> extras = new SparseArray<>();
-        assertFalse(mDut.loadNetworks(configs, extras));
+        assertFalse(mDut.loadNetworks(WLAN0_IFACE_NAME, configs, extras));
     }
 
     /**
@@ -380,7 +425,7 @@
 
         Map<String, WifiConfiguration> configs = new HashMap<>();
         SparseArray<Map<String, String>> extras = new SparseArray<>();
-        assertFalse(mDut.loadNetworks(configs, extras));
+        assertFalse(mDut.loadNetworks(WLAN0_IFACE_NAME, configs, extras));
     }
 
     /**
@@ -411,7 +456,7 @@
 
         Map<String, WifiConfiguration> configs = new HashMap<>();
         SparseArray<Map<String, String>> extras = new SparseArray<>();
-        assertTrue(mDut.loadNetworks(configs, extras));
+        assertTrue(mDut.loadNetworks(WLAN0_IFACE_NAME, configs, extras));
         assertTrue(configs.isEmpty());
     }
 
@@ -444,7 +489,7 @@
 
         Map<String, WifiConfiguration> configs = new HashMap<>();
         SparseArray<Map<String, String>> extras = new SparseArray<>();
-        assertTrue(mDut.loadNetworks(configs, extras));
+        assertTrue(mDut.loadNetworks(WLAN0_IFACE_NAME, configs, extras));
         assertTrue(configs.isEmpty());
     }
 
@@ -468,7 +513,7 @@
         setupMocksForConnectSequence(true /*haveExistingNetwork*/);
         // Make this network different by changing SSID.
         config.SSID = "AnDifferentSSID";
-        assertTrue(mDut.connectToNetwork(config));
+        assertTrue(mDut.connectToNetwork(WLAN0_IFACE_NAME, config));
         verify(mISupplicantStaIfaceMock).removeNetwork(SUPPLICANT_NETWORK_ID);
         verify(mISupplicantStaIfaceMock)
                 .addNetwork(any(ISupplicantStaIface.addNetworkCallback.class));
@@ -482,7 +527,29 @@
         // Reset mocks for mISupplicantStaIfaceMock because we finished the first connection.
         reset(mISupplicantStaIfaceMock);
         setupMocksForConnectSequence(true /*haveExistingNetwork*/);
-        assertTrue(mDut.connectToNetwork(config));
+        assertTrue(mDut.connectToNetwork(WLAN0_IFACE_NAME, config));
+        verify(mISupplicantStaIfaceMock, never()).removeNetwork(anyInt());
+        verify(mISupplicantStaIfaceMock, never())
+                .addNetwork(any(ISupplicantStaIface.addNetworkCallback.class));
+    }
+
+    @Test
+    public void connectToNetworkWithSameNetworkButDifferentBssidUpdatesNetworkFromSupplicant()
+            throws Exception {
+        executeAndValidateInitializationSequence();
+        WifiConfiguration config = executeAndValidateConnectSequence(SUPPLICANT_NETWORK_ID, false);
+        String testBssid = "11:22:33:44:55:66";
+        when(mSupplicantStaNetworkMock.setBssid(eq(testBssid))).thenReturn(true);
+
+        // Reset mocks for mISupplicantStaIfaceMock because we finished the first connection.
+        reset(mISupplicantStaIfaceMock);
+        setupMocksForConnectSequence(true /*haveExistingNetwork*/);
+        // Change the BSSID and connect to the same network.
+        assertFalse(TextUtils.equals(
+                testBssid, config.getNetworkSelectionStatus().getNetworkSelectionBSSID()));
+        config.getNetworkSelectionStatus().setNetworkSelectionBSSID(testBssid);
+        assertTrue(mDut.connectToNetwork(WLAN0_IFACE_NAME, config));
+        verify(mSupplicantStaNetworkMock).setBssid(eq(testBssid));
         verify(mISupplicantStaIfaceMock, never()).removeNetwork(anyInt());
         verify(mISupplicantStaIfaceMock, never())
                 .addNetwork(any(ISupplicantStaIface.addNetworkCallback.class));
@@ -503,7 +570,7 @@
         }).when(mISupplicantStaIfaceMock).addNetwork(
                 any(ISupplicantStaIface.addNetworkCallback.class));
 
-        assertFalse(mDut.connectToNetwork(createTestWifiConfiguration()));
+        assertFalse(mDut.connectToNetwork(WLAN0_IFACE_NAME, createTestWifiConfiguration()));
     }
 
     /**
@@ -517,7 +584,7 @@
         when(mSupplicantStaNetworkMock.saveWifiConfiguration(any(WifiConfiguration.class)))
                 .thenReturn(false);
 
-        assertFalse(mDut.connectToNetwork(createTestWifiConfiguration()));
+        assertFalse(mDut.connectToNetwork(WLAN0_IFACE_NAME, createTestWifiConfiguration()));
         // We should have removed the existing network once before connection and once more
         // on failure to save network configuration.
         verify(mISupplicantStaIfaceMock, times(2)).removeNetwork(anyInt());
@@ -535,7 +602,7 @@
                 .when(mSupplicantStaNetworkMock).saveWifiConfiguration(
                         any(WifiConfiguration.class));
 
-        assertFalse(mDut.connectToNetwork(createTestWifiConfiguration()));
+        assertFalse(mDut.connectToNetwork(WLAN0_IFACE_NAME, createTestWifiConfiguration()));
         // We should have removed the existing network once before connection and once more
         // on failure to save network configuration.
         verify(mISupplicantStaIfaceMock, times(2)).removeNetwork(anyInt());
@@ -551,7 +618,7 @@
 
         when(mSupplicantStaNetworkMock.select()).thenReturn(false);
 
-        assertFalse(mDut.connectToNetwork(createTestWifiConfiguration()));
+        assertFalse(mDut.connectToNetwork(WLAN0_IFACE_NAME, createTestWifiConfiguration()));
     }
 
     /**
@@ -561,7 +628,7 @@
     public void testRoamToSameNetwork() throws Exception {
         executeAndValidateInitializationSequence();
         executeAndValidateRoamSequence(true);
-        assertTrue(mDut.connectToNetwork(createTestWifiConfiguration()));
+        assertTrue(mDut.connectToNetwork(WLAN0_IFACE_NAME, createTestWifiConfiguration()));
     }
 
     /**
@@ -586,7 +653,7 @@
         WifiConfiguration roamingConfig = new WifiConfiguration();
         roamingConfig.networkId = connectedNetworkId;
         roamingConfig.getNetworkSelectionStatus().setNetworkSelectionBSSID("45:34:23:23:ab:ed");
-        assertFalse(mDut.roamToNetwork(roamingConfig));
+        assertFalse(mDut.roamToNetwork(WLAN0_IFACE_NAME, roamingConfig));
     }
 
     /**
@@ -608,7 +675,7 @@
             }
         }).when(mISupplicantStaIfaceMock).removeNetwork(anyInt());
 
-        assertTrue(mDut.removeAllNetworks());
+        assertTrue(mDut.removeAllNetworks(WLAN0_IFACE_NAME));
         verify(mISupplicantStaIfaceMock, times(NETWORK_ID_TO_SSID.size())).removeNetwork(anyInt());
     }
 
@@ -624,13 +691,13 @@
 
         // Connect to a network and verify current network is set.
         executeAndValidateConnectSequence(4, false);
-        assertTrue(mDut.setCurrentNetworkBssid(testBssid));
+        assertTrue(mDut.setCurrentNetworkBssid(WLAN0_IFACE_NAME, testBssid));
         verify(mSupplicantStaNetworkMock).setBssid(eq(testBssid));
         reset(mSupplicantStaNetworkMock);
 
         // Remove all networks and verify current network info is resetted.
-        assertTrue(mDut.removeAllNetworks());
-        assertFalse(mDut.setCurrentNetworkBssid(testBssid));
+        assertTrue(mDut.removeAllNetworks(WLAN0_IFACE_NAME));
+        assertFalse(mDut.setCurrentNetworkBssid(WLAN0_IFACE_NAME, testBssid));
         verify(mSupplicantStaNetworkMock, never()).setBssid(eq(testBssid));
     }
 
@@ -653,7 +720,7 @@
         WifiConfiguration roamingConfig = new WifiConfiguration();
         roamingConfig.networkId = connectedNetworkId;
         roamingConfig.getNetworkSelectionStatus().setNetworkSelectionBSSID("45:34:23:23:ab:ed");
-        assertFalse(mDut.roamToNetwork(roamingConfig));
+        assertFalse(mDut.roamToNetwork(WLAN0_IFACE_NAME, roamingConfig));
     }
 
     /**
@@ -666,10 +733,10 @@
 
         executeAndValidateInitializationSequence();
         // Return null when not connected to the network.
-        assertTrue(mDut.getCurrentNetworkWpsNfcConfigurationToken() == null);
+        assertTrue(mDut.getCurrentNetworkWpsNfcConfigurationToken(WLAN0_IFACE_NAME) == null);
         verify(mSupplicantStaNetworkMock, never()).getWpsNfcConfigurationToken();
         executeAndValidateConnectSequence(4, false);
-        assertEquals(token, mDut.getCurrentNetworkWpsNfcConfigurationToken());
+        assertEquals(token, mDut.getCurrentNetworkWpsNfcConfigurationToken(WLAN0_IFACE_NAME));
         verify(mSupplicantStaNetworkMock).getWpsNfcConfigurationToken();
     }
 
@@ -683,10 +750,10 @@
 
         executeAndValidateInitializationSequence();
         // Fail when not connected to a network.
-        assertFalse(mDut.setCurrentNetworkBssid(bssidStr));
+        assertFalse(mDut.setCurrentNetworkBssid(WLAN0_IFACE_NAME, bssidStr));
         verify(mSupplicantStaNetworkMock, never()).setBssid(eq(bssidStr));
         executeAndValidateConnectSequence(4, false);
-        assertTrue(mDut.setCurrentNetworkBssid(bssidStr));
+        assertTrue(mDut.setCurrentNetworkBssid(WLAN0_IFACE_NAME, bssidStr));
         verify(mSupplicantStaNetworkMock).setBssid(eq(bssidStr));
     }
 
@@ -701,10 +768,10 @@
 
         executeAndValidateInitializationSequence();
         // Fail when not connected to a network.
-        assertFalse(mDut.sendCurrentNetworkEapIdentityResponse(identity));
+        assertFalse(mDut.sendCurrentNetworkEapIdentityResponse(WLAN0_IFACE_NAME, identity));
         verify(mSupplicantStaNetworkMock, never()).sendNetworkEapIdentityResponse(eq(identity));
         executeAndValidateConnectSequence(4, false);
-        assertTrue(mDut.sendCurrentNetworkEapIdentityResponse(identity));
+        assertTrue(mDut.sendCurrentNetworkEapIdentityResponse(WLAN0_IFACE_NAME, identity));
         verify(mSupplicantStaNetworkMock).sendNetworkEapIdentityResponse(eq(identity));
     }
 
@@ -719,10 +786,11 @@
         executeAndValidateInitializationSequence();
 
         // Return null when not connected to the network.
-        assertEquals(null, mDut.getCurrentNetworkEapAnonymousIdentity());
+        assertEquals(null, mDut.getCurrentNetworkEapAnonymousIdentity(WLAN0_IFACE_NAME));
         executeAndValidateConnectSequence(4, false);
         // Return anonymous identity for the current network.
-        assertEquals(anonymousIdentity, mDut.getCurrentNetworkEapAnonymousIdentity());
+        assertEquals(
+                anonymousIdentity, mDut.getCurrentNetworkEapAnonymousIdentity(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -736,10 +804,10 @@
 
         executeAndValidateInitializationSequence();
         // Fail when not connected to a network.
-        assertFalse(mDut.sendCurrentNetworkEapSimGsmAuthResponse(params));
+        assertFalse(mDut.sendCurrentNetworkEapSimGsmAuthResponse(WLAN0_IFACE_NAME, params));
         verify(mSupplicantStaNetworkMock, never()).sendNetworkEapSimGsmAuthResponse(eq(params));
         executeAndValidateConnectSequence(4, false);
-        assertTrue(mDut.sendCurrentNetworkEapSimGsmAuthResponse(params));
+        assertTrue(mDut.sendCurrentNetworkEapSimGsmAuthResponse(WLAN0_IFACE_NAME, params));
         verify(mSupplicantStaNetworkMock).sendNetworkEapSimGsmAuthResponse(eq(params));
     }
 
@@ -754,10 +822,10 @@
 
         executeAndValidateInitializationSequence();
         // Fail when not connected to a network.
-        assertFalse(mDut.sendCurrentNetworkEapSimUmtsAuthResponse(params));
+        assertFalse(mDut.sendCurrentNetworkEapSimUmtsAuthResponse(WLAN0_IFACE_NAME, params));
         verify(mSupplicantStaNetworkMock, never()).sendNetworkEapSimUmtsAuthResponse(eq(params));
         executeAndValidateConnectSequence(4, false);
-        assertTrue(mDut.sendCurrentNetworkEapSimUmtsAuthResponse(params));
+        assertTrue(mDut.sendCurrentNetworkEapSimUmtsAuthResponse(WLAN0_IFACE_NAME, params));
         verify(mSupplicantStaNetworkMock).sendNetworkEapSimUmtsAuthResponse(eq(params));
     }
 
@@ -772,10 +840,10 @@
 
         executeAndValidateInitializationSequence();
         // Fail when not connected to a network.
-        assertFalse(mDut.sendCurrentNetworkEapSimUmtsAutsResponse(params));
+        assertFalse(mDut.sendCurrentNetworkEapSimUmtsAutsResponse(WLAN0_IFACE_NAME, params));
         verify(mSupplicantStaNetworkMock, never()).sendNetworkEapSimUmtsAutsResponse(eq(params));
         executeAndValidateConnectSequence(4, false);
-        assertTrue(mDut.sendCurrentNetworkEapSimUmtsAutsResponse(params));
+        assertTrue(mDut.sendCurrentNetworkEapSimUmtsAutsResponse(WLAN0_IFACE_NAME, params));
         verify(mSupplicantStaNetworkMock).sendNetworkEapSimUmtsAutsResponse(eq(params));
     }
 
@@ -794,13 +862,13 @@
         executeAndValidateInitializationSequence();
 
         // This should work.
-        assertTrue(mDut.setWpsDeviceType(validDeviceTypeStr));
+        assertTrue(mDut.setWpsDeviceType(WLAN0_IFACE_NAME, validDeviceTypeStr));
         verify(mISupplicantStaIfaceMock).setWpsDeviceType(eq(expectedDeviceType));
 
         // This should not work
-        assertFalse(mDut.setWpsDeviceType(invalidDeviceType1Str));
+        assertFalse(mDut.setWpsDeviceType(WLAN0_IFACE_NAME, invalidDeviceType1Str));
         // This should not work
-        assertFalse(mDut.setWpsDeviceType(invalidDeviceType2Str));
+        assertFalse(mDut.setWpsDeviceType(WLAN0_IFACE_NAME, invalidDeviceType2Str));
     }
 
     /**
@@ -817,12 +885,12 @@
         executeAndValidateInitializationSequence();
 
         // This should work.
-        assertTrue(mDut.setWpsConfigMethods(validConfigMethodsStr));
+        assertTrue(mDut.setWpsConfigMethods(WLAN0_IFACE_NAME, validConfigMethodsStr));
         verify(mISupplicantStaIfaceMock).setWpsConfigMethods(eq(expectedConfigMethods));
 
         // This should throw an illegal argument exception.
         try {
-            assertFalse(mDut.setWpsConfigMethods(invalidConfigMethodsStr));
+            assertFalse(mDut.setWpsConfigMethods(WLAN0_IFACE_NAME, invalidConfigMethodsStr));
         } catch (IllegalArgumentException e) {
             return;
         }
@@ -845,7 +913,8 @@
                 new ISupplicantStaIfaceCallback.Hs20AnqpData());
 
         ArgumentCaptor<AnqpEvent> anqpEventCaptor = ArgumentCaptor.forClass(AnqpEvent.class);
-        verify(mWifiMonitor).broadcastAnqpDoneEvent(eq(WLAN_IFACE_NAME), anqpEventCaptor.capture());
+        verify(mWifiMonitor).broadcastAnqpDoneEvent(
+                eq(WLAN0_IFACE_NAME), anqpEventCaptor.capture());
         assertEquals(
                 ByteBufferReader.readInteger(
                         ByteBuffer.wrap(bssid), ByteOrder.BIG_ENDIAN, bssid.length),
@@ -867,7 +936,8 @@
                 bssid, ICON_FILE_NAME, NativeUtil.byteArrayToArrayList(iconData));
 
         ArgumentCaptor<IconEvent> iconEventCaptor = ArgumentCaptor.forClass(IconEvent.class);
-        verify(mWifiMonitor).broadcastIconDoneEvent(eq(WLAN_IFACE_NAME), iconEventCaptor.capture());
+        verify(mWifiMonitor).broadcastIconDoneEvent(
+                eq(WLAN0_IFACE_NAME), iconEventCaptor.capture());
         assertEquals(
                 ByteBufferReader.readInteger(
                         ByteBuffer.wrap(bssid), ByteOrder.BIG_ENDIAN, bssid.length),
@@ -890,7 +960,7 @@
                 bssid, osuMethod, HS20_URL);
 
         ArgumentCaptor<WnmData> wnmDataCaptor = ArgumentCaptor.forClass(WnmData.class);
-        verify(mWifiMonitor).broadcastWnmEvent(eq(WLAN_IFACE_NAME), wnmDataCaptor.capture());
+        verify(mWifiMonitor).broadcastWnmEvent(eq(WLAN0_IFACE_NAME), wnmDataCaptor.capture());
         assertEquals(
                 ByteBufferReader.readInteger(
                         ByteBuffer.wrap(bssid), ByteOrder.BIG_ENDIAN, bssid.length),
@@ -931,7 +1001,7 @@
 
         // Can't compare WifiSsid instances because they lack an equals.
         verify(mWifiMonitor).broadcastSupplicantStateChangeEvent(
-                eq(WLAN_IFACE_NAME), eq(WifiConfiguration.INVALID_NETWORK_ID),
+                eq(WLAN0_IFACE_NAME), eq(WifiConfiguration.INVALID_NETWORK_ID),
                 any(WifiSsid.class), eq(BSSID), eq(SupplicantState.INACTIVE));
     }
 
@@ -951,7 +1021,7 @@
                 NativeUtil.decodeSsid(SUPPLICANT_SSID));
 
         verify(mWifiMonitor).broadcastSupplicantStateChangeEvent(
-                eq(WLAN_IFACE_NAME), eq(frameworkNetworkId),
+                eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId),
                 any(WifiSsid.class), eq(BSSID), eq(SupplicantState.ASSOCIATED));
     }
 
@@ -972,9 +1042,9 @@
                 NativeUtil.decodeSsid(SUPPLICANT_SSID));
 
         wifiMonitorInOrder.verify(mWifiMonitor).broadcastNetworkConnectionEvent(
-                eq(WLAN_IFACE_NAME), eq(frameworkNetworkId), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId), eq(BSSID));
         wifiMonitorInOrder.verify(mWifiMonitor).broadcastSupplicantStateChangeEvent(
-                eq(WLAN_IFACE_NAME), eq(frameworkNetworkId),
+                eq(WLAN0_IFACE_NAME), eq(frameworkNetworkId),
                 any(WifiSsid.class), eq(BSSID), eq(SupplicantState.COMPLETED));
     }
 
@@ -990,12 +1060,12 @@
         mISupplicantStaIfaceCallback.onDisconnected(
                 NativeUtil.macAddressToByteArray(BSSID), true, reasonCode);
         verify(mWifiMonitor).broadcastNetworkDisconnectionEvent(
-                eq(WLAN_IFACE_NAME), eq(1), eq(reasonCode), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), eq(1), eq(reasonCode), eq(BSSID));
 
         mISupplicantStaIfaceCallback.onDisconnected(
                 NativeUtil.macAddressToByteArray(BSSID), false, reasonCode);
         verify(mWifiMonitor).broadcastNetworkDisconnectionEvent(
-                eq(WLAN_IFACE_NAME), eq(0), eq(reasonCode), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), eq(0), eq(reasonCode), eq(BSSID));
     }
 
     /**
@@ -1025,7 +1095,7 @@
         mISupplicantStaIfaceCallback.onDisconnected(
                 NativeUtil.macAddressToByteArray(BSSID), false, reasonCode);
 
-        verify(mWifiMonitor, times(2)).broadcastAuthenticationFailureEvent(eq(WLAN_IFACE_NAME),
+        verify(mWifiMonitor, times(2)).broadcastAuthenticationFailureEvent(eq(WLAN0_IFACE_NAME),
                 eq(WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD));
 
     }
@@ -1040,7 +1110,7 @@
         executeAndValidateInitializationSequence();
         assertNotNull(mISupplicantStaIfaceCallback);
 
-        int reasonCode = 17; // IEEE 802.11i WLAN_REASON_IE_IN_4WAY_DIFFERS
+        int reasonCode = ISupplicantStaIfaceCallback.ReasonCode.IE_IN_4WAY_DIFFERS;
 
         mISupplicantStaIfaceCallback.onStateChanged(
                 ISupplicantStaIfaceCallback.State.FOURWAY_HANDSHAKE,
@@ -1052,6 +1122,20 @@
         verify(mWifiMonitor, times(0)).broadcastAuthenticationFailureEvent(any(), anyInt());
     }
 
+    /**
+     * Tests the handling of eap failure during disconnect.
+     */
+    @Test
+    public void testEapFailure() throws Exception {
+        executeAndValidateInitializationSequence();
+        assertNotNull(mISupplicantStaIfaceCallback);
+
+        int reasonCode = ISupplicantStaIfaceCallback.ReasonCode.IEEE_802_1X_AUTH_FAILED;
+        mISupplicantStaIfaceCallback.onDisconnected(
+                NativeUtil.macAddressToByteArray(BSSID), false, reasonCode);
+        verify(mWifiMonitor).broadcastAuthenticationFailureEvent(eq(WLAN0_IFACE_NAME),
+                eq(WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE));
+    }
 
     /**
      * Tests the handling of association rejection notification.
@@ -1065,7 +1149,7 @@
         mISupplicantStaIfaceCallback.onAssociationRejected(
                 NativeUtil.macAddressToByteArray(BSSID), statusCode, false);
         verify(mWifiMonitor).broadcastAssociationRejectionEvent(
-                eq(WLAN_IFACE_NAME), eq(statusCode), eq(false), eq(BSSID));
+                eq(WLAN0_IFACE_NAME), eq(statusCode), eq(false), eq(BSSID));
     }
 
     /**
@@ -1078,7 +1162,7 @@
 
         mISupplicantStaIfaceCallback.onAuthenticationTimeout(
                 NativeUtil.macAddressToByteArray(BSSID));
-        verify(mWifiMonitor).broadcastAuthenticationFailureEvent(eq(WLAN_IFACE_NAME),
+        verify(mWifiMonitor).broadcastAuthenticationFailureEvent(eq(WLAN0_IFACE_NAME),
                 eq(WifiManager.ERROR_AUTH_FAILURE_TIMEOUT));
     }
 
@@ -1092,20 +1176,22 @@
 
         mISupplicantStaIfaceCallback.onBssidChanged(
                 BssidChangeReason.ASSOC_START, NativeUtil.macAddressToByteArray(BSSID));
-        verify(mWifiMonitor).broadcastTargetBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
-        verify(mWifiMonitor, never()).broadcastAssociatedBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
+        verify(mWifiMonitor).broadcastTargetBssidEvent(eq(WLAN0_IFACE_NAME), eq(BSSID));
+        verify(mWifiMonitor, never()).broadcastAssociatedBssidEvent(
+                eq(WLAN0_IFACE_NAME), eq(BSSID));
 
         reset(mWifiMonitor);
         mISupplicantStaIfaceCallback.onBssidChanged(
                 BssidChangeReason.ASSOC_COMPLETE, NativeUtil.macAddressToByteArray(BSSID));
-        verify(mWifiMonitor, never()).broadcastTargetBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
-        verify(mWifiMonitor).broadcastAssociatedBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
+        verify(mWifiMonitor, never()).broadcastTargetBssidEvent(eq(WLAN0_IFACE_NAME), eq(BSSID));
+        verify(mWifiMonitor).broadcastAssociatedBssidEvent(eq(WLAN0_IFACE_NAME), eq(BSSID));
 
         reset(mWifiMonitor);
         mISupplicantStaIfaceCallback.onBssidChanged(
                 BssidChangeReason.DISASSOC, NativeUtil.macAddressToByteArray(BSSID));
-        verify(mWifiMonitor, never()).broadcastTargetBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
-        verify(mWifiMonitor, never()).broadcastAssociatedBssidEvent(eq(WLAN_IFACE_NAME), eq(BSSID));
+        verify(mWifiMonitor, never()).broadcastTargetBssidEvent(eq(WLAN0_IFACE_NAME), eq(BSSID));
+        verify(mWifiMonitor, never()).broadcastAssociatedBssidEvent(
+                eq(WLAN0_IFACE_NAME), eq(BSSID));
     }
 
     /**
@@ -1117,7 +1203,7 @@
         assertNotNull(mISupplicantStaIfaceCallback);
 
         mISupplicantStaIfaceCallback.onEapFailure();
-        verify(mWifiMonitor).broadcastAuthenticationFailureEvent(eq(WLAN_IFACE_NAME),
+        verify(mWifiMonitor).broadcastAuthenticationFailureEvent(eq(WLAN0_IFACE_NAME),
                 eq(WifiManager.ERROR_AUTH_FAILURE_EAP_FAILURE));
     }
 
@@ -1130,7 +1216,7 @@
         assertNotNull(mISupplicantStaIfaceCallback);
 
         mISupplicantStaIfaceCallback.onWpsEventSuccess();
-        verify(mWifiMonitor).broadcastWpsSuccessEvent(eq(WLAN_IFACE_NAME));
+        verify(mWifiMonitor).broadcastWpsSuccessEvent(eq(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -1145,7 +1231,7 @@
         short errorInd = ISupplicantStaIfaceCallback.WpsErrorIndication.SECURITY_WEP_PROHIBITED;
         mISupplicantStaIfaceCallback.onWpsEventFail(
                 NativeUtil.macAddressToByteArray(BSSID), cfgError, errorInd);
-        verify(mWifiMonitor).broadcastWpsFailEvent(eq(WLAN_IFACE_NAME),
+        verify(mWifiMonitor).broadcastWpsFailEvent(eq(WLAN0_IFACE_NAME),
                 eq((int) cfgError), eq((int) errorInd));
     }
 
@@ -1161,7 +1247,7 @@
         short errorInd = ISupplicantStaIfaceCallback.WpsErrorIndication.NO_ERROR;
         mISupplicantStaIfaceCallback.onWpsEventFail(
                 NativeUtil.macAddressToByteArray(BSSID), cfgError, errorInd);
-        verify(mWifiMonitor).broadcastWpsTimeoutEvent(eq(WLAN_IFACE_NAME));
+        verify(mWifiMonitor).broadcastWpsTimeoutEvent(eq(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -1173,7 +1259,7 @@
         assertNotNull(mISupplicantStaIfaceCallback);
 
         mISupplicantStaIfaceCallback.onWpsEventPbcOverlap();
-        verify(mWifiMonitor).broadcastWpsOverlapEvent(eq(WLAN_IFACE_NAME));
+        verify(mWifiMonitor).broadcastWpsOverlapEvent(eq(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -1184,11 +1270,13 @@
         executeAndValidateInitializationSequence();
         assertNotNull(mServiceManagerDeathCaptor.getValue());
         assertTrue(mDut.isInitializationComplete());
+        assertTrue(mDut.registerDeathHandler(mSupplicantHalDeathHandler));
 
         mServiceManagerDeathCaptor.getValue().serviceDied(5L);
 
         assertFalse(mDut.isInitializationComplete());
-        verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(WLAN_IFACE_NAME));
+        verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(WLAN0_IFACE_NAME));
+        verify(mSupplicantHalDeathHandler).onDeath();
     }
 
     /**
@@ -1199,11 +1287,13 @@
         executeAndValidateInitializationSequence();
         assertNotNull(mSupplicantDeathCaptor.getValue());
         assertTrue(mDut.isInitializationComplete());
+        assertTrue(mDut.registerDeathHandler(mSupplicantHalDeathHandler));
 
         mSupplicantDeathCaptor.getValue().serviceDied(5L);
 
         assertFalse(mDut.isInitializationComplete());
-        verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(WLAN_IFACE_NAME));
+        verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(WLAN0_IFACE_NAME));
+        verify(mSupplicantHalDeathHandler).onDeath();
     }
 
     /**
@@ -1218,7 +1308,7 @@
         mSupplicantStaIfaceDeathCaptor.getValue().serviceDied(5L);
 
         assertFalse(mDut.isInitializationComplete());
-        verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(WLAN_IFACE_NAME));
+        verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(WLAN0_IFACE_NAME));
     }
 
     /**
@@ -1268,17 +1358,17 @@
                 .thenReturn(mStatusSuccess);
 
         // Fail before initialization is performed.
-        assertFalse(mDut.startWpsRegistrar(null, null));
+        assertFalse(mDut.startWpsRegistrar(WLAN0_IFACE_NAME, null, null));
 
         executeAndValidateInitializationSequence();
 
-        assertFalse(mDut.startWpsRegistrar(null, null));
+        assertFalse(mDut.startWpsRegistrar(WLAN0_IFACE_NAME, null, null));
         verify(mISupplicantStaIfaceMock, never()).startWpsRegistrar(any(byte[].class), anyString());
 
-        assertFalse(mDut.startWpsRegistrar(new String(), "452233"));
+        assertFalse(mDut.startWpsRegistrar(WLAN0_IFACE_NAME, new String(), "452233"));
         verify(mISupplicantStaIfaceMock, never()).startWpsRegistrar(any(byte[].class), anyString());
 
-        assertTrue(mDut.startWpsRegistrar("45:23:12:12:12:98", "562535"));
+        assertTrue(mDut.startWpsRegistrar(WLAN0_IFACE_NAME, "45:23:12:12:12:98", "562535"));
         verify(mISupplicantStaIfaceMock).startWpsRegistrar(any(byte[].class), anyString());
     }
 
@@ -1293,15 +1383,15 @@
         byte[] anyBssidBytes = {0, 0, 0, 0, 0, 0};
 
         // Fail before initialization is performed.
-        assertFalse(mDut.startWpsPbc(bssid));
+        assertFalse(mDut.startWpsPbc(WLAN0_IFACE_NAME, bssid));
         verify(mISupplicantStaIfaceMock, never()).startWpsPbc(any(byte[].class));
 
         executeAndValidateInitializationSequence();
 
-        assertTrue(mDut.startWpsPbc(bssid));
+        assertTrue(mDut.startWpsPbc(WLAN0_IFACE_NAME, bssid));
         verify(mISupplicantStaIfaceMock).startWpsPbc(eq(bssidBytes));
 
-        assertTrue(mDut.startWpsPbc(null));
+        assertTrue(mDut.startWpsPbc(WLAN0_IFACE_NAME, null));
         verify(mISupplicantStaIfaceMock).startWpsPbc(eq(anyBssidBytes));
     }
 
@@ -1322,7 +1412,7 @@
                 bssid, reasonCode, reauthDelay, HS20_URL);
 
         ArgumentCaptor<WnmData> wnmDataCaptor = ArgumentCaptor.forClass(WnmData.class);
-        verify(mWifiMonitor).broadcastWnmEvent(eq(WLAN_IFACE_NAME), wnmDataCaptor.capture());
+        verify(mWifiMonitor).broadcastWnmEvent(eq(WLAN0_IFACE_NAME), wnmDataCaptor.capture());
         assertEquals(
                 ByteBufferReader.readInteger(
                         ByteBuffer.wrap(bssid), ByteOrder.BIG_ENDIAN, bssid.length),
@@ -1399,7 +1489,8 @@
         // act: cause the onRegistration(...) callback to execute
         mServiceNotificationCaptor.getValue().onRegistration(ISupplicant.kInterfaceName, "", true);
 
-        assertTrue(mDut.isInitializationComplete() == shouldSucceed);
+        assertTrue(mDut.isInitializationComplete());
+        assertTrue(mDut.setupIface(WLAN0_IFACE_NAME) == shouldSucceed);
         mInOrder.verify(mISupplicantMock).linkToDeath(mSupplicantDeathCaptor.capture(),
                 anyLong());
         // verify: listInterfaces is called
@@ -1411,7 +1502,8 @@
                             any(ISupplicant.getInterfaceCallback.class));
         }
         if (causeRemoteException) {
-            mInOrder.verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(eq(null));
+            mInOrder.verify(mWifiMonitor).broadcastSupplicantDisconnectionEvent(
+                    eq(WLAN0_IFACE_NAME));
         }
         if (!causeRemoteException && !getZeroInterfaces && !getNullInterface) {
             mInOrder.verify(mISupplicantStaIfaceMock).linkToDeath(
@@ -1529,7 +1621,7 @@
         setupMocksForConnectSequence(haveExistingNetwork);
         WifiConfiguration config = new WifiConfiguration();
         config.networkId = newFrameworkNetworkId;
-        assertTrue(mDut.connectToNetwork(config));
+        assertTrue(mDut.connectToNetwork(WLAN0_IFACE_NAME, config));
         validateConnectSequence(haveExistingNetwork, 1);
         return config;
     }
@@ -1566,7 +1658,7 @@
         WifiConfiguration roamingConfig = new WifiConfiguration();
         roamingConfig.networkId = roamNetworkId;
         roamingConfig.getNetworkSelectionStatus().setNetworkSelectionBSSID(roamBssid);
-        assertTrue(mDut.roamToNetwork(roamingConfig));
+        assertTrue(mDut.roamToNetwork(WLAN0_IFACE_NAME, roamingConfig));
 
         if (!sameNetwork) {
             validateConnectSequence(false, 2);
diff --git a/tests/wifitests/src/com/android/server/wifi/TestUtil.java b/tests/wifitests/src/com/android/server/wifi/TestUtil.java
index a0a1030..ab2cda5 100644
--- a/tests/wifitests/src/com/android/server/wifi/TestUtil.java
+++ b/tests/wifitests/src/com/android/server/wifi/TestUtil.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
+import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 
 import java.util.ArrayList;
@@ -42,6 +43,17 @@
     }
 
     /**
+     * Send {@link WifiManager#NETWORK_STATE_CHANGED_ACTION} broadcast.
+     */
+    public static void sendNetworkStateChanged(BroadcastReceiver broadcastReceiver,
+            Context context, NetworkInfo nwInfo, WifiInfo wifiInfo) {
+        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, nwInfo);
+        intent.putExtra(WifiManager.EXTRA_WIFI_INFO, wifiInfo);
+        broadcastReceiver.onReceive(context, intent);
+    }
+
+    /**
      * Send {@link WifiManager#SCAN_RESULTS_AVAILABLE_ACTION} broadcast.
      */
     public static void sendScanResultsAvailable(BroadcastReceiver broadcastReceiver,
@@ -85,7 +97,6 @@
         }
         intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, ifaceName);
         intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mode);
-
         broadcastReceiver.onReceive(context, intent);
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/VelocityBasedConnectedScoreTest.java b/tests/wifitests/src/com/android/server/wifi/VelocityBasedConnectedScoreTest.java
new file mode 100644
index 0000000..6a72522
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/VelocityBasedConnectedScoreTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.wifi.WifiInfo;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.VelocityBasedConnectedScore}.
+ */
+public class VelocityBasedConnectedScoreTest {
+
+    private static final int CELLULAR_THRESHOLD_SCORE = 50;
+
+    class FakeClock extends Clock {
+        long mWallClockMillis = 1500000000000L;
+        int mStepMillis = 3001;
+
+        @Override
+        public long getWallClockMillis() {
+            mWallClockMillis += mStepMillis;
+            return mWallClockMillis;
+        }
+    }
+
+    FakeClock mClock;
+    VelocityBasedConnectedScore mVelocityBasedConnectedScore;
+    ScanDetailCache mScanDetailCache;
+    WifiInfo mWifiInfo;
+    int mRssiExitThreshold2GHz;
+    int mRssiExitThreshold5GHz;
+    @Mock Context mContext;
+    @Spy private MockResources mResources = new MockResources();
+
+    private int setupIntegerResource(int resourceName, int value) {
+        doReturn(value).when(mResources).getInteger(resourceName);
+        return value;
+    }
+
+    /**
+     * Sets up resource values for testing
+     *
+     * See frameworks/base/core/res/res/values/config.xml
+     */
+    private void setUpResources(Resources resources) {
+        mRssiExitThreshold2GHz = setupIntegerResource(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz, -83);
+        mRssiExitThreshold5GHz = setupIntegerResource(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz, -80);
+    }
+
+    /**
+     * Sets up for unit test
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        setUpResources(mResources);
+        mWifiInfo = new WifiInfo();
+        mWifiInfo.setFrequency(2412);
+        when(mContext.getResources()).thenReturn(mResources);
+        mClock = new FakeClock();
+        mVelocityBasedConnectedScore = new VelocityBasedConnectedScore(mContext, mClock);
+    }
+
+    /**
+     *
+     * Low RSSI, but some data is moving and error rate is low.
+     *
+     * Expect a score above threshold.
+     */
+    @Test
+    public void allowLowRssiIfErrorRateIsLowAndSomeDataIsMoving() throws Exception {
+        mWifiInfo.setRssi(mRssiExitThreshold2GHz - 2);
+        mWifiInfo.setLinkSpeed(6); // Mbps
+        mWifiInfo.txSuccessRate = 2.1; // proportional to pps
+        mWifiInfo.txBadRate = .5;
+        mWifiInfo.rxSuccessRate = 2.1;
+        for (int i = 0; i < 10; i++) {
+            mVelocityBasedConnectedScore.updateUsingWifiInfo(mWifiInfo,
+                    mClock.getWallClockMillis());
+        }
+        int score = mVelocityBasedConnectedScore.generateScore();
+        assertTrue(score > CELLULAR_THRESHOLD_SCORE);
+    }
+
+    /**
+     *
+     * Low RSSI, and almost no data is moving.
+     *
+     * Expect a score below threshold.
+     */
+    @Test
+    public void disallowLowRssiIfDataIsNotMoving() throws Exception {
+        mWifiInfo.setRssi(mRssiExitThreshold2GHz - 1);
+        mWifiInfo.setLinkSpeed(6); // Mbps
+        mWifiInfo.txSuccessRate = .1; // proportional to pps
+        mWifiInfo.txBadRate = 0;
+        mWifiInfo.rxSuccessRate = .1;
+        for (int i = 0; i < 10; i++) {
+            mVelocityBasedConnectedScore.updateUsingWifiInfo(mWifiInfo,
+                    mClock.getWallClockMillis());
+        }
+        int score = mVelocityBasedConnectedScore.generateScore();
+        assertTrue(score < CELLULAR_THRESHOLD_SCORE);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java
new file mode 100644
index 0000000..6db9cf5
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WakeupConfigStoreDataTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 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 static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import com.google.android.collect.Sets;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link WakeupConfigStoreData}.
+ */
+public class WakeupConfigStoreDataTest {
+
+    @Mock private WakeupConfigStoreData.DataSource<Boolean> mActiveDataSource;
+    @Mock private WakeupConfigStoreData.DataSource<Set<ScanResultMatchInfo>> mNetworkDataSource;
+
+    private WakeupConfigStoreData mWakeupConfigData;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mWakeupConfigData = new WakeupConfigStoreData(mActiveDataSource, mNetworkDataSource);
+    }
+
+    /**
+     * Helper function for serializing configuration data to a XML block.
+     *
+     * @param shared Flag indicating serializing shared or user configurations
+     * @return byte[] of the XML data
+     */
+    private byte[] serializeData(boolean shared) throws Exception {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        mWakeupConfigData.serializeData(out, shared);
+        out.flush();
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Helper function for parsing configuration data from a XML block.
+     *
+     * @param data XML data to parse from
+     * @param shared Flag indicating parsing of shared or user configurations
+     */
+    private void deserializeData(byte[] data, boolean shared) throws Exception {
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        mWakeupConfigData.deserializeData(in, in.getDepth(), shared);
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when attempting to serialize data
+     * to the share store.
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void serializeShareDataThrowsException() throws Exception {
+        serializeData(true /* shared */);
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when attempting to deserialize
+     * data from the share store.
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void deserializeShareDataThrowsException() throws Exception {
+        deserializeData(new byte[0], true /* shared */);
+    }
+
+    /**
+     * Can correctly serialize and deserialize the empty set case.
+     */
+    @Test
+    public void deserializeSerializedData_emptySet() throws Exception {
+        Set<ScanResultMatchInfo> networks = Collections.emptySet();
+        boolean isActive = false;
+
+        when(mActiveDataSource.getData()).thenReturn(isActive);
+        when(mNetworkDataSource.getData()).thenReturn(networks);
+
+        byte[] bytes = serializeData(false /* shared */);
+        deserializeData(bytes, false /* shared */);
+
+        verify(mActiveDataSource).setData(eq(isActive));
+        verify(mNetworkDataSource).setData(eq(networks));
+    }
+
+    /**
+     * Can correctly serialize and deserialize a standard working case.
+     */
+    @Test
+    public void deserializeSerializedData() throws Exception {
+        ScanResultMatchInfo network1 = new ScanResultMatchInfo();
+        network1.networkSsid = "ssid 1";
+        network1.networkType = 0;
+
+        ScanResultMatchInfo network2 = new ScanResultMatchInfo();
+        network2.networkSsid = ",.23;4@, .#,%(,";
+        network2.networkType = 1;
+
+        ScanResultMatchInfo network3 = new ScanResultMatchInfo();
+        network3.networkSsid = "";
+        network3.networkType = 2;
+
+        Set<ScanResultMatchInfo> networks = Sets.newArraySet(network1, network2, network3);
+        boolean isActive = true;
+
+        when(mActiveDataSource.getData()).thenReturn(isActive);
+        when(mNetworkDataSource.getData()).thenReturn(networks);
+
+        byte[] bytes = serializeData(false /* shared */);
+        deserializeData(bytes, false /* shared */);
+
+        verify(mActiveDataSource).setData(eq(isActive));
+        verify(mNetworkDataSource).setData(eq(networks));
+    }
+
+    /**
+     * Verify that reset data wipes the data sources.
+     */
+    @Test
+    public void resetDataWipesDataSources() {
+        mWakeupConfigData.resetData(false /* shared */);
+
+        verify(mActiveDataSource).setData(false);
+        verify(mNetworkDataSource).setData(eq(Collections.emptySet()));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
new file mode 100644
index 0000000..5e45570
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+
+/**
+ * Unit tests for {@link WakeupController}.
+ */
+public class WakeupControllerTest {
+
+    @Mock private Context mContext;
+    @Mock private WakeupLock mWakeupLock;
+    @Mock private WifiConfigManager mWifiConfigManager;
+    @Mock private WifiConfigStore mWifiConfigStore;
+    @Mock private FrameworkFacade mFrameworkFacade;
+
+    private TestLooper mLooper;
+    private WakeupController mWakeupController;
+
+    /** Initialize objects before each test run. */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mLooper = new TestLooper();
+    }
+
+    private WakeupController newWakeupController() {
+        return new WakeupController(mContext, mLooper.getLooper(), mWakeupLock, mWifiConfigManager,
+                mWifiConfigStore, mFrameworkFacade);
+    }
+
+    /**
+     * Verify WakeupController is enabled when the settings toggle is true.
+     */
+    @Test
+    public void verifyEnabledWhenToggledOn() {
+        when(mFrameworkFacade.getIntegerSetting(mContext,
+                Settings.Global.WIFI_WAKEUP_ENABLED, 0)).thenReturn(1);
+        mWakeupController = newWakeupController();
+
+        assertTrue(mWakeupController.isEnabled());
+    }
+
+    /**
+     * Verify WakeupController is disabled when the settings toggle is false.
+     */
+    @Test
+    public void verifyDisabledWhenToggledOff() {
+        when(mFrameworkFacade.getIntegerSetting(mContext,
+                Settings.Global.WIFI_WAKEUP_ENABLED, 0)).thenReturn(0);
+        mWakeupController = newWakeupController();
+
+        assertFalse(mWakeupController.isEnabled());
+    }
+
+    /**
+     * Verify WakeupController registers its store data with the WifiConfigStore on construction.
+     */
+    @Test
+    public void registersWakeupConfigStoreData() {
+        mWakeupController = newWakeupController();
+        verify(mWifiConfigStore).registerStoreData(any(WakeupConfigStoreData.class));
+    }
+
+    /**
+     * Verify that dump calls also dump the state of the WakeupLock.
+     */
+    @Test
+    public void dumpIncludesWakeupLock() {
+        mWakeupController = newWakeupController();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        PrintWriter writer = new PrintWriter(stream);
+        mWakeupController.dump(null, writer, null);
+
+        verify(mWakeupLock).dump(null, writer, null);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WakeupLockTest.java b/tests/wifitests/src/com/android/server/wifi/WakeupLockTest.java
new file mode 100644
index 0000000..7144ecf
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WakeupLockTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Unit tests for {@link WakeupLock}.
+ */
+public class WakeupLockTest {
+
+    private static final String SSID_1 = "ssid1";
+    private static final String SSID_2 = "ssid2";
+
+    @Mock private WifiConfigManager mWifiConfigManager;
+
+    private ScanResultMatchInfo mNetwork1;
+    private ScanResultMatchInfo mNetwork2;
+    private WakeupLock mWakeupLock;
+
+    /**
+     * Initialize objects before each test run.
+     */
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mNetwork1 = new ScanResultMatchInfo();
+        mNetwork1.networkSsid = SSID_1;
+        mNetwork1.networkType = ScanResultMatchInfo.NETWORK_TYPE_OPEN;
+
+        mNetwork2 = new ScanResultMatchInfo();
+        mNetwork2.networkSsid = SSID_2;
+        mNetwork2.networkType = ScanResultMatchInfo.NETWORK_TYPE_EAP;
+
+        mWakeupLock = new WakeupLock(mWifiConfigManager);
+    }
+
+    /**
+     * Updates the lock enough times to evict any networks not passed in.
+     *
+     * <p>It calls update {@link WakeupLock#CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT} times with
+     * the given network list. It asserts that the lock isn't empty prior to each call to update.
+     */
+    private void updateEnoughTimesToEvictWithAsserts(Collection<ScanResultMatchInfo> networks) {
+        for (int i = 0; i < WakeupLock.CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT; i++) {
+            assertFalse("Lock empty after " + i + " scans", mWakeupLock.isEmpty());
+            mWakeupLock.update(networks);
+        }
+    }
+
+    /**
+     * Updates the lock enough times to evict any networks not passed in.
+     *
+     * <p>It calls update {@link WakeupLock#CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT} times with
+     * the given network list. It does not make any assertions about the state of the lock.
+     */
+    private void updateEnoughTimesToEvictWithoutAsserts(Collection<ScanResultMatchInfo> networks) {
+        for (int i = 0; i < WakeupLock.CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT; i++) {
+            mWakeupLock.update(networks);
+        }
+    }
+
+    /**
+     * Verify that the WakeupLock is not empty immediately after being initialized with networks.
+     */
+    @Test
+    public void verifyNotEmptyWhenInitializedWithNetworkList() {
+        mWakeupLock.initialize(Arrays.asList(mNetwork1, mNetwork2));
+        assertFalse(mWakeupLock.isEmpty());
+    }
+
+    /**
+     * Verify that the WakeupLock is empty when initialized with an empty list.
+     */
+    @Test
+    public void isEmptyWhenInitializedWithEmptyList() {
+        mWakeupLock.initialize(Collections.emptyList());
+        assertTrue(mWakeupLock.isEmpty());
+    }
+
+    /**
+     * Verify that initializing the WakeupLock clears out previous entries.
+     */
+    @Test
+    public void initializingLockClearsPreviousNetworks() {
+        mWakeupLock.initialize(Collections.singletonList(mNetwork1));
+        assertFalse(mWakeupLock.isEmpty());
+
+        mWakeupLock.initialize(Collections.emptyList());
+        assertTrue(mWakeupLock.isEmpty());
+    }
+
+    /**
+     * Updating the lock should evict scan results that haven't been seen in
+     * {@link WakeupLock#CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT} scans.
+     */
+    @Test
+    public void updateShouldRemoveNetworksAfterConsecutiveMissedScans() {
+        mWakeupLock.initialize(Collections.singletonList(mNetwork1));
+
+        updateEnoughTimesToEvictWithAsserts(Collections.singletonList(mNetwork2));
+
+        assertTrue(mWakeupLock.isEmpty());
+    }
+
+    /**
+     * Ensure that missed scans must be consecutive in order to evict networks from lock.
+     */
+    @Test
+    public void updateWithLockedNetworkShouldResetRequiredNumberOfScans() {
+        List<ScanResultMatchInfo> lockedNetworks = Collections.singletonList(mNetwork1);
+        List<ScanResultMatchInfo> updateNetworks = Collections.singletonList(mNetwork2);
+
+        mWakeupLock.initialize(lockedNetworks);
+
+        // one update without network
+        mWakeupLock.update(updateNetworks);
+        // one update with network
+        mWakeupLock.update(lockedNetworks);
+
+        updateEnoughTimesToEvictWithAsserts(updateNetworks);
+
+        assertTrue(mWakeupLock.isEmpty());
+    }
+
+    /**
+     * Once a network is removed from the lock, it should not be reset even if it's seen again.
+     */
+    @Test
+    public void updateWithLockedNetworkAfterItIsRemovedDoesNotReset() {
+        List<ScanResultMatchInfo> lockedNetworks = Collections.singletonList(mNetwork1);
+        mWakeupLock.initialize(lockedNetworks);
+
+        updateEnoughTimesToEvictWithAsserts(Collections.emptyList());
+
+        assertTrue(mWakeupLock.isEmpty());
+        mWakeupLock.update(lockedNetworks);
+        assertTrue(mWakeupLock.isEmpty());
+    }
+
+    /**
+     * Verify that networks can be incrementally removed from the lock. Their counters should be
+     * independent.
+     */
+    @Test
+    public void networksCanBeRemovedIncrementallyFromLock() {
+        List<ScanResultMatchInfo> lockedNetworks = Arrays.asList(mNetwork1, mNetwork2);
+        mWakeupLock.initialize(lockedNetworks);
+
+        updateEnoughTimesToEvictWithAsserts(Collections.singletonList(mNetwork1));
+        assertFalse(mWakeupLock.isEmpty());
+
+        updateEnoughTimesToEvictWithAsserts(Collections.singletonList(mNetwork2));
+        assertTrue(mWakeupLock.isEmpty());
+    }
+
+    /**
+     * Verify that initializing the lock persists the SSID list to the config store.
+     */
+    @Test
+    public void initializeShouldSaveSsidsToStore() {
+        mWakeupLock.initialize(Collections.singletonList(mNetwork1));
+        verify(mWifiConfigManager).saveToStore(eq(false));
+    }
+
+    /**
+     * Verify that update saves to store if the lock changes.
+     */
+    @Test
+    public void updateShouldOnlySaveIfLockChanges() {
+        mWakeupLock.initialize(Collections.singletonList(mNetwork1));
+        updateEnoughTimesToEvictWithoutAsserts(Collections.emptyList());
+
+        // need exactly 2 invocations: 1 for initialize, 1 for successful update
+        verify(mWifiConfigManager, times(2)).saveToStore(eq(false));
+    }
+
+    /**
+     * Verify that update does not save to store if the lock does not change.
+     */
+    @Test
+    public void updateShouldNotSaveIfLockDoesNotChange() {
+        mWakeupLock.update(Collections.singletonList(mNetwork1));
+        verify(mWifiConfigManager, never()).saveToStore(anyBoolean());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiCertManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiCertManagerTest.java
deleted file mode 100644
index 04c2802..0000000
--- a/tests/wifitests/src/com/android/server/wifi/WifiCertManagerTest.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-
-import android.content.Context;
-import android.os.UserHandle;
-import android.security.Credentials;
-import android.security.KeyStore;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.Mock;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.HashSet;
-
-/**
- * Unit tests for {@link com.android.server.wifi.WifiCertManager}.
- */
-@SmallTest
-public class WifiCertManagerTest {
-    private static final String TAG = "WifiCertManagerTest";
-    private byte[] mConfig;
-    private String mConfigFile = "";
-
-    @Mock private Context mContext;
-    @Rule public TemporaryFolder mTempFolder = new TemporaryFolder();
-
-    public WifiCertManagerTest() {
-        mConfig = null;
-    }
-
-    @Before
-    public void setUp() {
-        try {
-            File configFile = mTempFolder.newFile();
-            mConfigFile = configFile.getAbsolutePath();
-            configFile.delete();
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to construct test", e);
-        }
-    }
-
-    /**
-     * This class is created to avoid mocking file system and KeyStore.
-     */
-    private class TestWifiCertManager extends WifiCertManager {
-        private boolean mAffiliatedUser;
-
-        public TestWifiCertManager(Context context) {
-            super(context, mConfigFile);
-            mAffiliatedUser = false;
-        }
-
-        protected String[] listClientCertsForAllUsers() {
-            String prefix = Credentials.USER_PRIVATE_KEY;
-            String mockAnswer[] = {prefix + "abc", prefix + "def", prefix + "ghi"};
-            return mockAnswer;
-        }
-
-        protected byte[] readConfigFile() {
-            return mConfig;
-        }
-
-        protected void writeConfigFile(byte[] payload) {
-            mConfig = payload;
-        }
-
-        protected boolean isAffiliatedUser() {
-            return mAffiliatedUser;
-        }
-
-        public void setAffiliatedUser(boolean value) {
-            mAffiliatedUser = value;
-        }
-    }
-
-    @Test
-    public void testEmptyConfigFile() {
-        WifiCertManager certManager = new WifiCertManager(mContext, mConfigFile);
-        final String[] expected =
-                KeyStore.getInstance().list(
-                        Credentials.USER_PRIVATE_KEY, UserHandle.myUserId());
-        assertArrayEquals(expected, certManager.listClientCertsForCurrentUser());
-    }
-
-    @Test
-    public void testOperations() {
-        TestWifiCertManager certManager = new TestWifiCertManager(mContext);
-        final HashSet<String> expected1 = new HashSet<>();
-        String prefix = Credentials.USER_PRIVATE_KEY;
-        expected1.add(prefix + "abc");
-        expected1.add(prefix + "def");
-        expected1.add(prefix + "ghi");
-
-        final HashSet<String> expected2 = new HashSet<>();
-        expected2.add(prefix + "abc");
-
-        certManager.setAffiliatedUser(false);
-        assertEquals(expected1,
-                new HashSet<>(Arrays.asList(certManager.listClientCertsForCurrentUser())));
-
-        certManager.hideCertFromUnaffiliatedUsers("def");
-        certManager.hideCertFromUnaffiliatedUsers("ghi");
-        assertEquals(expected2,
-                new HashSet<>(Arrays.asList(certManager.listClientCertsForCurrentUser())));
-
-        certManager.setAffiliatedUser(true);
-        assertEquals(expected1,
-                new HashSet<>(Arrays.asList(certManager.listClientCertsForCurrentUser())));
-
-        TestWifiCertManager certManager2 = new TestWifiCertManager(mContext);
-        certManager2.setAffiliatedUser(false);
-        assertEquals(expected2,
-                new HashSet<>(Arrays.asList(certManager2.listClientCertsForCurrentUser())));
-
-        certManager2.setAffiliatedUser(true);
-        assertEquals(expected1,
-                new HashSet<>(Arrays.asList(certManager2.listClientCertsForCurrentUser())));
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
index 0fa6600..a2c989a 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
@@ -84,6 +84,7 @@
     private static final String TEST_CREATOR_NAME = "com.wificonfigmanager.creator";
     private static final String TEST_UPDATE_NAME = "com.wificonfigmanager.update";
     private static final String TEST_NO_PERM_NAME = "com.wificonfigmanager.noperm";
+    private static final String TEST_WIFI_NAME = "android.uid.system";
     private static final String TEST_DEFAULT_GW_MAC_ADDRESS = "0f:67:ad:ef:09:34";
     private static final String TEST_STATIC_PROXY_HOST_1 = "192.168.48.1";
     private static final int    TEST_STATIC_PROXY_PORT_1 = 8000;
@@ -149,6 +150,8 @@
                     return WifiConfigManager.SYSUI_PACKAGE_NAME;
                 } else if (uid == TEST_NO_PERM_UID) {
                     return TEST_NO_PERM_NAME;
+                } else if (uid == Process.WIFI_UID) {
+                    return TEST_WIFI_NAME;
                 }
                 fail("Unexpected UID: " + uid);
                 return "";
@@ -793,7 +796,7 @@
 
     /**
      * Verifies the updation of network's connectUid using
-     * {@link WifiConfigManager#checkAndUpdateLastConnectUid(int, int)}.
+     * {@link WifiConfigManager#updateLastConnectUid(int, int)}.
      */
     @Test
     public void testUpdateLastConnectUid() throws Exception {
@@ -802,21 +805,11 @@
         NetworkUpdateResult result = verifyAddNetworkToWifiConfigManager(openNetwork);
 
         assertTrue(
-                mWifiConfigManager.checkAndUpdateLastConnectUid(
+                mWifiConfigManager.updateLastConnectUid(
                         result.getNetworkId(), TEST_CREATOR_UID));
         WifiConfiguration retrievedNetwork =
                 mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
         assertEquals(TEST_CREATOR_UID, retrievedNetwork.lastConnectUid);
-
-        when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(false);
-
-        // Now try to update the last connect UID with |TEST_UPDATE_UID|, it should fail and
-        // the lastConnectUid should remain the same.
-        assertFalse(
-                mWifiConfigManager.checkAndUpdateLastConnectUid(
-                        result.getNetworkId(), TEST_UPDATE_UID));
-        retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(result.getNetworkId());
-        assertEquals(TEST_CREATOR_UID, retrievedNetwork.lastConnectUid);
     }
 
     /**
@@ -864,7 +857,7 @@
      * This invokes {@link WifiConfigManager#enableNetwork(int, boolean, int)},
      * {@link WifiConfigManager#disableNetwork(int, int)},
      * {@link WifiConfigManager#updateNetworkSelectionStatus(int, int)} and
-     * {@link WifiConfigManager#checkAndUpdateLastConnectUid(int, int)}.
+     * {@link WifiConfigManager#updateLastConnectUid(int, int)}.
      */
     @Test
     public void testChangeConfigurationWithInvalidNetworkId() {
@@ -877,7 +870,7 @@
         assertFalse(mWifiConfigManager.disableNetwork(result.getNetworkId() + 1, TEST_CREATOR_UID));
         assertFalse(mWifiConfigManager.updateNetworkSelectionStatus(
                 result.getNetworkId() + 1, NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER));
-        assertFalse(mWifiConfigManager.checkAndUpdateLastConnectUid(
+        assertFalse(mWifiConfigManager.updateLastConnectUid(
                 result.getNetworkId() + 1, TEST_CREATOR_UID));
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
index 2925273..358f3b6 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java
@@ -44,6 +44,8 @@
     static final int OTHER_USER_ID = 11;
     static final String TEST_SSID = "test_ssid";
     static final String TEST_SSID_1 = "test_ssid_1";
+    static final String TEST_BSSID = "aa:aa:11:22:cc:dd";
+    static final String TEST_BSSID_1 = "11:22:11:22:cc:dd";
     static final List<UserInfo> PROFILES = Arrays.asList(
             new UserInfo(CURRENT_USER_ID, "owner", 0),
             new UserInfo(CURRENT_USER_MANAGED_PROFILE_USER_ID, "managed profile", 0));
@@ -474,6 +476,19 @@
     }
 
     /**
+     * Verify that WifiConfigurationUtil.isSameNetwork returns true when two WifiConfiguration
+     * objects have the same parameters but different network selection BSSID's.
+     */
+    @Test
+    public void testIsSameNetworkReturnsTrueOnSameNetworkWithDifferentBSSID() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
+        network.getNetworkSelectionStatus().setNetworkSelectionBSSID(TEST_BSSID);
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork(TEST_SSID);
+        network1.getNetworkSelectionStatus().setNetworkSelectionBSSID(TEST_BSSID_1);
+        assertTrue(WifiConfigurationUtil.isSameNetwork(network, network1));
+    }
+
+    /**
      * Verify that WifiConfigurationUtil.isSameNetwork returns false when two WifiConfiguration
      * objects have the different SSIDs.
      */
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiLinkLayerStatsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiLinkLayerStatsTest.java
new file mode 100644
index 0000000..0189aed
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiLinkLayerStatsTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 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 static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Random;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.WifiLinkLayerStats}.
+ */
+public class WifiLinkLayerStatsTest {
+
+    ExtendedWifiInfo mWifiInfo;
+    WifiLinkLayerStats mWifiLinkLayerStats;
+    Random mRandom = new Random();
+
+    /**
+     * Sets up for unit test
+     */
+    @Before
+    public void setUp() throws Exception {
+        mWifiInfo = new ExtendedWifiInfo();
+        mWifiLinkLayerStats = new WifiLinkLayerStats();
+    }
+
+    /**
+     * Increments the counters
+     *
+     * The values are carved up among the 4 classes (be, bk, vi, vo) so the totals come out right.
+     */
+    private void bumpCounters(WifiLinkLayerStats s, int txg, int txr, int txb, int rxg) {
+        int a = mRandom.nextInt(31);
+        int b = mRandom.nextInt(31);
+        int m0 = a & b;
+        int m1 = a & ~b;
+        int m2 = ~a & b;
+        int m3 = ~a & ~b;
+        assertEquals(-1, m0 + m1 + m2 + m3);
+
+        s.rxmpdu_be += rxg & m0;
+        s.txmpdu_be += txg & m0;
+        s.lostmpdu_be += txb & m0;
+        s.retries_be += txr & m0;
+
+        s.rxmpdu_bk += rxg & m1;
+        s.txmpdu_bk += txg & m1;
+        s.lostmpdu_bk += txb & m1;
+        s.retries_bk += txr & m1;
+
+        s.rxmpdu_vi += rxg & m2;
+        s.txmpdu_vi += txg & m2;
+        s.lostmpdu_vi += txb & m2;
+        s.retries_vi += txr & m2;
+
+        s.rxmpdu_vo += rxg & m3;
+        s.txmpdu_vo += txg & m3;
+        s.lostmpdu_vo += txb & m3;
+        s.retries_vo += txr & m3;
+    }
+
+    /**
+     *
+     * Check that average rates converge to the right values
+     *
+     * Check that the total packet counts are correct
+     *
+     */
+    @Test
+    public void checkThatAverageRatesConvergeToTheRightValuesAndTotalsAreRight() throws Exception {
+        int txg = mRandom.nextInt(1000);
+        int txr = mRandom.nextInt(100);
+        int txb = mRandom.nextInt(100);
+        int rxg = mRandom.nextInt(1000);
+        int n = 3 * 5; // Time constant is 3 seconds, 5 times time constant should get 99% there
+        for (int i = 0; i < n; i++) {
+            bumpCounters(mWifiLinkLayerStats, txg, txr, txb, rxg);
+            mWifiLinkLayerStats.timeStampInMs += 1000;
+            mWifiInfo.updatePacketRates(mWifiLinkLayerStats, mWifiLinkLayerStats.timeStampInMs);
+        }
+        // assertEquals(double, double, double) takes a tolerance as the third argument
+        assertEquals((double) txg, mWifiInfo.txSuccessRate, txg * 0.02);
+        assertEquals((double) txr, mWifiInfo.txRetriesRate, txr * 0.02);
+        assertEquals((double) txb, mWifiInfo.txBadRate, txb * 0.02);
+        assertEquals((double) rxg, mWifiInfo.rxSuccessRate, rxg * 0.02);
+
+        assertEquals(mWifiInfo.txSuccess, n * txg);
+        assertEquals(mWifiInfo.txRetries, n * txr);
+        assertEquals(mWifiInfo.txBad, n * txb);
+        assertEquals(mWifiInfo.rxSuccess, n * rxg);
+    }
+
+    /**
+     * A single packet in a short period of time should have small effect
+     */
+    @Test
+    public void aSinglePacketInAShortPeriodOfTimeShouldHaveSmallEffect() throws Exception {
+        bumpCounters(mWifiLinkLayerStats, 999999999, 999999999, 999999999, 99999999);
+        mWifiLinkLayerStats.timeStampInMs = 999999999;
+        mWifiInfo.updatePacketRates(mWifiLinkLayerStats, mWifiLinkLayerStats.timeStampInMs);
+        assertEquals(0.0, mWifiInfo.txSuccessRate, 0.0001);
+        bumpCounters(mWifiLinkLayerStats, 1, 1, 1, 1);
+        mWifiLinkLayerStats.timeStampInMs += 1;
+        mWifiInfo.updatePacketRates(mWifiLinkLayerStats, mWifiLinkLayerStats.timeStampInMs);
+        assertEquals(0.33, mWifiInfo.txSuccessRate, 0.01);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index 65427be..e3e7c0b 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -18,7 +18,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.net.NetworkAgent;
 import android.net.wifi.ScanResult;
@@ -40,7 +42,9 @@
 import com.android.server.wifi.nano.WifiMetricsProto;
 import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
 import com.android.server.wifi.nano.WifiMetricsProto.PnoScanMetrics;
+import com.android.server.wifi.nano.WifiMetricsProto.SoftApConnectedClientsEvent;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
+import com.android.server.wifi.nano.WifiMetricsProto.WpsMetrics;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -260,6 +264,15 @@
     private static final int NUM_PNO_SCAN_STARTED_OVER_OFFLOAD = 17;
     private static final int NUM_PNO_SCAN_FAILED_OVER_OFFLOAD = 8;
     private static final int NUM_PNO_FOUND_NETWORK_EVENTS = 10;
+    private static final int NUM_WPS_ATTEMPTS = 17;
+    private static final int NUM_WPS_SUCCESS = 21;
+    private static final int NUM_WPS_START_FAILURE = 7;
+    private static final int NUM_WPS_OVERLAP_FAILURE = 3;
+    private static final int NUM_WPS_TIMEOUT_FAILURE = 8;
+    private static final int NUM_WPS_OTHER_CONNECTION_FAILURE = 16;
+    private static final int NUM_WPS_SUPPLICANT_FAILURE = 12;
+    private static final int NUM_WPS_CANCELLATION = 11;
+
     /** Number of notifications per "Connect to Network" notification type. */
     private static final int[] NUM_CONNECT_TO_NETWORK_NOTIFICATIONS = {0, 10, 20, 30, 40};
     /** Number of notifications per "Connect to Network notification type and action type. */
@@ -273,6 +286,8 @@
     private static final boolean IS_WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = true;
     private static final int NUM_OPEN_NETWORK_CONNECT_MESSAGE_FAILED_TO_SEND = 5;
     private static final int NUM_OPEN_NETWORK_RECOMMENDATION_UPDATES = 8;
+    private static final int NUM_SOFT_AP_EVENT_ENTRIES = 3;
+    private static final int NUM_SOFT_AP_ASSOCIATED_STATIONS = 3;
 
     private ScanDetail buildMockScanDetail(boolean hidden, NetworkDetail.HSRelease hSRelease,
             String capabilities) {
@@ -311,6 +326,23 @@
         return mockScanDetail;
     }
 
+    private ScanDetail buildMockScanDetailPasspoint(String ssid, String bssid, long hessid,
+            int anqpDomainId, NetworkDetail.HSRelease hsRelease) {
+        ScanDetail mockScanDetail = mock(ScanDetail.class);
+        NetworkDetail mockNetworkDetail = mock(NetworkDetail.class);
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = ssid;
+        scanResult.BSSID = bssid;
+        scanResult.hessid = hessid;
+        scanResult.capabilities = "PSK";
+        when(mockScanDetail.getNetworkDetail()).thenReturn(mockNetworkDetail);
+        when(mockScanDetail.getScanResult()).thenReturn(scanResult);
+        when(mockNetworkDetail.getHSRelease()).thenReturn(hsRelease);
+        when(mockNetworkDetail.getAnqpDomainID()).thenReturn(anqpDomainId);
+        when(mockNetworkDetail.isInterworking()).thenReturn(true);
+        return mockScanDetail;
+    }
+
     private List<ScanDetail> buildMockScanDetailList() {
         List<ScanDetail> mockScanDetails = new ArrayList<ScanDetail>();
         mockScanDetails.add(buildMockScanDetail(true, null, "[ESS]"));
@@ -539,6 +571,78 @@
         for (int i = 0; i < NUM_OPEN_NETWORK_CONNECT_MESSAGE_FAILED_TO_SEND; i++) {
             mWifiMetrics.incrementNumOpenNetworkConnectMessageFailedToSend();
         }
+
+        addSoftApEventsToMetrics();
+
+        // increment wps metrics
+        for (int i = 0; i < NUM_WPS_ATTEMPTS; i++) {
+            mWifiMetrics.incrementWpsAttemptCount();
+        }
+        for (int i = 0; i < NUM_WPS_SUCCESS; i++) {
+            mWifiMetrics.incrementWpsSuccessCount();
+        }
+        for (int i = 0; i < NUM_WPS_START_FAILURE; i++) {
+            mWifiMetrics.incrementWpsStartFailureCount();
+        }
+        for (int i = 0; i < NUM_WPS_OVERLAP_FAILURE; i++) {
+            mWifiMetrics.incrementWpsOverlapFailureCount();
+        }
+        for (int i = 0; i < NUM_WPS_TIMEOUT_FAILURE; i++) {
+            mWifiMetrics.incrementWpsTimeoutFailureCount();
+        }
+        for (int i = 0; i < NUM_WPS_OTHER_CONNECTION_FAILURE; i++) {
+            mWifiMetrics.incrementWpsOtherConnectionFailureCount();
+        }
+        for (int i = 0; i < NUM_WPS_SUPPLICANT_FAILURE; i++) {
+            mWifiMetrics.incrementWpsSupplicantFailureCount();
+        }
+        for (int i = 0; i < NUM_WPS_CANCELLATION; i++) {
+            mWifiMetrics.incrementWpsCancellationCount();
+        }
+    }
+
+    private void addSoftApEventsToMetrics() {
+        // Total number of events recorded is NUM_SOFT_AP_EVENT_ENTRIES in both modes
+
+        mWifiMetrics.addSoftApUpChangedEvent(true, WifiManager.IFACE_IP_MODE_TETHERED);
+        mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(NUM_SOFT_AP_ASSOCIATED_STATIONS,
+                WifiManager.IFACE_IP_MODE_TETHERED);
+        mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(NUM_SOFT_AP_ASSOCIATED_STATIONS,
+                WifiManager.IFACE_IP_MODE_UNSPECIFIED);  // Should be dropped.
+        mWifiMetrics.addSoftApUpChangedEvent(false, WifiManager.IFACE_IP_MODE_TETHERED);
+
+        mWifiMetrics.addSoftApUpChangedEvent(true, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+        mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(NUM_SOFT_AP_ASSOCIATED_STATIONS,
+                WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+        // Should be dropped.
+        mWifiMetrics.addSoftApUpChangedEvent(false, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
+        mWifiMetrics.addSoftApUpChangedEvent(false, WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+    }
+
+    private void verifySoftApEventsStoredInProto() {
+        assertEquals(NUM_SOFT_AP_EVENT_ENTRIES,
+                mDecodedProto.softApConnectedClientsEventsTethered.length);
+        assertEquals(SoftApConnectedClientsEvent.SOFT_AP_UP,
+                mDecodedProto.softApConnectedClientsEventsTethered[0].eventType);
+        assertEquals(0, mDecodedProto.softApConnectedClientsEventsTethered[0].numConnectedClients);
+        assertEquals(SoftApConnectedClientsEvent.NUM_CLIENTS_CHANGED,
+                mDecodedProto.softApConnectedClientsEventsTethered[1].eventType);
+        assertEquals(NUM_SOFT_AP_ASSOCIATED_STATIONS,
+                mDecodedProto.softApConnectedClientsEventsTethered[1].numConnectedClients);
+        assertEquals(SoftApConnectedClientsEvent.SOFT_AP_DOWN,
+                mDecodedProto.softApConnectedClientsEventsTethered[2].eventType);
+        assertEquals(0, mDecodedProto.softApConnectedClientsEventsTethered[2].numConnectedClients);
+
+        assertEquals(SoftApConnectedClientsEvent.SOFT_AP_UP,
+                mDecodedProto.softApConnectedClientsEventsLocalOnly[0].eventType);
+        assertEquals(0, mDecodedProto.softApConnectedClientsEventsLocalOnly[0].numConnectedClients);
+        assertEquals(SoftApConnectedClientsEvent.NUM_CLIENTS_CHANGED,
+                mDecodedProto.softApConnectedClientsEventsLocalOnly[1].eventType);
+        assertEquals(NUM_SOFT_AP_ASSOCIATED_STATIONS,
+                mDecodedProto.softApConnectedClientsEventsLocalOnly[1].numConnectedClients);
+        assertEquals(SoftApConnectedClientsEvent.SOFT_AP_DOWN,
+                mDecodedProto.softApConnectedClientsEventsLocalOnly[2].eventType);
+        assertEquals(0, mDecodedProto.softApConnectedClientsEventsLocalOnly[2].numConnectedClients);
     }
 
     /**
@@ -717,6 +821,19 @@
                 mDecodedProto.numOpenNetworkRecommendationUpdates);
         assertEquals(NUM_OPEN_NETWORK_CONNECT_MESSAGE_FAILED_TO_SEND,
                 mDecodedProto.numOpenNetworkConnectMessageFailedToSend);
+
+        verifySoftApEventsStoredInProto();
+
+        WpsMetrics wps_metrics = mDecodedProto.wpsMetrics;
+        assertNotNull(wps_metrics);
+        assertEquals(NUM_WPS_ATTEMPTS, wps_metrics.numWpsAttempts);
+        assertEquals(NUM_WPS_SUCCESS, wps_metrics.numWpsSuccess);
+        assertEquals(NUM_WPS_START_FAILURE, wps_metrics.numWpsStartFailure);
+        assertEquals(NUM_WPS_OVERLAP_FAILURE, wps_metrics.numWpsOverlapFailure);
+        assertEquals(NUM_WPS_TIMEOUT_FAILURE, wps_metrics.numWpsTimeoutFailure);
+        assertEquals(NUM_WPS_OTHER_CONNECTION_FAILURE, wps_metrics.numWpsOtherConnectionFailure);
+        assertEquals(NUM_WPS_SUPPLICANT_FAILURE, wps_metrics.numWpsSupplicantFailure);
+        assertEquals(NUM_WPS_CANCELLATION, wps_metrics.numWpsCancellation);
     }
 
     /**
@@ -763,6 +880,31 @@
         // pending their implementation</TODO>
     }
 
+    /**
+     * Test that score breach events are properly generated
+     */
+    @Test
+    public void testScoreBeachEvents() throws Exception {
+        int upper = WifiMetrics.LOW_WIFI_SCORE + 7;
+        int mid = WifiMetrics.LOW_WIFI_SCORE;
+        int lower = WifiMetrics.LOW_WIFI_SCORE - 8;
+        mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
+        for (int score = upper; score >= mid; score--) mWifiMetrics.incrementWifiScoreCount(score);
+        mWifiMetrics.incrementWifiScoreCount(mid + 1);
+        mWifiMetrics.incrementWifiScoreCount(lower); // First breach
+        for (int score = lower; score <= mid; score++) mWifiMetrics.incrementWifiScoreCount(score);
+        mWifiMetrics.incrementWifiScoreCount(mid - 1);
+        mWifiMetrics.incrementWifiScoreCount(upper); // Second breach
+
+        dumpProtoAndDeserialize();
+
+        assertEquals(2, mDecodedProto.staEventList.length);
+        assertEquals(StaEvent.TYPE_SCORE_BREACH, mDecodedProto.staEventList[0].type);
+        assertEquals(lower, mDecodedProto.staEventList[0].lastScore);
+        assertEquals(StaEvent.TYPE_SCORE_BREACH, mDecodedProto.staEventList[1].type);
+        assertEquals(upper, mDecodedProto.staEventList[1].lastScore);
+    }
+
     private static final String SSID = "red";
     private static final int CONFIG_DTIM = 3;
     private static final int NETWORK_DETAIL_WIFIMODE = 5;
@@ -1041,7 +1183,7 @@
     private static final int ASSOC_TIMEOUT = 1;
     private static final int LOCAL_GEN = 1;
     private static final int AUTH_FAILURE_REASON = WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD;
-    private static final int NUM_TEST_STA_EVENTS = 14;
+    private static final int NUM_TEST_STA_EVENTS = 15;
     private static final String   sSSID = "\"SomeTestSsid\"";
     private static final WifiSsid sWifiSsid = WifiSsid.createFromAsciiEncoded(sSSID);
     private static final String   sBSSID = "01:02:03:04:05:06";
@@ -1089,7 +1231,8 @@
         {StaEvent.TYPE_CMD_START_ROAM,                  0,                          1},
         {StaEvent.TYPE_CONNECT_NETWORK,                 0,                          1},
         {StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK,     0,                          0},
-        {StaEvent.TYPE_FRAMEWORK_DISCONNECT,            StaEvent.DISCONNECT_API,    0}
+        {StaEvent.TYPE_FRAMEWORK_DISCONNECT,            StaEvent.DISCONNECT_API,    0},
+        {StaEvent.TYPE_SCORE_BREACH,                    0,                          0}
     };
     // Values used to generate the StaEvent log calls from WifiMonitor
     // <type>, <reason>, <status>, <local_gen>,
@@ -1122,6 +1265,8 @@
         {StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK,     -1,            -1,         0,
             /**/                               0,             0,        0, 0},    /**/
         {StaEvent.TYPE_FRAMEWORK_DISCONNECT,            -1,            -1,         0,
+            /**/                               0,             0,        0, 0},    /**/
+        {StaEvent.TYPE_SCORE_BREACH,                    -1,            -1,         0,
             /**/                               0,             0,        0, 0}     /**/
     };
 
@@ -1142,6 +1287,7 @@
         }
     }
     private void verifyDeserializedStaEvents(WifiMetricsProto.WifiLog wifiLog) {
+        assertNotNull(mTestWifiConfig);
         assertEquals(NUM_TEST_STA_EVENTS, wifiLog.staEventList.length);
         int j = 0; // De-serialized event index
         for (int i = 0; i < mTestStaMessageInts.length; i++) {
@@ -1161,6 +1307,21 @@
                 j++;
             }
         }
+        for (int i = 0; i < mTestStaLogInts.length; i++) {
+            StaEvent event = wifiLog.staEventList[j];
+            int[] evs = mExpectedValues[j];
+            assertEquals(evs[0], event.type);
+            assertEquals(evs[1], event.reason);
+            assertEquals(evs[2], event.status);
+            assertEquals(evs[3] == 1 ? true : false, event.localGen);
+            assertEquals(evs[4], event.authFailureReason);
+            assertEquals(evs[5] == 1 ? true : false, event.associationTimedOut);
+            assertEquals(evs[6], event.supplicantStateChangesBitmask);
+            assertConfigInfoEqualsWifiConfig(
+                    evs[7] == 1 ? mTestWifiConfig : null, event.configInfo);
+            j++;
+        }
+        assertEquals(mExpectedValues.length, j);
     }
 
     /**
@@ -1271,9 +1432,77 @@
     }
 
     /**
+     * Test that Hotspot 2.0 (Passpoint) scan results are collected correctly and that relevant
+     * bounds are observed.
+     */
+    @Test
+    public void testObservedHotspotAps() throws Exception {
+        List<ScanDetail> scan = new ArrayList<ScanDetail>();
+        // 2 R1 (Unknown AP isn't counted) passpoint APs belonging to a single provider: hessid1
+        long hessid1 = 10;
+        int anqpDomainId1 = 5;
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_XX", "00:02:03:04:05:06", hessid1,
+                anqpDomainId1, NetworkDetail.HSRelease.R1));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_XY", "01:02:03:04:05:06", hessid1,
+                anqpDomainId1, NetworkDetail.HSRelease.R1));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_XYZ", "02:02:03:04:05:06", hessid1,
+                anqpDomainId1, NetworkDetail.HSRelease.Unknown));
+        // 2 R2 passpoint APs belonging to a single provider: hessid2
+        long hessid2 = 12;
+        int anqpDomainId2 = 6;
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Y", "AA:02:03:04:05:06", hessid2,
+                anqpDomainId2, NetworkDetail.HSRelease.R2));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Z", "AB:02:03:04:05:06", hessid2,
+                anqpDomainId2, NetworkDetail.HSRelease.R2));
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        scan = new ArrayList<ScanDetail>();
+        // 3 R2 passpoint APs belonging to a single provider: hessid3 (in next scan)
+        long hessid3 = 15;
+        int anqpDomainId3 = 8;
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Y", "AA:02:03:04:05:06", hessid3,
+                anqpDomainId3, NetworkDetail.HSRelease.R2));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Y", "AA:02:03:04:05:06", hessid3,
+                anqpDomainId3, NetworkDetail.HSRelease.R2));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Z", "AB:02:03:04:05:06", hessid3,
+                anqpDomainId3, NetworkDetail.HSRelease.R2));
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        dumpProtoAndDeserialize();
+
+        verifyHist(mDecodedProto.observedHotspotR1ApsInScanHistogram, 2, a(0, 2), a(1, 1));
+        verifyHist(mDecodedProto.observedHotspotR2ApsInScanHistogram, 2, a(2, 3), a(1, 1));
+        verifyHist(mDecodedProto.observedHotspotR1EssInScanHistogram, 2, a(0, 1), a(1, 1));
+        verifyHist(mDecodedProto.observedHotspotR2EssInScanHistogram, 1, a(1), a(2));
+        verifyHist(mDecodedProto.observedHotspotR1ApsPerEssInScanHistogram, 1, a(2), a(1));
+        verifyHist(mDecodedProto.observedHotspotR2ApsPerEssInScanHistogram, 2, a(2, 3), a(1, 1));
+
+        // check bounds
+        scan.clear();
+        int lotsOfSSids = Math.max(WifiMetrics.MAX_TOTAL_PASSPOINT_APS_BUCKET,
+                WifiMetrics.MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET) + 5;
+        for (int i = 0; i < lotsOfSSids; i++) {
+            scan.add(buildMockScanDetailPasspoint("PASSPOINT_XX" + i, "00:02:03:04:05:06", i,
+                    i + 10, NetworkDetail.HSRelease.R1));
+            scan.add(buildMockScanDetailPasspoint("PASSPOINT_XY" + i, "AA:02:03:04:05:06", 1000 * i,
+                    i + 10, NetworkDetail.HSRelease.R2));
+        }
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        dumpProtoAndDeserialize();
+        verifyHist(mDecodedProto.observedHotspotR1ApsInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_APS_BUCKET), a(1));
+        verifyHist(mDecodedProto.observedHotspotR2ApsInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_APS_BUCKET), a(1));
+        verifyHist(mDecodedProto.observedHotspotR1EssInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET), a(1));
+        verifyHist(mDecodedProto.observedHotspotR2EssInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET), a(1));
+
+    }
+
+    /**
      * Test Open Network Notification blacklist size and feature state are not cleared when proto
      * is dumped.
      */
+    @Test
     public void testOpenNetworkNotificationBlacklistSizeAndFeatureStateNotCleared()
             throws Exception {
         mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
index 32d1daa..9a79a23 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
@@ -20,8 +20,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -47,6 +45,7 @@
  */
 @SmallTest
 public class WifiNativeTest {
+    private static final String WIFI_IFACE_NAME = "mockWlan";
     private static final long FATE_REPORT_DRIVER_TIMESTAMP_USEC = 12345;
     private static final byte[] FATE_REPORT_FRAME_BYTES = new byte[] {
             'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 0, 1, 2, 3, 4, 5, 6, 7};
@@ -155,8 +154,10 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mWifiVendorHal.isVendorHalSupported()).thenReturn(true);
-        when(mWifiVendorHal.startVendorHal(anyBoolean())).thenReturn(true);
-        mWifiNative = new WifiNative("test0", mWifiVendorHal, mStaIfaceHal, mWificondControl);
+        when(mWifiVendorHal.startVendorHalSta()).thenReturn(true);
+        when(mWifiVendorHal.startVendorHalAp()).thenReturn(true);
+        mWifiNative =
+                new WifiNative(WIFI_IFACE_NAME, mWifiVendorHal, mStaIfaceHal, mWificondControl);
     }
 
     /**
@@ -481,127 +482,139 @@
     // TODO(b/28005116): Add test for the success case of getDriverStateDump().
 
     /**
-     * Verifies that setupDriverForClientMode() calls underlying WificondControl.
+     * Verifies that setupInterfaceForClientMode(WIFI_IFACE_NAME) calls underlying WificondControl.
      */
     @Test
     public void testSetupDriverForClientMode() {
         IClientInterface clientInterface = mock(IClientInterface.class);
-        when(mWificondControl.setupDriverForClientMode()).thenReturn(clientInterface);
+        when(mWificondControl.setupInterfaceForClientMode(WIFI_IFACE_NAME))
+                .thenReturn(clientInterface);
 
-        Pair<Integer, IClientInterface> statusAndClientInterface = mWifiNative.setupForClientMode();
+        Pair<Integer, IClientInterface> statusAndClientInterface =
+                mWifiNative.setupForClientMode(WIFI_IFACE_NAME);
         assertTrue(WifiNative.SETUP_SUCCESS == statusAndClientInterface.first);
         assertEquals(clientInterface, statusAndClientInterface.second);
-        verify(mWifiVendorHal).startVendorHal(eq(true));
-        verify(mWificondControl).setupDriverForClientMode();
+        verify(mWifiVendorHal).startVendorHalSta();
+        verify(mWificondControl).setupInterfaceForClientMode(WIFI_IFACE_NAME);
     }
 
     /**
-     * Verifies that setupDriverForClientMode() does not call start vendor HAL when it is not
-     * supported and calls underlying WificondControl setup.
+     * Verifies that setupInterfaceForClientMode(WIFI_IFACE_NAME) does not call start vendor HAL
+     * when it is not supported and calls underlying WificondControl setup.
      */
     @Test
     public void testSetupDriverForClientModeWithNoVendorHal() {
         when(mWifiVendorHal.isVendorHalSupported()).thenReturn(false);
         IClientInterface clientInterface = mock(IClientInterface.class);
-        when(mWificondControl.setupDriverForClientMode()).thenReturn(clientInterface);
+        when(mWificondControl.setupInterfaceForClientMode(WIFI_IFACE_NAME))
+                .thenReturn(clientInterface);
 
-        Pair<Integer, IClientInterface> statusAndClientInterface = mWifiNative.setupForClientMode();
+        Pair<Integer, IClientInterface> statusAndClientInterface =
+                mWifiNative.setupForClientMode(WIFI_IFACE_NAME);
         assertTrue(WifiNative.SETUP_SUCCESS == statusAndClientInterface.first);
         assertEquals(clientInterface, statusAndClientInterface.second);
-        verify(mWifiVendorHal, never()).startVendorHal(anyBoolean());
-        verify(mWificondControl).setupDriverForClientMode();
+        verify(mWifiVendorHal, never()).startVendorHalSta();
+        verify(mWificondControl).setupInterfaceForClientMode(WIFI_IFACE_NAME);
     }
 
     /**
-     * Verifies that setupDriverForClientMode() returns null when underlying WificondControl
-     * call fails.
+     * Verifies that setupInterfaceForClientMode(WIFI_IFACE_NAME) returns null when underlying
+     * WificondControl call fails.
      */
     @Test
     public void testSetupDriverForClientModeWificondError() {
-        when(mWificondControl.setupDriverForClientMode()).thenReturn(null);
+        when(mWificondControl.setupInterfaceForClientMode(WIFI_IFACE_NAME)).thenReturn(null);
 
-        Pair<Integer, IClientInterface> statusAndClientInterface = mWifiNative.setupForClientMode();
+        Pair<Integer, IClientInterface> statusAndClientInterface =
+                mWifiNative.setupForClientMode(WIFI_IFACE_NAME);
         assertTrue(WifiNative.SETUP_FAILURE_WIFICOND == statusAndClientInterface.first);
         assertEquals(null, statusAndClientInterface.second);
-        verify(mWifiVendorHal).startVendorHal(eq(true));
-        verify(mWificondControl).setupDriverForClientMode();
+        verify(mWifiVendorHal).startVendorHalSta();
+        verify(mWificondControl).setupInterfaceForClientMode(WIFI_IFACE_NAME);
     }
 
     /**
-     * Verifies that setupDriverForClientMode() returns null when underlying Hal call fails.
+     * Verifies that setupInterfaceForClientMode(WIFI_IFACE_NAME) returns null when underlying Hal
+     * call fails.
      */
     @Test
     public void testSetupDriverForClientModeHalError() {
-        when(mWifiVendorHal.startVendorHal(anyBoolean())).thenReturn(false);
+        when(mWifiVendorHal.startVendorHalSta()).thenReturn(false);
 
-        Pair<Integer, IClientInterface> statusAndClientInterface = mWifiNative.setupForClientMode();
+        Pair<Integer, IClientInterface> statusAndClientInterface =
+                mWifiNative.setupForClientMode(WIFI_IFACE_NAME);
         assertTrue(WifiNative.SETUP_FAILURE_HAL == statusAndClientInterface.first);
         assertEquals(null, statusAndClientInterface.second);
-        verify(mWifiVendorHal).startVendorHal(eq(true));
-        verify(mWificondControl, never()).setupDriverForClientMode();
+        verify(mWifiVendorHal).startVendorHalSta();
+        verify(mWificondControl, never()).setupInterfaceForClientMode(WIFI_IFACE_NAME);
     }
 
     /**
-     * Verifies that setupDriverForSoftApMode() calls underlying WificondControl.
+     * Verifies that setupInterfaceForSoftApMode(WIFI_IFACE_NAME) calls underlying WificondControl.
      */
     @Test
     public void testSetupDriverForSoftApMode() {
         IApInterface apInterface = mock(IApInterface.class);
-        when(mWificondControl.setupDriverForSoftApMode()).thenReturn(apInterface);
+        when(mWificondControl.setupInterfaceForSoftApMode(WIFI_IFACE_NAME)).thenReturn(apInterface);
 
-        Pair<Integer, IApInterface> statusAndApInterface = mWifiNative.setupForSoftApMode();
+        Pair<Integer, IApInterface> statusAndApInterface =
+                mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME);
         assertTrue(WifiNative.SETUP_SUCCESS == statusAndApInterface.first);
         assertEquals(apInterface, statusAndApInterface.second);
-        verify(mWifiVendorHal).startVendorHal(eq(false));
-        verify(mWificondControl).setupDriverForSoftApMode();
+        verify(mWifiVendorHal).startVendorHalAp();
+        verify(mWificondControl).setupInterfaceForSoftApMode(WIFI_IFACE_NAME);
     }
 
     /**
-     * Verifies that setupDriverForClientMode() does not call start vendor HAL when it is not
-     * supported and calls underlying WificondControl setup.
+     * Verifies that setupInterfaceForClientMode(WIFI_IFACE_NAME) does not call start vendor HAL
+     * when it is not supported and calls underlying WificondControl setup.
      */
     @Test
     public void testSetupDriverForSoftApModeWithNoVendorHal() {
         when(mWifiVendorHal.isVendorHalSupported()).thenReturn(false);
         IApInterface apInterface = mock(IApInterface.class);
-        when(mWificondControl.setupDriverForSoftApMode()).thenReturn(apInterface);
+        when(mWificondControl.setupInterfaceForSoftApMode(WIFI_IFACE_NAME)).thenReturn(apInterface);
 
-        Pair<Integer, IApInterface> statusAndApInterface = mWifiNative.setupForSoftApMode();
+        Pair<Integer, IApInterface> statusAndApInterface =
+                mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME);
         assertTrue(WifiNative.SETUP_SUCCESS == statusAndApInterface.first);
         assertEquals(apInterface, statusAndApInterface.second);
-        verify(mWifiVendorHal, never()).startVendorHal(anyBoolean());
-        verify(mWificondControl).setupDriverForSoftApMode();
+        verify(mWifiVendorHal, never()).startVendorHalAp();
+        verify(mWificondControl).setupInterfaceForSoftApMode(WIFI_IFACE_NAME);
     }
 
     /**
-     * Verifies that setupDriverForSoftApMode() returns null when underlying WificondControl
-     * call fails.
+     * Verifies that setupInterfaceForSoftApMode(WIFI_IFACE_NAME) returns null when underlying
+     * WificondControl call fails.
      */
     @Test
     public void testSetupDriverForSoftApModeWificondError() {
-        when(mWificondControl.setupDriverForSoftApMode()).thenReturn(null);
+        when(mWificondControl.setupInterfaceForSoftApMode(WIFI_IFACE_NAME)).thenReturn(null);
 
-        Pair<Integer, IApInterface> statusAndApInterface = mWifiNative.setupForSoftApMode();
+        Pair<Integer, IApInterface> statusAndApInterface =
+                mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME);
         assertTrue(WifiNative.SETUP_FAILURE_WIFICOND == statusAndApInterface.first);
         assertEquals(null, statusAndApInterface.second);
 
-        verify(mWifiVendorHal).startVendorHal(eq(false));
-        verify(mWificondControl).setupDriverForSoftApMode();
+        verify(mWifiVendorHal).startVendorHalAp();
+        verify(mWificondControl).setupInterfaceForSoftApMode(WIFI_IFACE_NAME);
     }
 
     /**
-     * Verifies that setupDriverForSoftApMode() returns null when underlying Hal call fails.
+     * Verifies that setupInterfaceForSoftApMode(WIFI_IFACE_NAME) returns null when underlying Hal
+     * call fails.
      */
     @Test
     public void testSetupDriverForSoftApModeHalError() {
-        when(mWifiVendorHal.startVendorHal(anyBoolean())).thenReturn(false);
+        when(mWifiVendorHal.startVendorHalAp()).thenReturn(false);
 
-        Pair<Integer, IApInterface> statusAndApInterface = mWifiNative.setupForSoftApMode();
+        Pair<Integer, IApInterface> statusAndApInterface =
+                mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME);
         assertTrue(WifiNative.SETUP_FAILURE_HAL == statusAndApInterface.first);
         assertEquals(null, statusAndApInterface.second);
 
-        verify(mWifiVendorHal).startVendorHal(eq(false));
-        verify(mWificondControl, never()).setupDriverForSoftApMode();
+        verify(mWifiVendorHal).startVendorHalAp();
+        verify(mWificondControl, never()).setupInterfaceForSoftApMode(WIFI_IFACE_NAME);
     }
 
     /**
@@ -657,10 +670,11 @@
      */
     @Test
     public void testSignalPoll() throws Exception {
-        when(mWificondControl.signalPoll()).thenReturn(SIGNAL_POLL_RESULT);
+        when(mWificondControl.signalPoll(WIFI_IFACE_NAME))
+                .thenReturn(SIGNAL_POLL_RESULT);
 
         assertEquals(SIGNAL_POLL_RESULT, mWifiNative.signalPoll());
-        verify(mWificondControl).signalPoll();
+        verify(mWificondControl).signalPoll(WIFI_IFACE_NAME);
     }
 
     /**
@@ -668,10 +682,11 @@
      */
     @Test
     public void testGetTxPacketCounters() throws Exception {
-        when(mWificondControl.getTxPacketCounters()).thenReturn(PACKET_COUNTERS_RESULT);
+        when(mWificondControl.getTxPacketCounters(WIFI_IFACE_NAME))
+                .thenReturn(PACKET_COUNTERS_RESULT);
 
         assertEquals(PACKET_COUNTERS_RESULT, mWifiNative.getTxPacketCounters());
-        verify(mWificondControl).getTxPacketCounters();
+        verify(mWificondControl).getTxPacketCounters(WIFI_IFACE_NAME);
     }
 
     /**
@@ -680,7 +695,8 @@
     @Test
     public void testScan() throws Exception {
         mWifiNative.scan(SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET);
-        verify(mWificondControl).scan(SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET);
+        verify(mWificondControl).scan(
+                WIFI_IFACE_NAME, SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET);
     }
 
     /**
@@ -689,7 +705,8 @@
     @Test
     public void testStartPnoScan() throws Exception {
         mWifiNative.startPnoScan(TEST_PNO_SETTINGS);
-        verify(mWificondControl).startPnoScan(TEST_PNO_SETTINGS);
+        verify(mWificondControl).startPnoScan(
+                WIFI_IFACE_NAME, TEST_PNO_SETTINGS);
     }
 
     /**
@@ -698,7 +715,7 @@
     @Test
     public void testStopPnoScan() throws Exception {
         mWifiNative.stopPnoScan();
-        verify(mWificondControl).stopPnoScan();
+        verify(mWificondControl).stopPnoScan(WIFI_IFACE_NAME);
     }
 
     /**
@@ -709,8 +726,8 @@
         WifiConfiguration config = mock(WifiConfiguration.class);
         mWifiNative.connectToNetwork(config);
         // connectToNetwork() should abort ongoing scan before connection.
-        verify(mWificondControl).abortScan();
-        verify(mStaIfaceHal).connectToNetwork(config);
+        verify(mWificondControl).abortScan(WIFI_IFACE_NAME);
+        verify(mStaIfaceHal).connectToNetwork(WIFI_IFACE_NAME, config);
     }
 
     /**
@@ -721,8 +738,7 @@
         WifiConfiguration config = mock(WifiConfiguration.class);
         mWifiNative.roamToNetwork(config);
         // roamToNetwork() should abort ongoing scan before connection.
-        verify(mWificondControl).abortScan();
-        verify(mStaIfaceHal).roamToNetwork(config);
+        verify(mWificondControl).abortScan(WIFI_IFACE_NAME);
+        verify(mStaIfaceHal).roamToNetwork(WIFI_IFACE_NAME, config);
     }
-
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
index 3d3af36..b56da5d 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSelectorTest.java
@@ -613,8 +613,8 @@
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G + 1);
         // No streaming traffic.
-        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
-        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+        mWifiInfo.txSuccessRate = 0.0;
+        mWifiInfo.rxSuccessRate = 0.0;
 
         // Do not perform selection on 2GHz if current network is good and no 5GHz available
         testStayOrTryToSwitch(
@@ -640,8 +640,8 @@
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G + 1);
         // No streaming traffic.
-        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
-        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+        mWifiInfo.txSuccessRate = 0.0;
+        mWifiInfo.rxSuccessRate = 0.0;
 
         // When on 2GHz, even with "good" signal strength, run selection if 5GHz available
         testStayOrTryToSwitch(
@@ -670,8 +670,8 @@
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G - 1);
         // No streaming traffic.
-        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
-        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+        mWifiInfo.txSuccessRate = 0.0;
+        mWifiInfo.rxSuccessRate = 0.0;
 
         // Run Selection when the current 5Ghz network has low RSSI.
         testStayOrTryToSwitch(
@@ -695,8 +695,8 @@
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G + 1);
         // No streaming traffic.
-        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
-        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+        mWifiInfo.txSuccessRate = 0.0;
+        mWifiInfo.rxSuccessRate = 0.0;
 
         // Connected to a high quality 5Ghz network, so the other result is irrelevant
         testStayOrTryToSwitch(
@@ -719,8 +719,8 @@
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G + 1);
         // No streaming traffic.
-        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
-        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+        mWifiInfo.txSuccessRate = 0.0;
+        mWifiInfo.rxSuccessRate = 0.0;
 
         testStayOrTryToSwitch(
                 // Parameters for network1:
@@ -747,8 +747,8 @@
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G - 1);
         // No streaming traffic.
-        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
-        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+        mWifiInfo.txSuccessRate = 0.0;
+        mWifiInfo.rxSuccessRate = 0.0;
 
         testStayOrTryToSwitch(
                 mThresholdQualifiedRssi2G + 1 /* rssi before connected */,
@@ -769,9 +769,8 @@
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G + 1);
         // Streaming traffic
-        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(
-                (double) (mStayOnNetworkMinimumTxRate + 1));
-        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+        mWifiInfo.txSuccessRate = ((double) (mStayOnNetworkMinimumTxRate + 1));
+        mWifiInfo.rxSuccessRate = 0.0;
 
         testStayOrTryToSwitch(
                 mThresholdQualifiedRssi2G + 1 /* rssi before connected */,
@@ -792,9 +791,8 @@
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi2G + 1);
         // Streaming traffic
-        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
-        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(
-                (double) (mStayOnNetworkMinimumRxRate + 1));
+        mWifiInfo.txSuccessRate = 0.0;
+        mWifiInfo.rxSuccessRate = ((double) (mStayOnNetworkMinimumRxRate + 1));
 
         testStayOrTryToSwitch(
                 mThresholdQualifiedRssi2G + 1 /* rssi before connected */,
@@ -815,9 +813,8 @@
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G + 1);
         // Streaming traffic
-        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(
-                (double) (mStayOnNetworkMinimumTxRate + 1));
-        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(0.0);
+        mWifiInfo.txSuccessRate = ((double) (mStayOnNetworkMinimumTxRate + 1));
+        mWifiInfo.rxSuccessRate = 0.0;
 
         testStayOrTryToSwitch(
                 mThresholdQualifiedRssi5G + 1 /* rssi before connected */,
@@ -838,9 +835,8 @@
         // Rssi after connected.
         when(mWifiInfo.getRssi()).thenReturn(mThresholdQualifiedRssi5G + 1);
         // Streaming traffic
-        when(mWifiInfo.getTxSuccessRatePps()).thenReturn(0.0);
-        when(mWifiInfo.getRxSuccessRatePps()).thenReturn(
-                (double) (mStayOnNetworkMinimumRxRate + 1));
+        mWifiInfo.txSuccessRate = 0.0;
+        mWifiInfo.rxSuccessRate = ((double) (mStayOnNetworkMinimumRxRate + 1));
 
         testStayOrTryToSwitch(
                 mThresholdQualifiedRssi5G + 1 /* rssi before connected */,
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java b/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
index 6f01c8e..022d5dd 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
@@ -17,10 +17,13 @@
 package com.android.server.wifi;
 
 import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.answerVoid;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -240,11 +243,16 @@
     @Test
     public void giveUpOnBadRssiAggressively() throws Exception {
         mAggr = 1;
-        mWifiInfo.setRssi(-83);
-        mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mAggr, mWifiMetrics);
+        String oops = "giveUpOnBadRssiAggressively";
+        for (int rssi = -60; rssi >= -83; rssi -= 1) {
+            mWifiInfo.setRssi(rssi);
+            oops += " " + mClock.mWallClockMillis + "," + rssi;
+            mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mAggr, mWifiMetrics);
+            oops += ":" + mWifiInfo.score;
+        }
         int score = mWifiInfo.score;
         verify(mNetworkAgent, atLeast(1)).sendNetworkScore(score);
-        assertTrue(score < CELLULAR_THRESHOLD_SCORE);
+        assertTrue(oops, score < CELLULAR_THRESHOLD_SCORE);
     }
 
     /**
@@ -261,6 +269,21 @@
     }
 
     /**
+     * This setup causes some reports to be generated when println
+     * methods are called, to check for "concurrent" modification
+     * errors.
+     */
+    private void setupToGenerateAReportWhenPrintlnIsCalled() {
+        int[] counter = new int[1];
+        doAnswer(answerVoid((String line) -> {
+            if (counter[0]++ < 3) {
+                mWifiScoreReport.calculateAndReportScore(
+                        mWifiInfo, mNetworkAgent, mAggr, mWifiMetrics);
+            }
+        })).when(mPrintWriter).println(anyString());
+    }
+
+    /**
      * Test data logging
      */
     @Test
@@ -276,8 +299,9 @@
             mWifiInfo.rxSuccessRate = 0.3 + i;
             mWifiScoreReport.calculateAndReportScore(mWifiInfo, mNetworkAgent, mAggr, mWifiMetrics);
         }
+        setupToGenerateAReportWhenPrintlnIsCalled();
         mWifiScoreReport.dump(null, mPrintWriter, null);
-        verify(mPrintWriter, atLeast(11)).println(anyString());
+        verify(mPrintWriter, times(11)).println(anyString());
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
index e43bdc2..ea81d53 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
@@ -64,6 +64,8 @@
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -83,10 +85,10 @@
 
 import com.android.internal.util.AsyncChannel;
 import com.android.server.wifi.WifiServiceImpl.LocalOnlyRequestorCallback;
+import com.android.server.wifi.hotspot2.PasspointProvisioningTestUtil;
 import com.android.server.wifi.util.WifiAsyncChannel;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 
-
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -119,6 +121,7 @@
     private static final int TEST_PID = 6789;
     private static final int TEST_PID2 = 9876;
     private static final String WIFI_IFACE_NAME = "wlan0";
+    private static final String TEST_COUNTRY_CODE = "US";
 
     private WifiServiceImpl mWifiServiceImpl;
     private TestLooper mLooper;
@@ -127,6 +130,7 @@
     private Messenger mAppMessenger;
     private int mPid;
     private int mPid2 = Process.myPid();
+    private OsuProvider mOsuProvider;
 
     final ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor =
             ArgumentCaptor.forClass(BroadcastReceiver.class);
@@ -139,6 +143,7 @@
 
     @Mock Context mContext;
     @Mock WifiInjector mWifiInjector;
+    @Mock WifiCountryCode mWifiCountryCode;
     @Mock Clock mClock;
     @Mock WifiController mWifiController;
     @Mock WifiTrafficPoller mWifiTrafficPoller;
@@ -163,6 +168,7 @@
     @Mock IBinder mAppBinder;
     @Mock LocalOnlyHotspotRequestInfo mRequestInfo;
     @Mock LocalOnlyHotspotRequestInfo mRequestInfo2;
+    @Mock IProvisioningCallback mProvisioningCallback;
 
     @Spy FakeWifiLog mLog;
 
@@ -236,6 +242,7 @@
         when(mRequestInfo.getPid()).thenReturn(mPid);
         when(mRequestInfo2.getPid()).thenReturn(mPid2);
         when(mWifiInjector.getUserManager()).thenReturn(mUserManager);
+        when(mWifiInjector.getWifiCountryCode()).thenReturn(mWifiCountryCode);
         when(mWifiInjector.getWifiController()).thenReturn(mWifiController);
         when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
         when(mWifiInjector.getWifiStateMachine()).thenReturn(mWifiStateMachine);
@@ -277,6 +284,12 @@
         when(mWifiInjector.getWifiPermissionsUtil()).thenReturn(mWifiPermissionsUtil);
         when(mWifiInjector.getWifiSettingsStore()).thenReturn(mSettingsStore);
         when(mWifiInjector.getClock()).thenReturn(mClock);
+        when(mWifiStateMachine.syncStartSubscriptionProvisioning(anyInt(),
+                any(OsuProvider.class), any(IProvisioningCallback.class), any())).thenReturn(true);
+        when(mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)).thenReturn(true);
+        // Create an OSU provider that can be provisioned via an open OSU AP
+        mOsuProvider = PasspointProvisioningTestUtil.generateOsuProvider(true);
         mWifiServiceImpl = new WifiServiceImpl(mContext, mWifiInjector, mAsyncChannel);
         mWifiServiceImpl.setWifiHandlerLogForTest(mLog);
     }
@@ -586,94 +599,6 @@
     }
 
     /**
-     * Verify setWifiApEnabled works with the correct permissions and a null config.
-     */
-    @Test
-    public void testSetWifiApEnabledWithProperPermissionsWithNullConfig() {
-        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
-        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_CONFIG_TETHERING)))
-                .thenReturn(false);
-        mWifiServiceImpl.setWifiApEnabled(null, true);
-        verify(mWifiController)
-                .sendMessage(eq(CMD_SET_AP), eq(1), eq(0), mSoftApModeConfigCaptor.capture());
-        assertNull(mSoftApModeConfigCaptor.getValue().getWifiConfiguration());
-    }
-
-    /**
-     * Verify setWifiApEnabled works with correct permissions and a valid config.
-     *
-     * TODO: should really validate that ap configs have a set of basic config settings b/37280779
-     */
-    @Test
-    public void testSetWifiApEnabledWithProperPermissionsWithValidConfig() {
-        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
-        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_CONFIG_TETHERING)))
-                .thenReturn(false);
-        WifiConfiguration apConfig = new WifiConfiguration();
-        mWifiServiceImpl.setWifiApEnabled(apConfig, true);
-        verify(mWifiController).sendMessage(
-                eq(CMD_SET_AP), eq(1), eq(0), mSoftApModeConfigCaptor.capture());
-        assertEquals(apConfig, mSoftApModeConfigCaptor.getValue().getWifiConfiguration());
-    }
-
-    /**
-     * Verify setWifiApEnabled when disabling softap with correct permissions sends the correct
-     * message to WifiController.
-     */
-    @Test
-    public void testSetWifiApEnabledFalseWithProperPermissionsWithNullConfig() {
-        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
-        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_CONFIG_TETHERING)))
-                .thenReturn(false);
-        mWifiServiceImpl.setWifiApEnabled(null, false);
-        verify(mWifiController)
-                .sendMessage(eq(CMD_SET_AP), eq(0), eq(0), mSoftApModeConfigCaptor.capture());
-        assertNull(mSoftApModeConfigCaptor.getValue().getWifiConfiguration());
-    }
-
-    /**
-     * setWifiApEnabled should fail if the provided config is not valid.
-     */
-    @Test
-    public void testSetWifiApEnabledWithProperPermissionInvalidConfigFails() {
-        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
-        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_CONFIG_TETHERING)))
-                .thenReturn(false);
-        // mApConfig is a mock and the values are not set - triggering the invalid config.  Testing
-        // will be improved when we actually do test softap configs in b/37280779
-        mWifiServiceImpl.setWifiApEnabled(mApConfig, true);
-        verify(mWifiController, never())
-                .sendMessage(eq(CMD_SET_AP), eq(1), eq(0), any(SoftApModeConfiguration.class));
-    }
-
-    /**
-     * setWifiApEnabled should throw a security exception when the caller does not have the correct
-     * permissions.
-     */
-    @Test(expected = SecurityException.class)
-    public void testSetWifiApEnabledThrowsSecurityExceptionWithoutConfigOverridePermission()
-            throws Exception {
-        doThrow(new SecurityException()).when(mContext)
-                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.CHANGE_WIFI_STATE),
-                        eq("WifiService"));
-        mWifiServiceImpl.setWifiApEnabled(null, true);
-    }
-
-    /**
-     * setWifiApEnabled should throw a SecurityException when disallow tethering is set for the
-     * user.
-     */
-    @Test(expected = SecurityException.class)
-    public void testSetWifiApEnabledThrowsSecurityExceptionWithDisallowTethering()
-            throws Exception {
-        when(mWifiPermissionsUtil.checkConfigOverridePermission(anyInt())).thenReturn(true);
-        when(mUserManager.hasUserRestriction(eq(UserManager.DISALLOW_CONFIG_TETHERING)))
-                .thenReturn(true);
-        mWifiServiceImpl.setWifiApEnabled(null, true);
-
-    }
-
-    /**
      * Verify caller with proper permission can call startSoftAp.
      */
     @Test
@@ -1600,6 +1525,60 @@
     }
 
     /**
+     * Verify that the call to startSubscriptionProvisioning is redirected to the Passpoint
+     * specific API startSubscriptionProvisioning when the caller has the right permissions.
+     */
+    @Test
+    public void testStartSubscriptionProvisioningWithPermission() throws Exception {
+        mWifiServiceImpl.startSubscriptionProvisioning(mOsuProvider, mProvisioningCallback);
+        verify(mWifiStateMachine).syncStartSubscriptionProvisioning(anyInt(),
+                eq(mOsuProvider), eq(mProvisioningCallback), any());
+    }
+
+    /**
+     * Verify that the call to startSubscriptionProvisioning is not directed to the Passpoint
+     * specific API startSubscriptionProvisioning when the feature is not supported.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testStartSubscriptionProvisioniningPasspointUnsupported() throws Exception {
+        when(mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)).thenReturn(false);
+        mWifiServiceImpl.startSubscriptionProvisioning(mOsuProvider, mProvisioningCallback);
+    }
+
+    /**
+     * Verify that the call to startSubscriptionProvisioning is not redirected to the Passpoint
+     * specific API startSubscriptionProvisioning when the caller provides invalid arguments
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testStartSubscriptionProvisioningWithInvalidProvider() throws Exception {
+        mWifiServiceImpl.startSubscriptionProvisioning(null, mProvisioningCallback);
+    }
+
+
+    /**
+     * Verify that the call to startSubscriptionProvisioning is not redirected to the Passpoint
+     * specific API startSubscriptionProvisioning when the caller provides invalid callback
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testStartSubscriptionProvisioningWithInvalidCallback() throws Exception {
+        mWifiServiceImpl.startSubscriptionProvisioning(mOsuProvider, null);
+    }
+
+    /**
+     * Verify that the call to startSubscriptionProvisioning is not redirected to the Passpoint
+     * specific API startSubscriptionProvisioning when the caller doesn't have NETWORK_SETTINGS
+     * permissions.
+     */
+    @Test(expected = SecurityException.class)
+    public void testStartSubscriptionProvisioningWithoutPermission() throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                        eq("WifiService"));
+        mWifiServiceImpl.startSubscriptionProvisioning(mOsuProvider, mProvisioningCallback);
+    }
+
+    /**
      * Verify that a call to {@link WifiServiceImpl#restoreBackupData(byte[])} is only allowed from
      * callers with the signature only NETWORK_SETTINGS permission.
      */
@@ -1640,6 +1619,31 @@
     }
 
     /**
+     * Verify that a call to {@link WifiServiceImpl#enableVerboseLogging(int)} is allowed from
+     * callers with the signature only NETWORK_SETTINGS permission.
+     */
+    public void testEnableVerboseLoggingWithNetworkSettingsPermission() {
+        doNothing().when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                        eq("WifiService"));
+        mWifiServiceImpl.enableVerboseLogging(1);
+        verify(mWifiStateMachine).enableVerboseLogging(anyInt());
+    }
+
+    /**
+     * Verify that a call to {@link WifiServiceImpl#enableVerboseLogging(int)} is not allowed from
+     * callers without the signature only NETWORK_SETTINGS permission.
+     */
+    @Test(expected = SecurityException.class)
+    public void testEnableVerboseLoggingWithNoNetworkSettingsPermission() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                        eq("WifiService"));
+        mWifiServiceImpl.enableVerboseLogging(1);
+        verify(mWifiStateMachine, never()).enableVerboseLogging(anyInt());
+    }
+
+    /**
      * Helper to test handling of async messages by wifi service when the message comes from an
      * app without {@link android.Manifest.permission#CHANGE_WIFI_STATE} permission.
      */
@@ -1817,4 +1821,27 @@
         verifyAsyncChannelMessageHandlingWithChangePermisson(
                 WifiManager.RSSI_PKTCNT_FETCH, new Object());
     }
+
+    /**
+     * Verify that setCountryCode() calls WifiCountryCode object on succeess.
+     */
+    @Test
+    public void testSetCountryCode() throws Exception {
+        mWifiServiceImpl.setCountryCode(TEST_COUNTRY_CODE);
+        verify(mWifiCountryCode).setCountryCode(TEST_COUNTRY_CODE);
+    }
+
+    /**
+     * Verify that setCountryCode() fails and doesn't call WifiCountryCode object
+     * if the caller doesn't have CONNECTIVITY_INTERNAL permission.
+     */
+    @Test(expected = SecurityException.class)
+    public void testSetCountryCodeFailsWithoutConnectivityInternalPermission() throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(
+                        eq(android.Manifest.permission.CONNECTIVITY_INTERNAL),
+                        eq("ConnectivityService"));
+        mWifiServiceImpl.setCountryCode(TEST_COUNTRY_CODE);
+        verify(mWifiCountryCode, never()).setCountryCode(TEST_COUNTRY_CODE);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java
index 49c7d18..f2d6cc6 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java
@@ -20,13 +20,13 @@
 import static org.mockito.Mockito.*;
 
 import android.net.wifi.IApInterface;
-import android.net.wifi.IWificond;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.INetworkManagementService;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
+import android.util.Pair;
 
 import org.junit.After;
 import org.junit.Before;
@@ -50,11 +50,12 @@
     private static final String CLIENT_MODE_ACTIVE_STATE_STRING = "ClientModeActiveState";
     private static final String SCAN_ONLY_MODE_ACTIVE_STATE_STRING = "ScanOnlyModeActiveState";
     private static final String SOFT_AP_MODE_ACTIVE_STATE_STRING = "SoftAPModeActiveState";
+    private static final String WIFI_IFACE_NAME = "mockWlan";
 
     @Mock WifiInjector mWifiInjector;
+    @Mock WifiNative mWifiNative;
     @Mock WifiApConfigStore mWifiApConfigStore;
     TestLooper mLooper;
-    @Mock IWificond mWificond;
     @Mock IApInterface mApInterface;
     @Mock INetworkManagementService mNMService;
     @Mock SoftApManager mSoftApManager;
@@ -71,13 +72,18 @@
         MockitoAnnotations.initMocks(this);
         mLooper = new TestLooper();
 
-        mWifiInjector = mock(WifiInjector.class);
+        when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
+        when(mWifiNative.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         mWifiStateMachinePrime = createWifiStateMachinePrime();
+
+        // creating a new WSMP cleans up any existing interfaces, check and reset expectations
+        verifyCleanupCalled();
+        reset(mWifiNative);
     }
 
     private WifiStateMachinePrime createWifiStateMachinePrime() {
-        when(mWifiInjector.makeWificond()).thenReturn(null);
-        return new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(), mNMService);
+        return new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(),
+                mWifiNative, mNMService);
     }
 
     /**
@@ -89,7 +95,8 @@
     }
 
     private void enterSoftApActiveMode() throws Exception {
-        enterSoftApActiveMode(null);
+        enterSoftApActiveMode(
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
     }
 
     /**
@@ -97,10 +104,11 @@
      *
      * This method puts the test object into the correct state and verifies steps along the way.
      */
-    private void enterSoftApActiveMode(WifiConfiguration wifiConfig) throws Exception {
+    private void enterSoftApActiveMode(SoftApModeConfiguration softApConfig) throws Exception {
         String fromState = mWifiStateMachinePrime.getCurrentMode();
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
-        when(mWificond.createApInterface()).thenReturn(mApInterface);
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_SUCCESS, mApInterface));
+        when(mApInterface.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         doAnswer(
                 new Answer<Object>() {
                     public SoftApManager answer(InvocationOnMock invocation) {
@@ -108,35 +116,43 @@
                         assertEquals(mNMService, (INetworkManagementService) args[0]);
                         mSoftApListener = (SoftApManager.Listener) args[1];
                         assertEquals(mApInterface, (IApInterface) args[2]);
-                        assertEquals(wifiConfig, (WifiConfiguration) args[3]);
+                        assertEquals(WIFI_IFACE_NAME, (String) args[3]);
+                        assertEquals(softApConfig, (SoftApModeConfiguration) args[4]);
                         return mSoftApManager;
                     }
                 }).when(mWifiInjector).makeSoftApManager(any(INetworkManagementService.class),
                                                          any(SoftApManager.Listener.class),
                                                          any(IApInterface.class),
+                                                         anyString(),
                                                          any());
-        mWifiStateMachinePrime.enterSoftAPMode(wifiConfig);
+        mWifiStateMachinePrime.enterSoftAPMode(softApConfig);
         mLooper.dispatchAll();
         Log.e("WifiStateMachinePrimeTest", "check fromState: " + fromState);
         if (!fromState.equals(WIFI_DISABLED_STATE_STRING)) {
-            verify(mWificond).tearDownInterfaces();
+            verifyCleanupCalled();
         }
         assertEquals(SOFT_AP_MODE_ACTIVE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
         verify(mSoftApManager).start();
     }
 
+    private void verifyCleanupCalled() {
+        // for now, this is a single call, but make a helper to avoid adding any additional cleanup
+        // checks
+        verify(mWifiNative).tearDown();
+    }
+
     /**
-     * Test that when a new instance of WifiStateMachinePrime is created, any existing interfaces in
-     * the retrieved Wificond instance are cleaned up.
+     * Test that when a new instance of WifiStateMachinePrime is created, any existing
+     * resources in WifiNative are cleaned up.
      * Expectations:  When the new WifiStateMachinePrime instance is created a call to
-     * Wificond.tearDownInterfaces() is made.
+     * WifiNative.tearDown() is made.
      */
     @Test
-    public void testWificondExistsOnStartup() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+    public void testCleanupOnStart() throws Exception {
         WifiStateMachinePrime testWifiStateMachinePrime =
-                new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(), mNMService);
-        verify(mWificond).tearDownInterfaces();
+                new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(),
+                                          mWifiNative, mNMService);
+        verifyCleanupCalled();
     }
 
     /**
@@ -150,12 +166,10 @@
 
     /**
      * Test that WifiStateMachinePrime properly enters the SoftApModeActiveState from another state.
-     * Expectations: When going from one state to another, any interfaces that are still up are torn
-     * down.
+     * Expectations: When going from one state to another, cleanup will be called
      */
     @Test
     public void testEnterSoftApModeFromDifferentState() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
         mWifiStateMachinePrime.enterClientMode();
         mLooper.dispatchAll();
         assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
@@ -172,7 +186,7 @@
         mWifiStateMachinePrime.disableWifi();
         mLooper.dispatchAll();
         verify(mSoftApManager).stop();
-        verify(mWificond).tearDownInterfaces();
+        verifyCleanupCalled();
         assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
     }
 
@@ -181,15 +195,18 @@
      */
     @Test
     public void testDisableWifiFromSoftApModeState() throws Exception {
-        // Use a failure getting wificond to stay in the SoftAPModeState
-        when(mWifiInjector.makeWificond()).thenReturn(null);
-        mWifiStateMachinePrime.enterSoftAPMode(null);
+        // Use a failure getting the interface to stay in SoftApMode
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                            .thenReturn(new Pair(WifiNative.SETUP_FAILURE_HAL, null));
+
+        mWifiStateMachinePrime.enterSoftAPMode(
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mLooper.dispatchAll();
         assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
 
         mWifiStateMachinePrime.disableWifi();
         mLooper.dispatchAll();
-        // mWificond will be null due to this test, no call to tearDownInterfaces here.
+        verifyCleanupCalled();
         assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
     }
 
@@ -205,25 +222,11 @@
         mWifiStateMachinePrime.enterClientMode();
         mLooper.dispatchAll();
         verify(mSoftApManager).stop();
-        verify(mWificond).tearDownInterfaces();
+        verifyCleanupCalled();
         assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
     }
 
     /**
-     * Test that we do not attempt to enter SoftApModeActiveState when we cannot get a reference to
-     * wificond.
-     * Expectations: After a failed attempt to get wificond from WifiInjector, we should remain in
-     * the SoftApModeState.
-     */
-    @Test
-    public void testWificondNullWhenSwitchingToApMode() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(null);
-        mWifiStateMachinePrime.enterSoftAPMode(null);
-        mLooper.dispatchAll();
-        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
-    }
-
-    /**
      * Test that we do not attempt to enter SoftApModeActiveState when we cannot get an ApInterface
      * from wificond.
      * Expectations: After a failed attempt to get an ApInterface from WifiInjector, we should
@@ -231,23 +234,25 @@
      */
     @Test
     public void testAPInterfaceFailedWhenSwitchingToApMode() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
-        when(mWificond.createApInterface()).thenReturn(null);
-        mWifiStateMachinePrime.enterSoftAPMode(null);
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_FAILURE_HAL, null));
+        mWifiStateMachinePrime.enterSoftAPMode(
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mLooper.dispatchAll();
         assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
     }
 
     /**
-     * Test that we do can enter the SoftApModeActiveState if we are already in the SoftApModeState.
+     * Test that we do enter the SoftApModeActiveState if we are already in the SoftApModeState.
      * Expectations: We should exit the current SoftApModeState and re-enter before successfully
      * entering the SoftApModeActiveState.
      */
     @Test
     public void testEnterSoftApModeActiveWhenAlreadyInSoftApMode() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
-        when(mWificond.createApInterface()).thenReturn(null);
-        mWifiStateMachinePrime.enterSoftAPMode(null);
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_SUCCESS, null));
+        mWifiStateMachinePrime.enterSoftAPMode(
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mLooper.dispatchAll();
         assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
 
@@ -294,7 +299,9 @@
     public void testConfigIsPassedToWifiInjector() throws Exception {
         WifiConfiguration config = new WifiConfiguration();
         config.SSID = "ThisIsAConfig";
-        enterSoftApActiveMode(config);
+        SoftApModeConfiguration softApConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config);
+        enterSoftApActiveMode(softApConfig);
     }
 
     /**
@@ -307,7 +314,7 @@
      */
     @Test
     public void testNullConfigIsPassedToWifiInjector() throws Exception {
-        enterSoftApActiveMode(null);
+        enterSoftApActiveMode();
     }
 
     /**
@@ -316,77 +323,90 @@
      * config - this second call should use the correct config.
      */
     @Test
-    public void testNullConfigFailsSecondCallWithConfigSuccessful() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
-        when(mWificond.createApInterface()).thenReturn(null);
-        mWifiStateMachinePrime.enterSoftAPMode(null);
+    public void testNullApModeConfigFails() throws Exception {
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_SUCCESS, null));
+        mWifiStateMachinePrime.enterSoftAPMode(
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mLooper.dispatchAll();
         assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
         WifiConfiguration config = new WifiConfiguration();
         config.SSID = "ThisIsAConfig";
-        enterSoftApActiveMode(config);
+        SoftApModeConfiguration softApConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config);
+        enterSoftApActiveMode(softApConfig);
     }
 
     /**
-     * Test that a failed call to start softap with a valid config has the config saved for future
-     * calls to enable softap.
+     * Test that a failed call to start softap with a valid config does not persist the ap
+     * configuration to the WifiApConfigStore.
      *
-     * Expectations: A call to start SoftAPMode with a config should write out the config if we
+     * Expectations: A call to start SoftAPMode with a config should not write out the config if we
      * did not create a SoftApManager.
      */
     @Test
     public void testValidConfigIsSavedOnFailureToStart() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(null);
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_SUCCESS, null));
         when(mWifiInjector.getWifiApConfigStore()).thenReturn(mWifiApConfigStore);
         WifiConfiguration config = new WifiConfiguration();
         config.SSID = "ThisIsAConfig";
-        mWifiStateMachinePrime.enterSoftAPMode(config);
+        SoftApModeConfiguration softApConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config);
+        mWifiStateMachinePrime.enterSoftAPMode(softApConfig);
         mLooper.dispatchAll();
         assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
-        verify(mWifiApConfigStore).setApConfiguration(eq(config));
+        verify(mWifiApConfigStore, never()).setApConfiguration(eq(config));
     }
 
     /**
-     * Thest that two calls to switch to SoftAPMode in succession ends up with the correct config.
+     * Test that two calls to switch to SoftAPMode in succession ends up with the correct config.
      *
      * Expectation: we should end up in SoftAPMode state configured with the second config.
      */
     @Test
     public void testStartSoftApModeTwiceWithTwoConfigs() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
-        when(mWificond.createApInterface()).thenReturn(mApInterface);
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_SUCCESS, mApInterface));
+        when(mApInterface.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         when(mWifiInjector.getWifiApConfigStore()).thenReturn(mWifiApConfigStore);
         WifiConfiguration config1 = new WifiConfiguration();
         config1.SSID = "ThisIsAConfig";
+        SoftApModeConfiguration softApConfig1 =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config1);
         WifiConfiguration config2 = new WifiConfiguration();
         config2.SSID = "ThisIsASecondConfig";
+        SoftApModeConfiguration softApConfig2 =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, config2);
 
         when(mWifiInjector.makeSoftApManager(any(INetworkManagementService.class),
                                              any(SoftApManager.Listener.class),
                                              any(IApInterface.class),
-                                             eq(config1)))
+                                             anyString(),
+                                             eq(softApConfig1)))
                 .thenReturn(mSoftApManager);
         when(mWifiInjector.makeSoftApManager(any(INetworkManagementService.class),
                                              any(SoftApManager.Listener.class),
                                              any(IApInterface.class),
-                                             eq(config2)))
+                                             anyString(),
+                                             eq(softApConfig2)))
                 .thenReturn(mSoftApManager);
 
 
-        mWifiStateMachinePrime.enterSoftAPMode(config1);
-        mWifiStateMachinePrime.enterSoftAPMode(config2);
+        mWifiStateMachinePrime.enterSoftAPMode(softApConfig1);
+        mWifiStateMachinePrime.enterSoftAPMode(softApConfig2);
         mLooper.dispatchAll();
         assertEquals(SOFT_AP_MODE_ACTIVE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
     }
 
     /**
      * Test that we safely disable wifi if it is already disabled.
-     * Expectations: We should not interact with wificond since we should have already cleaned up
+     * Expectations: We should not interact with WifiNative since we should have already cleaned up
      * everything.
      */
     @Test
     public void disableWifiWhenAlreadyOff() throws Exception {
-        verifyNoMoreInteractions(mWificond);
+        verifyNoMoreInteractions(mWifiNative);
         mWifiStateMachinePrime.disableWifi();
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
index 090224e..e6972ea 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
@@ -63,6 +63,8 @@
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.WpsInfo;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.net.wifi.p2p.IWifiP2pManager;
@@ -80,7 +82,6 @@
 import android.os.Messenger;
 import android.os.PowerManager;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.test.TestLooper;
@@ -102,8 +103,10 @@
 import com.android.internal.util.StateMachine;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.hotspot2.PasspointManager;
+import com.android.server.wifi.hotspot2.PasspointProvisioningTestUtil;
 import com.android.server.wifi.p2p.WifiP2pServiceImpl;
 import com.android.server.wifi.util.WifiPermissionsUtil;
+import com.android.server.wifi.util.WifiPermissionsWrapper;
 
 import org.junit.After;
 import org.junit.Before;
@@ -150,6 +153,7 @@
     private static final int WPS_FRAMEWORK_NETWORK_ID = 10;
     private static final String DEFAULT_TEST_SSID = "\"GoogleGuest\"";
     private static final String OP_PACKAGE_NAME = "com.xxx";
+    private static final int TEST_UID = Process.SYSTEM_UID + 1000;
 
     private long mBinderToken;
 
@@ -334,6 +338,7 @@
     IpClient.Callback mIpClientCallback;
     PhoneStateListener mPhoneStateListener;
     NetworkRequest mDefaultNetworkRequest;
+    OsuProvider mOsuProvider;
 
     final ArgumentCaptor<SoftApManager.Listener> mSoftApManagerListenerCaptor =
                     ArgumentCaptor.forClass(SoftApManager.Listener.class);
@@ -352,8 +357,6 @@
     @Mock IWificond mWificond;
     @Mock IApInterface mApInterface;
     @Mock IClientInterface mClientInterface;
-    @Mock IBinder mApInterfaceBinder;
-    @Mock IBinder mClientInterfaceBinder;
     @Mock IBinder mPackageManagerBinder;
     @Mock WifiConfigManager mWifiConfigManager;
     @Mock WifiNative mWifiNative;
@@ -370,6 +373,10 @@
     @Mock ScanDetailCache mScanDetailCache;
     @Mock BaseWifiDiagnostics mWifiDiagnostics;
     @Mock ConnectivityManager mConnectivityManager;
+    @Mock IProvisioningCallback mProvisioningCallback;
+    @Mock HandlerThread mWifiServiceHandlerThread;
+    @Mock WifiPermissionsWrapper mWifiPermissionsWrapper;
+    @Mock WakeupController mWakeupController;
 
     public WifiStateMachineTest() throws Exception {
     }
@@ -401,8 +408,9 @@
         when(mWifiInjector.makeWifiConnectivityManager(any(WifiInfo.class), anyBoolean()))
                 .thenReturn(mWifiConnectivityManager);
         when(mWifiInjector.makeSoftApManager(any(INetworkManagementService.class),
-                mSoftApManagerListenerCaptor.capture(), any(IApInterface.class),
-                any(WifiConfiguration.class)))
+                                             mSoftApManagerListenerCaptor.capture(),
+                                             any(IApInterface.class), anyString(),
+                                             any(SoftApModeConfiguration.class)))
                 .thenReturn(mSoftApManager);
         when(mWifiInjector.getPasspointManager()).thenReturn(mPasspointManager);
         when(mWifiInjector.getWifiStateTracker()).thenReturn(mWifiStateTracker);
@@ -412,17 +420,23 @@
         when(mWifiInjector.getWifiPermissionsUtil()).thenReturn(mWifiPermissionsUtil);
         when(mWifiInjector.makeTelephonyManager()).thenReturn(mTelephonyManager);
         when(mWifiInjector.getClock()).thenReturn(mClock);
+        when(mWifiServiceHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
+        when(mWifiInjector.getWifiServiceHandlerThread()).thenReturn(mWifiServiceHandlerThread);
+        when(mWifiInjector.getWifiPermissionsWrapper()).thenReturn(mWifiPermissionsWrapper);
+        when(mWifiInjector.getWakeupController()).thenReturn(mWakeupController);
 
-        when(mWifiNative.setupForClientMode())
+        when(mWifiNative.setupForClientMode(WIFI_IFACE_NAME))
                 .thenReturn(Pair.create(WifiNative.SETUP_SUCCESS, mClientInterface));
-        when(mWifiNative.setupForSoftApMode())
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
                 .thenReturn(Pair.create(WifiNative.SETUP_SUCCESS, mApInterface));
         when(mApInterface.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         when(mWifiNative.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         when(mWifiNative.enableSupplicant()).thenReturn(true);
         when(mWifiNative.disableSupplicant()).thenReturn(true);
         when(mWifiNative.getFrameworkNetworkId(anyInt())).thenReturn(0);
-
+        when(mWifiNative.initializeVendorHal(any(WifiNative.VendorHalDeathEventHandler.class)))
+                .thenReturn(true);
+        when(mWifiNative.registerWificondDeathHandler(any())).thenReturn(true);
 
         mFrameworkFacade = getFrameworkFacade();
         mContext = getContext();
@@ -448,9 +462,6 @@
                 new UserInfo(UserHandle.USER_SYSTEM, "owner", 0),
                 new UserInfo(11, "managed profile", 0)));
 
-        when(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
-        when(mClientInterface.asBinder()).thenReturn(mClientInterfaceBinder);
-
         doAnswer(new AnswerWithArguments() {
             public void answer(PhoneStateListener phoneStateListener, int events)
                     throws Exception {
@@ -459,7 +470,11 @@
         }).when(mTelephonyManager).listen(any(PhoneStateListener.class), anyInt());
 
         when(mWifiPermissionsUtil.checkNetworkSettingsPermission(anyInt())).thenReturn(true);
+        when(mWifiPermissionsWrapper.getLocalMacAddressPermission(anyInt()))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
         initializeWsm();
+
+        mOsuProvider = PasspointProvisioningTestUtil.generateOsuProvider(true);
     }
 
     private void registerAsyncChannel(Consumer<AsyncChannel> consumer, Messenger messenger) {
@@ -585,33 +600,36 @@
 
         assertEquals("SoftApState", getCurrentState().getName());
 
-        verify(mWifiNative).setupForSoftApMode();
+        verify(mWifiNative).setupForSoftApMode(WIFI_IFACE_NAME);
         verify(mSoftApManager).start();
 
-        // reset expectations for mContext due to previously sent AP broadcast
-        reset(mContext);
-
         // get the SoftApManager.Listener and trigger some updates
         SoftApManager.Listener listener = mSoftApManagerListenerCaptor.getValue();
         listener.onStateChanged(WIFI_AP_STATE_ENABLING, 0);
+        assertEquals(WIFI_AP_STATE_ENABLING, mWsm.syncGetWifiApState());
         listener.onStateChanged(WIFI_AP_STATE_ENABLED, 0);
+        assertEquals(WIFI_AP_STATE_ENABLED, mWsm.syncGetWifiApState());
         listener.onStateChanged(WIFI_AP_STATE_DISABLING, 0);
+        assertEquals(WIFI_AP_STATE_DISABLING, mWsm.syncGetWifiApState());
         // note, this will trigger a mode change when TestLooper is dispatched
         listener.onStateChanged(WIFI_AP_STATE_DISABLED, 0);
+        assertEquals(WIFI_AP_STATE_DISABLED, mWsm.syncGetWifiApState());
+    }
 
-        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mContext, times(4))
-                .sendStickyBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL));
+    private void setupMockWpsPbc() throws Exception {
+        loadComponentsInStaMode();
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        assertEquals("DisconnectedState", getCurrentState().getName());
 
-        List<Intent> capturedIntents = intentCaptor.getAllValues();
-        checkApStateChangedBroadcast(capturedIntents.get(0), WIFI_AP_STATE_ENABLING,
-                WIFI_AP_STATE_DISABLED, HOTSPOT_NO_ERROR, WIFI_IFACE_NAME, mode);
-        checkApStateChangedBroadcast(capturedIntents.get(1), WIFI_AP_STATE_ENABLED,
-                WIFI_AP_STATE_ENABLING, HOTSPOT_NO_ERROR, WIFI_IFACE_NAME, mode);
-        checkApStateChangedBroadcast(capturedIntents.get(2), WIFI_AP_STATE_DISABLING,
-                WIFI_AP_STATE_ENABLED, HOTSPOT_NO_ERROR, WIFI_IFACE_NAME, mode);
-        checkApStateChangedBroadcast(capturedIntents.get(3), WIFI_AP_STATE_DISABLED,
-                WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR, WIFI_IFACE_NAME, mode);
+        WpsInfo wpsInfo = new WpsInfo();
+        wpsInfo.setup = WpsInfo.PBC;
+        wpsInfo.BSSID = sBSSID;
+        when(mWifiNative.removeAllNetworks()).thenReturn(true);
+        when(mWifiNative.startWpsPbc(anyString())).thenReturn(true);
+        mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
+        mLooper.dispatchAll();
+        assertEquals("WpsRunningState", getCurrentState().getName());
     }
 
     @Test
@@ -640,14 +658,13 @@
         // We start out with valid default values, break them going backwards so that
         // we test all the bailout cases.
 
-        // ClientInterface dies after creation.
-        doThrow(new RemoteException()).when(mClientInterfaceBinder).linkToDeath(any(), anyInt());
+        // wificond dies after inteface creation.
+        when(mWifiNative.registerWificondDeathHandler(any())).thenReturn(false);
         mWsm.setSupplicantRunning(true);
         mLooper.dispatchAll();
         assertEquals("InitialState", getCurrentState().getName());
 
         // Failed to even create the client interface.
-        when(mWificond.createClientInterface()).thenReturn(null);
         mWsm.setSupplicantRunning(true);
         mLooper.dispatchAll();
         assertEquals("InitialState", getCurrentState().getName());
@@ -960,7 +977,7 @@
         mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
         mLooper.dispatchAll();
 
-        verify(mWifiNative).setupForClientMode();
+        verify(mWifiNative).setupForClientMode(WIFI_IFACE_NAME);
         verify(mWifiLastResortWatchdog).clearAllFailureCounts();
     }
 
@@ -1087,7 +1104,7 @@
     private void setupAndStartConnectSequence(WifiConfiguration config) throws Exception {
         when(mWifiConfigManager.enableNetwork(eq(config.networkId), eq(true), anyInt()))
                 .thenReturn(true);
-        when(mWifiConfigManager.checkAndUpdateLastConnectUid(eq(config.networkId), anyInt()))
+        when(mWifiConfigManager.updateLastConnectUid(eq(config.networkId), anyInt()))
                 .thenReturn(true);
         when(mWifiConfigManager.getConfiguredNetwork(eq(config.networkId)))
                 .thenReturn(config);
@@ -1243,44 +1260,6 @@
     }
 
     @Test
-    public void connectWithNoEnablePermission() throws Exception {
-        initializeAndAddNetworkAndVerifySuccess();
-        when(mWifiConfigManager.enableNetwork(eq(0), eq(true), anyInt())).thenReturn(false);
-        when(mWifiConfigManager.checkAndUpdateLastConnectUid(eq(0), anyInt())).thenReturn(false);
-
-        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
-        mLooper.dispatchAll();
-        verify(mWifiNative).removeAllNetworks();
-
-        mLooper.startAutoDispatch();
-        assertTrue(mWsm.syncEnableNetwork(mWsmAsyncChannel, 0, true));
-        mLooper.stopAutoDispatch();
-
-        verify(mWifiConfigManager).enableNetwork(eq(0), eq(true), anyInt());
-        verify(mWifiConnectivityManager, never()).setUserConnectChoice(eq(0));
-
-        mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID);
-        mLooper.dispatchAll();
-
-        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
-                new StateChangeResult(0, sWifiSsid, sBSSID, SupplicantState.COMPLETED));
-        mLooper.dispatchAll();
-
-        assertEquals("ObtainingIpState", getCurrentState().getName());
-
-        DhcpResults dhcpResults = new DhcpResults();
-        dhcpResults.setGateway("1.2.3.4");
-        dhcpResults.setIpAddress("192.168.1.100", 0);
-        dhcpResults.addDns("8.8.8.8");
-        dhcpResults.setLeaseDuration(3600);
-
-        injectDhcpSuccess(dhcpResults);
-        mLooper.dispatchAll();
-
-        assertEquals("ConnectedState", getCurrentState().getName());
-    }
-
-    @Test
     public void enableWithInvalidNetworkId() throws Exception {
         initializeAndAddNetworkAndVerifySuccess();
         when(mWifiConfigManager.getConfiguredNetwork(eq(0))).thenReturn(null);
@@ -1294,7 +1273,7 @@
         mLooper.stopAutoDispatch();
 
         verify(mWifiConfigManager, never()).enableNetwork(eq(0), eq(true), anyInt());
-        verify(mWifiConfigManager, never()).checkAndUpdateLastConnectUid(eq(0), anyInt());
+        verify(mWifiConfigManager, never()).updateLastConnectUid(eq(0), anyInt());
     }
 
     /**
@@ -1814,6 +1793,57 @@
     }
 
     /**
+     * Verify that syncStartSubscriptionProvisioning will redirect calls with right parameters
+     * to {@link PasspointManager} with expected true being returned when in client mode.
+     */
+    @Test
+    public void syncStartSubscriptionProvisioningInClientMode() throws Exception {
+        mLooper.startAutoDispatch();
+        mWsm.syncInitialize(mWsmAsyncChannel);
+        verify(mPasspointManager).initializeProvisioner(any(Looper.class));
+        mLooper.stopAutoDispatch();
+
+        loadComponentsInStaMode();
+        when(mPasspointManager.startSubscriptionProvisioning(anyInt(),
+                any(OsuProvider.class), any(IProvisioningCallback.class))).thenReturn(true);
+        mLooper.startAutoDispatch();
+        assertEquals(true, mWsm.syncStartSubscriptionProvisioning(
+                OTHER_USER_UID, mOsuProvider, mProvisioningCallback, mWsmAsyncChannel));
+        verify(mPasspointManager).startSubscriptionProvisioning(OTHER_USER_UID, mOsuProvider,
+                mProvisioningCallback);
+        mLooper.stopAutoDispatch();
+    }
+
+    /**
+     * Verify that syncStartSubscriptionProvisioning will be a no-op and return false before
+     * SUPPLICANT_START command is received by the WSM.
+     */
+    @Test
+    public void syncStartSubscriptionProvisioningBeforeSupplicantOrAPStart() throws Exception {
+        mLooper.startAutoDispatch();
+        assertEquals(false, mWsm.syncStartSubscriptionProvisioning(
+                OTHER_USER_UID, mOsuProvider, mProvisioningCallback, mWsmAsyncChannel));
+        mLooper.stopAutoDispatch();
+        verify(mPasspointManager, never()).startSubscriptionProvisioning(
+                anyInt(), any(OsuProvider.class), any(IProvisioningCallback.class));
+    }
+
+    /**
+     * Verify that syncStartSubscriptionProvisioning will be a no-op and return false when not in
+     * client mode.
+     */
+    @Test
+    public void syncStartSubscriptionProvisioningInAPMode() throws Exception {
+        loadComponentsInApMode(WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
+        mLooper.startAutoDispatch();
+        assertEquals(false, mWsm.syncStartSubscriptionProvisioning(
+                OTHER_USER_UID, mOsuProvider, mProvisioningCallback, mWsmAsyncChannel));
+        mLooper.stopAutoDispatch();
+        verify(mPasspointManager, never()).startSubscriptionProvisioning(
+                anyInt(), any(OsuProvider.class), any(IProvisioningCallback.class));
+    }
+
+    /**
      *  Test that we disconnect from a network if it was removed while we are in the
      *  ObtainingIpState.
      */
@@ -1822,7 +1852,7 @@
         initializeAndAddNetworkAndVerifySuccess();
 
         when(mWifiConfigManager.enableNetwork(eq(0), eq(true), anyInt())).thenReturn(true);
-        when(mWifiConfigManager.checkAndUpdateLastConnectUid(eq(0), anyInt())).thenReturn(true);
+        when(mWifiConfigManager.updateLastConnectUid(eq(0), anyInt())).thenReturn(true);
 
         mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
         mLooper.dispatchAll();
@@ -1880,25 +1910,14 @@
      */
     @Test
     public void wpsPbcConnectSuccess() throws Exception {
-        loadComponentsInStaMode();
-        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
-        mLooper.dispatchAll();
-        assertEquals("DisconnectedState", getCurrentState().getName());
-
-        WpsInfo wpsInfo = new WpsInfo();
-        wpsInfo.setup = WpsInfo.PBC;
-        wpsInfo.BSSID = sBSSID;
-        when(mWifiNative.removeAllNetworks()).thenReturn(true);
-        when(mWifiNative.startWpsPbc(anyString())).thenReturn(true);
-        mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
-        mLooper.dispatchAll();
-        assertEquals("WpsRunningState", getCurrentState().getName());
-
+        setupMockWpsPbc();
         setupMocksForWpsNetworkMigration();
         mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
         mLooper.dispatchAll();
         assertEquals("ObtainingIpState", getCurrentState().getName());
         verifyMocksForWpsNetworkMigration();
+        verify(mWifiMetrics).incrementWpsAttemptCount();
+        verify(mWifiMetrics).incrementWpsSuccessCount();
     }
 
     /**
@@ -1918,7 +1937,9 @@
         mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
         mLooper.dispatchAll();
         verify(mWifiNative).startWpsPbc(eq(sBSSID));
-
+        verify(mWifiMetrics).incrementWpsAttemptCount();
+        verify(mWifiMetrics, never()).incrementWpsSuccessCount();
+        verify(mWifiMetrics).incrementWpsStartFailureCount();
         assertFalse("WpsRunningState".equals(getCurrentState().getName()));
     }
 
@@ -1928,20 +1949,7 @@
      */
     @Test
     public void wpsPbcConnectFailure_tooManyConfigs() throws Exception {
-        loadComponentsInStaMode();
-        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
-        mLooper.dispatchAll();
-        assertEquals("DisconnectedState", getCurrentState().getName());
-
-        WpsInfo wpsInfo = new WpsInfo();
-        wpsInfo.setup = WpsInfo.PBC;
-        wpsInfo.BSSID = sBSSID;
-        when(mWifiNative.removeAllNetworks()).thenReturn(true);
-        when(mWifiNative.startWpsPbc(anyString())).thenReturn(true);
-        mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
-        mLooper.dispatchAll();
-        assertEquals("WpsRunningState", getCurrentState().getName());
-
+        setupMockWpsPbc();
         setupMocksForWpsNetworkMigration();
         doAnswer(new AnswerWithArguments() {
             public boolean answer(Map<String, WifiConfiguration> configs,
@@ -1953,6 +1961,11 @@
         }).when(mWifiNative).migrateNetworksFromSupplicant(any(Map.class), any(SparseArray.class));
         mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
         mLooper.dispatchAll();
+        verify(mWifiMetrics).incrementWpsAttemptCount();
+        // When there are multiple networks in supplicant, it returns load success
+        // with invalid network id. Wps framework thinks that network connection succeeded
+        // and messages WPS_COMPLETED.
+        verify(mWifiMetrics).incrementWpsSuccessCount();
         assertTrue("DisconnectedState".equals(getCurrentState().getName()));
     }
 
@@ -1962,25 +1975,70 @@
      */
     @Test
     public void wpsPbcConnectFailure_migrateNetworksFailure() throws Exception {
-        loadComponentsInStaMode();
-        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
-        mLooper.dispatchAll();
-        assertEquals("DisconnectedState", getCurrentState().getName());
-
-        WpsInfo wpsInfo = new WpsInfo();
-        wpsInfo.setup = WpsInfo.PBC;
-        wpsInfo.BSSID = sBSSID;
-        when(mWifiNative.removeAllNetworks()).thenReturn(true);
-        when(mWifiNative.startWpsPbc(anyString())).thenReturn(true);
-        mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
-        mLooper.dispatchAll();
-        assertEquals("WpsRunningState", getCurrentState().getName());
-
+        setupMockWpsPbc();
         setupMocksForWpsNetworkMigration();
         when(mWifiNative.migrateNetworksFromSupplicant(any(Map.class), any(SparseArray.class)))
                 .thenReturn(false);
         mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
         mLooper.dispatchAll();
+        verify(mWifiMetrics, never()).incrementWpsSuccessCount();
+        verify(mWifiMetrics).incrementWpsSupplicantFailureCount();
+        assertEquals("DisconnectedState", getCurrentState().getName());
+    }
+
+    /**
+     * Verify failure in wps overlap event
+     */
+    @Test
+    public void wpsPbcConnectFailure_overlapFailure() throws Exception {
+        setupMockWpsPbc();
+        setupMocksForWpsNetworkMigration();
+        mWsm.sendMessage(WifiMonitor.WPS_OVERLAP_EVENT, 0, 0, null);
+        mLooper.dispatchAll();
+        verify(mWifiMetrics, never()).incrementWpsSuccessCount();
+        verify(mWifiMetrics).incrementWpsOverlapFailureCount();
+        assertEquals("DisconnectedState", getCurrentState().getName());
+    }
+
+    /**
+     * Verify failure in wps timeout event
+     */
+    @Test
+    public void wpsPbcConnectFailure_timeoutFailure() throws Exception {
+        setupMockWpsPbc();
+        setupMocksForWpsNetworkMigration();
+        mWsm.sendMessage(WifiMonitor.WPS_TIMEOUT_EVENT, 0, 0, null);
+        mLooper.dispatchAll();
+        verify(mWifiMetrics, never()).incrementWpsSuccessCount();
+        verify(mWifiMetrics).incrementWpsTimeoutFailureCount();
+        assertEquals("DisconnectedState", getCurrentState().getName());
+    }
+
+    /**
+     * Verify that wps metrics handles WPS_FAIL_EVENT
+     */
+    @Test
+    public void wpsPbcConnectFailure_otherFailure() throws Exception {
+        setupMockWpsPbc();
+        setupMocksForWpsNetworkMigration();
+        mWsm.sendMessage(WifiMonitor.WPS_FAIL_EVENT, 0, 1, null);
+        mLooper.dispatchAll();
+        verify(mWifiMetrics, never()).incrementWpsSuccessCount();
+        verify(mWifiMetrics).incrementWpsOtherConnectionFailureCount();
+        assertEquals("DisconnectedState", getCurrentState().getName());
+    }
+
+    /**
+     * Verify that wps metrics handles connection cancellation
+     */
+    @Test
+    public void wpsPbcConnectCancellation() throws Exception {
+        setupMockWpsPbc();
+        setupMocksForWpsNetworkMigration();
+        mWsm.sendMessage(WifiManager.CANCEL_WPS, 0, 0, null);
+        mLooper.dispatchAll();
+        verify(mWifiMetrics, never()).incrementWpsSuccessCount();
+        verify(mWifiMetrics).incrementWpsCancellationCount();
         assertEquals("DisconnectedState", getCurrentState().getName());
     }
 
@@ -2009,6 +2067,7 @@
         mWsm.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, null);
         mLooper.dispatchAll();
 
+        verify(mWifiMetrics).incrementWpsSuccessCount();
         assertEquals("ObtainingIpState", getCurrentState().getName());
         verifyMocksForWpsNetworkMigration();
     }
@@ -2030,6 +2089,8 @@
         mWsm.sendMessage(WifiManager.START_WPS, 0, 0, wpsInfo);
         mLooper.dispatchAll();
         verify(mWifiNative).startWpsPinDisplay(eq(sBSSID));
+        verify(mWifiMetrics).incrementWpsAttemptCount();
+        verify(mWifiMetrics).incrementWpsStartFailureCount();
 
         assertFalse("WpsRunningState".equals(getCurrentState().getName()));
     }
@@ -2062,20 +2123,19 @@
 
     @Test
     public void handleWificondDeath() throws Exception {
-        ArgumentCaptor<StateMachineDeathRecipient> deathHandlerCapturer =
-                ArgumentCaptor.forClass(StateMachineDeathRecipient.class);
+        ArgumentCaptor<WifiNative.WificondDeathEventHandler> deathHandlerCapturer =
+                ArgumentCaptor.forClass(WifiNative.WificondDeathEventHandler.class);
 
         // Trigger initialize to capture the death handler registration.
         loadComponentsInStaMode();
 
-        verify(mClientInterfaceBinder).linkToDeath(deathHandlerCapturer.capture(), anyInt());
-        StateMachineDeathRecipient deathHandler = deathHandlerCapturer.getValue();
+        verify(mWifiNative).registerWificondDeathHandler(deathHandlerCapturer.capture());
 
         mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
         mLooper.dispatchAll();
 
         // Now trigger the death notification.
-        deathHandler.binderDied();
+        deathHandlerCapturer.getValue().onDeath();
         mLooper.dispatchAll();
 
         verify(mWifiMetrics).incrementNumWificondCrashes();
@@ -2141,6 +2201,16 @@
         assertEquals(sBSSID1, wifiInfo.getBSSID());
         assertEquals(sFreq1, wifiInfo.getFrequency());
         assertEquals(SupplicantState.COMPLETED, wifiInfo.getSupplicantState());
+
+        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, sWifiSsid, sBSSID1, SupplicantState.DISCONNECTED));
+        mLooper.dispatchAll();
+
+        wifiInfo = mWsm.getWifiInfo();
+        assertEquals(null, wifiInfo.getBSSID());
+        assertEquals(WifiSsid.NONE, wifiInfo.getSSID());
+        assertEquals(WifiConfiguration.INVALID_NETWORK_ID, wifiInfo.getNetworkId());
+        assertEquals(SupplicantState.DISCONNECTED, wifiInfo.getSupplicantState());
     }
 
     /**
@@ -2236,56 +2306,29 @@
     }
 
     /**
-     * Test that the process uid has full wifiInfo access.
-     * Also tests that {@link WifiStateMachine#syncRequestConnectionInfo(String)} always
-     * returns a copy of WifiInfo.
-     */
-    @Test
-    public void testConnectedIdsAreVisibleFromOwnUid() throws Exception {
-        assertEquals(Process.myUid(), Binder.getCallingUid());
-        WifiInfo wifiInfo = mWsm.getWifiInfo();
-        wifiInfo.setBSSID(sBSSID);
-        wifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(sSSID));
-
-        connect();
-        WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName());
-
-        assertNotEquals(wifiInfo, connectionInfo);
-        assertEquals(wifiInfo.getSSID(), connectionInfo.getSSID());
-        assertEquals(wifiInfo.getBSSID(), connectionInfo.getBSSID());
-    }
-
-    /**
      * Test that connected SSID and BSSID are not exposed to an app that does not have the
      * appropriate permissions.
      * Also tests that {@link WifiStateMachine#syncRequestConnectionInfo(String)} always
      * returns a copy of WifiInfo.
      */
     @Test
-    public void testConnectedIdsAreHiddenFromRandomApp() throws Exception {
-        int actualUid = Binder.getCallingUid();
-        int fakeUid = Process.myUid() + 100000;
-        assertNotEquals(actualUid, fakeUid);
-        BinderUtil.setUid(fakeUid);
-        try {
-            WifiInfo wifiInfo = mWsm.getWifiInfo();
+    public void testConnectedIdsAreHiddenFromAppWithoutPermission() throws Exception {
+        WifiInfo wifiInfo = mWsm.getWifiInfo();
 
-            // Get into a connected state, with known BSSID and SSID
-            connect();
-            assertEquals(sBSSID, wifiInfo.getBSSID());
-            assertEquals(sWifiSsid, wifiInfo.getWifiSsid());
+        // Get into a connected state, with known BSSID and SSID
+        connect();
+        assertEquals(sBSSID, wifiInfo.getBSSID());
+        assertEquals(sWifiSsid, wifiInfo.getWifiSsid());
 
-            when(mWifiPermissionsUtil.canAccessFullConnectionInfo(any(), anyString(), eq(fakeUid),
-                    anyInt())).thenReturn(false);
+        when(mWifiPermissionsUtil.canAccessScanResults(anyString(), eq(TEST_UID), anyInt()))
+                .thenReturn(false);
 
-            WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName());
+        WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName(),
+                TEST_UID);
 
-            assertNotEquals(wifiInfo, connectionInfo);
-            assertEquals(WifiSsid.NONE, connectionInfo.getSSID());
-            assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, connectionInfo.getBSSID());
-        } finally {
-            BinderUtil.setUid(actualUid);
-        }
+        assertNotEquals(wifiInfo, connectionInfo);
+        assertEquals(WifiSsid.NONE, connectionInfo.getSSID());
+        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, connectionInfo.getBSSID());
     }
 
     /**
@@ -2296,29 +2339,48 @@
      */
     @Test
     public void testConnectedIdsAreHiddenOnSecurityException() throws Exception {
-        int actualUid = Binder.getCallingUid();
-        int fakeUid = Process.myUid() + 100000;
-        assertNotEquals(actualUid, fakeUid);
-        BinderUtil.setUid(fakeUid);
-        try {
-            WifiInfo wifiInfo = mWsm.getWifiInfo();
+        WifiInfo wifiInfo = mWsm.getWifiInfo();
 
-            // Get into a connected state, with known BSSID and SSID
-            connect();
-            assertEquals(sBSSID, wifiInfo.getBSSID());
-            assertEquals(sWifiSsid, wifiInfo.getWifiSsid());
+        // Get into a connected state, with known BSSID and SSID
+        connect();
+        assertEquals(sBSSID, wifiInfo.getBSSID());
+        assertEquals(sWifiSsid, wifiInfo.getWifiSsid());
 
-            when(mWifiPermissionsUtil.canAccessFullConnectionInfo(any(), anyString(), eq(fakeUid),
-                    anyInt())).thenThrow(new SecurityException());
+        when(mWifiPermissionsUtil.canAccessScanResults(anyString(), eq(TEST_UID), anyInt()))
+                .thenThrow(new SecurityException());
 
-            WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName());
+        WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName(),
+                TEST_UID);
 
-            assertNotEquals(wifiInfo, connectionInfo);
-            assertEquals(WifiSsid.NONE, connectionInfo.getSSID());
-            assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, connectionInfo.getBSSID());
-        } finally {
-            BinderUtil.setUid(actualUid);
-        }
+        assertNotEquals(wifiInfo, connectionInfo);
+        assertEquals(WifiSsid.NONE, connectionInfo.getSSID());
+        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, connectionInfo.getBSSID());
+    }
+
+    /**
+     * Test that connected SSID and BSSID are exposed to system server
+     */
+    @Test
+    public void testConnectedIdsAreVisibleFromSystemServer() throws Exception {
+        when(mWifiPermissionsWrapper.getLocalMacAddressPermission(anyInt()))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        WifiInfo wifiInfo = mWsm.getWifiInfo();
+        // Get into a connected state, with known BSSID and SSID
+        connect();
+        assertEquals(sBSSID, wifiInfo.getBSSID());
+        assertEquals(sWifiSsid, wifiInfo.getWifiSsid());
+
+        when(mWifiPermissionsUtil.canAccessScanResults(anyString(), eq(TEST_UID), anyInt()))
+                .thenReturn(true);
+
+        WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName(),
+                TEST_UID);
+
+        assertNotEquals(wifiInfo, connectionInfo);
+        assertEquals(wifiInfo.getSSID(), connectionInfo.getSSID());
+        assertEquals(wifiInfo.getBSSID(), connectionInfo.getBSSID());
+        assertEquals(wifiInfo.getMacAddress(), connectionInfo.getMacAddress());
     }
 
     /**
@@ -2327,30 +2389,24 @@
      */
     @Test
     public void testConnectedIdsAreVisibleFromPermittedApp() throws Exception {
-        int actualUid = Binder.getCallingUid();
-        int fakeUid = Process.myUid() + 100000;
-        BinderUtil.setUid(fakeUid);
-        try {
-            WifiInfo wifiInfo = mWsm.getWifiInfo();
+        WifiInfo wifiInfo = mWsm.getWifiInfo();
 
-            // Get into a connected state, with known BSSID and SSID
-            connect();
-            assertEquals(sBSSID, wifiInfo.getBSSID());
-            assertEquals(sWifiSsid, wifiInfo.getWifiSsid());
+        // Get into a connected state, with known BSSID and SSID
+        connect();
+        assertEquals(sBSSID, wifiInfo.getBSSID());
+        assertEquals(sWifiSsid, wifiInfo.getWifiSsid());
 
-            when(mWifiPermissionsUtil.canAccessFullConnectionInfo(any(), anyString(), eq(fakeUid),
-                    anyInt())).thenReturn(true);
+        when(mWifiPermissionsUtil.canAccessScanResults(anyString(), eq(TEST_UID), anyInt()))
+                .thenReturn(true);
 
-            WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName());
+        WifiInfo connectionInfo = mWsm.syncRequestConnectionInfo(mContext.getOpPackageName(),
+                TEST_UID);
 
-            assertNotEquals(wifiInfo, connectionInfo);
-            assertEquals(wifiInfo.getSSID(), connectionInfo.getSSID());
-            assertEquals(wifiInfo.getBSSID(), connectionInfo.getBSSID());
-            // Access to our MAC address uses a different permission, make sure it is not granted
-            assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, connectionInfo.getMacAddress());
-        } finally {
-            BinderUtil.setUid(actualUid);
-        }
+        assertNotEquals(wifiInfo, connectionInfo);
+        assertEquals(wifiInfo.getSSID(), connectionInfo.getSSID());
+        assertEquals(wifiInfo.getBSSID(), connectionInfo.getBSSID());
+        // Access to our MAC address uses a different permission, make sure it is not granted
+        assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, connectionInfo.getMacAddress());
     }
 
     /**
@@ -2404,6 +2460,8 @@
         Message reply = mWsmAsyncChannel.sendMessageSynchronously(WifiManager.START_WPS, 0, 0,
                 null);
         mLooper.stopAutoDispatch();
+        verify(mWifiMetrics).incrementWpsAttemptCount();
+        verify(mWifiMetrics).incrementWpsStartFailureCount();
         assertEquals(WifiManager.WPS_FAILED, reply.what);
     }
 
@@ -2427,7 +2485,7 @@
      */
     @Test
     public void testSetupForSoftApModeHalFailureIncrementsMetrics() throws Exception {
-        when(mWifiNative.setupForSoftApMode())
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
                 .thenReturn(Pair.create(WifiNative.SETUP_FAILURE_HAL, null));
 
         SoftApModeConfiguration config = new SoftApModeConfiguration(
@@ -2435,7 +2493,7 @@
         mWsm.setHostApRunning(config, true);
         mLooper.dispatchAll();
 
-        verify(mWifiNative).setupForSoftApMode();
+        verify(mWifiNative).setupForSoftApMode(WIFI_IFACE_NAME);
         verify(mWifiMetrics).incrementNumWifiOnFailureDueToHal();
     }
 
@@ -2444,7 +2502,7 @@
      */
     @Test
     public void testSetupForSoftApModeWificondFailureIncrementsMetrics() throws Exception {
-        when(mWifiNative.setupForSoftApMode())
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
                 .thenReturn(Pair.create(WifiNative.SETUP_FAILURE_WIFICOND, null));
 
         SoftApModeConfiguration config = new SoftApModeConfiguration(
@@ -2452,7 +2510,7 @@
         mWsm.setHostApRunning(config, true);
         mLooper.dispatchAll();
 
-        verify(mWifiNative).setupForSoftApMode();
+        verify(mWifiNative).setupForSoftApMode(WIFI_IFACE_NAME);
         verify(mWifiMetrics).incrementNumWifiOnFailureDueToWificond();
     }
 
@@ -2461,7 +2519,7 @@
      */
     @Test
     public void testSetupForClientModeHalFailureIncrementsMetrics() throws Exception {
-        when(mWifiNative.setupForClientMode())
+        when(mWifiNative.setupForClientMode(WIFI_IFACE_NAME))
                 .thenReturn(Pair.create(WifiNative.SETUP_FAILURE_HAL, null));
 
         mWsm.setSupplicantRunning(true);
@@ -2470,7 +2528,7 @@
         mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
         mLooper.dispatchAll();
 
-        verify(mWifiNative).setupForClientMode();
+        verify(mWifiNative).setupForClientMode(WIFI_IFACE_NAME);
         verify(mWifiMetrics).incrementNumWifiOnFailureDueToHal();
     }
 
@@ -2479,7 +2537,7 @@
      */
     @Test
     public void testSetupForClientModeWificondFailureIncrementsMetrics() throws Exception {
-        when(mWifiNative.setupForClientMode())
+        when(mWifiNative.setupForClientMode(WIFI_IFACE_NAME))
                 .thenReturn(Pair.create(WifiNative.SETUP_FAILURE_WIFICOND, null));
 
         mWsm.setSupplicantRunning(true);
@@ -2488,7 +2546,7 @@
         mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
         mLooper.dispatchAll();
 
-        verify(mWifiNative).setupForClientMode();
+        verify(mWifiNative).setupForClientMode(WIFI_IFACE_NAME);
         verify(mWifiMetrics).incrementNumWifiOnFailureDueToWificond();
     }
 
@@ -2773,4 +2831,15 @@
         currentConfig.networkId = lastSelectedNetworkId - 1;
         assertFalse(mWsm.shouldEvaluateWhetherToSendExplicitlySelected(currentConfig));
     }
+
+    /**
+     * Verify that WSM dump includes WakeupController.
+     */
+    @Test
+    public void testDumpShouldDumpWakeupController() {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        PrintWriter writer = new PrintWriter(stream);
+        mWsm.dump(null, writer, null);
+        verify(mWakeupController).dump(null, writer, null);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
index 84de9d2..812ef03 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
@@ -15,6 +15,30 @@
  */
 
 package com.android.server.wifi;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.anyShort;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.hardware.wifi.V1_0.IWifiApIface;
 import android.hardware.wifi.V1_0.IWifiChip;
@@ -25,8 +49,10 @@
 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.RttBw;
 import android.hardware.wifi.V1_0.RttCapabilities;
 import android.hardware.wifi.V1_0.RttConfig;
+import android.hardware.wifi.V1_0.RttPreamble;
 import android.hardware.wifi.V1_0.StaApfPacketFilterCapabilities;
 import android.hardware.wifi.V1_0.StaBackgroundScanCapabilities;
 import android.hardware.wifi.V1_0.StaBackgroundScanParameters;
@@ -51,11 +77,11 @@
 import android.net.apf.ApfCapabilities;
 import android.net.wifi.RttManager;
 import android.net.wifi.ScanResult;
-import android.net.wifi.WifiLinkLayerStats;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.WifiWakeReasonAndCounts;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
@@ -64,9 +90,6 @@
 import com.android.server.connectivity.KeepalivePacketData;
 import com.android.server.wifi.util.NativeUtil;
 
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.*;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -87,6 +110,7 @@
  */
 public class WifiVendorHalTest {
 
+    private static final String TEST_IFACE_NAME = "wlan0";
     WifiVendorHal mWifiVendorHal;
     private WifiStatus mWifiStatusSuccess;
     private WifiStatus mWifiStatusFailure;
@@ -171,10 +195,10 @@
                 .thenReturn(mIWifiStaIface);
         when(mHalDeviceManager.createApIface(eq(null), eq(null)))
                 .thenReturn(mIWifiApIface);
+        when(mHalDeviceManager.removeIface(any())).thenReturn(true);
         when(mHalDeviceManager.getChip(any(IWifiIface.class)))
                 .thenReturn(mIWifiChip);
-        when(mHalDeviceManager.createRttController(any(IWifiIface.class)))
-                .thenReturn(mIWifiRttController);
+        when(mHalDeviceManager.createRttController()).thenReturn(mIWifiRttController);
         when(mIWifiChip.registerEventCallback(any(IWifiChipEventCallback.class)))
                 .thenReturn(mWifiStatusSuccess);
         mIWifiStaIfaceEventCallback = null;
@@ -195,6 +219,19 @@
         when(mIWifiRttController.registerEventCallback(any(IWifiRttControllerEventCallback.class)))
                 .thenReturn(mWifiStatusSuccess);
 
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiIface.getNameCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, TEST_IFACE_NAME);
+            }
+        }).when(mIWifiStaIface).getName(any(IWifiIface.getNameCallback.class));
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiIface.getNameCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusSuccess, TEST_IFACE_NAME);
+            }
+        }).when(mIWifiApIface).getName(any(IWifiIface.getNameCallback.class));
+
         // Create the vendor HAL object under test.
         mWifiVendorHal = new WifiVendorHal(mHalDeviceManager, mLooper.getLooper());
 
@@ -202,24 +239,25 @@
         mWifiVendorHal.initialize(mVendorHalDeathHandler);
         ArgumentCaptor<WifiVendorHal.HalDeviceManagerStatusListener> hdmCallbackCaptor =
                 ArgumentCaptor.forClass(WifiVendorHal.HalDeviceManagerStatusListener.class);
-        verify(mHalDeviceManager).registerStatusListener(hdmCallbackCaptor.capture(), any());
+        verify(mHalDeviceManager).registerStatusListener(hdmCallbackCaptor.capture(),
+                any(Handler.class));
         mHalDeviceManagerStatusCallbacks = hdmCallbackCaptor.getValue();
 
     }
 
     /**
      * Tests the successful starting of HAL in STA mode using
-     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     * {@link WifiVendorHal#startVendorHalSta()}.
      */
     @Test
     public void testStartHalSuccessInStaMode() throws  Exception {
-        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(mWifiVendorHal.startVendorHalSta());
         assertTrue(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
         verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
         verify(mHalDeviceManager).getChip(eq(mIWifiStaIface));
-        verify(mHalDeviceManager).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager).createRttController();
         verify(mHalDeviceManager).isReady();
         verify(mHalDeviceManager).isStarted();
         verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
@@ -230,11 +268,11 @@
 
     /**
      * Tests the successful starting of HAL in AP mode using
-     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     * {@link WifiVendorHal#startVendorHalAp()}.
      */
     @Test
     public void testStartHalSuccessInApMode() throws Exception {
-        assertTrue(mWifiVendorHal.startVendorHal(false));
+        assertTrue(mWifiVendorHal.startVendorHalAp());
         assertTrue(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
@@ -244,12 +282,12 @@
         verify(mHalDeviceManager).isStarted();
 
         verify(mHalDeviceManager, never()).createStaIface(eq(null), eq(null));
-        verify(mHalDeviceManager, never()).createRttController(any(IWifiIface.class));
+        verify(mHalDeviceManager, never()).createRttController();
     }
 
     /**
      * Tests the failure to start HAL in STA mode using
-     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     * {@link WifiVendorHal#startVendorHalSta()}.
      */
     @Test
     public void testStartHalFailureInStaMode() throws Exception {
@@ -260,7 +298,7 @@
                 return false;
             }
         }).when(mHalDeviceManager).start();
-        assertFalse(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.startVendorHalSta());
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
@@ -268,19 +306,19 @@
         verify(mHalDeviceManager, never()).createStaIface(eq(null), eq(null));
         verify(mHalDeviceManager, never()).createApIface(eq(null), eq(null));
         verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
-        verify(mHalDeviceManager, never()).createRttController(any(IWifiIface.class));
+        verify(mHalDeviceManager, never()).createRttController();
         verify(mIWifiStaIface, never())
                 .registerEventCallback(any(IWifiStaIfaceEventCallback.class));
     }
 
     /**
      * Tests the failure to start HAL in STA mode using
-     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     * {@link WifiVendorHal#startVendorHalSta()}.
      */
     @Test
     public void testStartHalFailureInIfaceCreationInStaMode() throws Exception {
         when(mHalDeviceManager.createStaIface(eq(null), eq(null))).thenReturn(null);
-        assertFalse(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.startVendorHalSta());
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
@@ -289,24 +327,24 @@
 
         verify(mHalDeviceManager, never()).createApIface(eq(null), eq(null));
         verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
-        verify(mHalDeviceManager, never()).createRttController(any(IWifiIface.class));
+        verify(mHalDeviceManager, never()).createRttController();
         verify(mIWifiStaIface, never())
                 .registerEventCallback(any(IWifiStaIfaceEventCallback.class));
     }
 
     /**
      * Tests the failure to start HAL in STA mode using
-     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     * {@link WifiVendorHal#startVendorHalSta()}.
      */
     @Test
     public void testStartHalFailureInRttControllerCreationInStaMode() throws Exception {
-        when(mHalDeviceManager.createRttController(any(IWifiIface.class))).thenReturn(null);
-        assertFalse(mWifiVendorHal.startVendorHal(true));
+        when(mHalDeviceManager.createRttController()).thenReturn(null);
+        assertFalse(mWifiVendorHal.startVendorHalSta());
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
         verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
-        verify(mHalDeviceManager).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager).createRttController();
         verify(mHalDeviceManager).stop();
         verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
 
@@ -316,17 +354,17 @@
 
     /**
      * Tests the failure to start HAL in STA mode using
-     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     * {@link WifiVendorHal#startVendorHalSta()}.
      */
     @Test
     public void testStartHalFailureInChipGetInStaMode() throws Exception {
         when(mHalDeviceManager.getChip(any(IWifiIface.class))).thenReturn(null);
-        assertFalse(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.startVendorHalSta());
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
         verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
-        verify(mHalDeviceManager).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager).createRttController();
         verify(mHalDeviceManager).getChip(any(IWifiIface.class));
         verify(mHalDeviceManager).stop();
         verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
@@ -336,13 +374,13 @@
 
     /**
      * Tests the failure to start HAL in STA mode using
-     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     * {@link WifiVendorHal#startVendorHalSta()}.
      */
     @Test
     public void testStartHalFailureInStaIfaceCallbackRegistration() throws Exception {
         when(mIWifiStaIface.registerEventCallback(any(IWifiStaIfaceEventCallback.class)))
                 .thenReturn(mWifiStatusFailure);
-        assertFalse(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.startVendorHalSta());
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
@@ -350,25 +388,25 @@
         verify(mHalDeviceManager).stop();
         verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
 
-        verify(mHalDeviceManager, never()).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager, never()).createRttController();
         verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
         verify(mHalDeviceManager, never()).createApIface(eq(null), eq(null));
     }
 
     /**
      * Tests the failure to start HAL in STA mode using
-     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     * {@link WifiVendorHal#startVendorHalSta()}.
      */
     @Test
     public void testStartHalFailureInChipCallbackRegistration() throws Exception {
         when(mIWifiChip.registerEventCallback(any(IWifiChipEventCallback.class)))
                 .thenReturn(mWifiStatusFailure);
-        assertFalse(mWifiVendorHal.startVendorHal(true));
+        assertFalse(mWifiVendorHal.startVendorHalSta());
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
         verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
-        verify(mHalDeviceManager).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager).createRttController();
         verify(mHalDeviceManager).getChip(any(IWifiIface.class));
         verify(mHalDeviceManager).stop();
         verify(mIWifiStaIface).registerEventCallback(any(IWifiStaIfaceEventCallback.class));
@@ -378,13 +416,13 @@
     }
 
     /**
-     * Tests the failure to start HAL in STA mode using
-     * {@link WifiVendorHal#startVendorHal(boolean)}.
+     * Tests the failure to start HAL in AP mode using
+     * {@link WifiVendorHal#startVendorHalAp()}.
      */
     @Test
     public void testStartHalFailureInApMode() throws Exception {
         when(mHalDeviceManager.createApIface(eq(null), eq(null))).thenReturn(null);
-        assertFalse(mWifiVendorHal.startVendorHal(false));
+        assertFalse(mWifiVendorHal.startVendorHalAp());
         assertFalse(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
@@ -393,7 +431,7 @@
 
         verify(mHalDeviceManager, never()).createStaIface(eq(null), eq(null));
         verify(mHalDeviceManager, never()).getChip(any(IWifiIface.class));
-        verify(mHalDeviceManager, never()).createRttController(any(IWifiIface.class));
+        verify(mHalDeviceManager, never()).createRttController();
     }
 
     /**
@@ -402,7 +440,7 @@
      */
     @Test
     public void testStopHalInStaMode() {
-        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(mWifiVendorHal.startVendorHalSta());
         assertTrue(mWifiVendorHal.isHalStarted());
 
         mWifiVendorHal.stopVendorHal();
@@ -412,7 +450,7 @@
         verify(mHalDeviceManager).stop();
         verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
         verify(mHalDeviceManager).getChip(eq(mIWifiStaIface));
-        verify(mHalDeviceManager).createRttController(eq(mIWifiStaIface));
+        verify(mHalDeviceManager).createRttController();
         verify(mHalDeviceManager, times(2)).isReady();
         verify(mHalDeviceManager, times(2)).isStarted();
 
@@ -425,7 +463,7 @@
      */
     @Test
     public void testStopHalInApMode() {
-        assertTrue(mWifiVendorHal.startVendorHal(false));
+        assertTrue(mWifiVendorHal.startVendorHalAp());
         assertTrue(mWifiVendorHal.isHalStarted());
 
         mWifiVendorHal.stopVendorHal();
@@ -439,7 +477,7 @@
         verify(mHalDeviceManager, times(2)).isStarted();
 
         verify(mHalDeviceManager, never()).createStaIface(eq(null), eq(null));
-        verify(mHalDeviceManager, never()).createRttController(any(IWifiIface.class));
+        verify(mHalDeviceManager, never()).createRttController();
     }
 
     /**
@@ -449,7 +487,7 @@
     public void testEnterLogging() {
         mWifiVendorHal.mLog = spy(mWifiLog);
         mWifiVendorHal.enableVerboseLogging(true);
-        mWifiVendorHal.installPacketFilter(new byte[0]);
+        mWifiVendorHal.installPacketFilter(TEST_IFACE_NAME, new byte[0]);
         verify(mWifiVendorHal.mLog).trace(eq("% filter length %"));
     }
 
@@ -459,10 +497,10 @@
     @Test
     public void testEnterSilenceWhenNotEnabled() {
         mWifiVendorHal.mLog = spy(mWifiLog);
-        mWifiVendorHal.installPacketFilter(new byte[0]);
+        mWifiVendorHal.installPacketFilter(TEST_IFACE_NAME, new byte[0]);
         mWifiVendorHal.enableVerboseLogging(true);
         mWifiVendorHal.enableVerboseLogging(false);
-        mWifiVendorHal.installPacketFilter(new byte[0]);
+        mWifiVendorHal.installPacketFilter(TEST_IFACE_NAME, new byte[0]);
         verify(mWifiVendorHal.mLog, never()).trace(eq("% filter length %"));
     }
 
@@ -474,7 +512,8 @@
         mWifiLog = spy(mWifiLog);
         mWifiVendorHal.mLog = mWifiLog;
         mWifiVendorHal.mVerboseLog = mWifiLog;
-        assertFalse(mWifiVendorHal.getBgScanCapabilities(new WifiNative.ScanCapabilities()));
+        assertFalse(mWifiVendorHal.getBgScanCapabilities(
+                TEST_IFACE_NAME, new WifiNative.ScanCapabilities()));
         verify(mWifiLog).err("% returns %");
     }
 
@@ -503,9 +542,12 @@
 
         WifiNative.ScanCapabilities result = new WifiNative.ScanCapabilities();
 
-        assertFalse(mWifiVendorHal.getBgScanCapabilities(result));  // should fail - not started
-        assertTrue(mWifiVendorHal.startVendorHalSta());           // Start the vendor hal
-        assertTrue(mWifiVendorHal.getBgScanCapabilities(result));   // should succeed
+        // should fail - not started
+        assertFalse(mWifiVendorHal.getBgScanCapabilities(TEST_IFACE_NAME, result));
+        // Start the vendor hal
+        assertTrue(mWifiVendorHal.startVendorHalSta());
+        // should succeed
+        assertTrue(mWifiVendorHal.getBgScanCapabilities(TEST_IFACE_NAME, result));
 
         assertEquals(12, result.max_scan_cache_size);
         assertEquals(34, result.max_scan_buckets);
@@ -513,67 +555,6 @@
         assertEquals(78, result.max_scan_reporting_threshold);
     }
 
-    private void setupValidFrequenciesForBand(ArrayList<Integer> frequencies) throws Exception {
-
-        doAnswer(new AnswerWithArguments() {
-            public void answer(int band, IWifiStaIface.getValidFrequenciesForBandCallback cb)
-                    throws RemoteException {
-                cb.onValues(mWifiStatusSuccess, frequencies);
-            }
-        }).when(mIWifiStaIface).getValidFrequenciesForBand(anyInt(), any(
-                IWifiStaIface.getValidFrequenciesForBandCallback.class));
-
-        doAnswer(new AnswerWithArguments() {
-            public void answer(int band, IWifiApIface.getValidFrequenciesForBandCallback cb)
-                    throws RemoteException {
-                cb.onValues(mWifiStatusSuccess, frequencies);
-            }
-        }).when(mIWifiApIface).getValidFrequenciesForBand(anyInt(), any(
-                IWifiApIface.getValidFrequenciesForBandCallback.class));
-
-    }
-
-    private int[] intArrayFromArrayList(ArrayList<Integer> in) {
-        int[] ans = new int[in.size()];
-        int i = 0;
-        for (Integer e : in) ans[i++] = e;
-        return ans;
-    }
-
-    /**
-     * Test that isGetChannelsForBandSupported works in STA mode
-     */
-    @Test
-    public void testGetChannelsForBandSupportedSta() throws Exception {
-        ArrayList<Integer> freq = new ArrayList<>();
-        freq.add(2405);
-
-        setupValidFrequenciesForBand(freq);
-
-        assertFalse(mWifiVendorHal.isGetChannelsForBandSupported());
-
-        assertTrue(mWifiVendorHal.startVendorHalSta());
-
-        assertTrue(mWifiVendorHal.isGetChannelsForBandSupported());
-    }
-
-    /**
-     * Test that isGetChannelsForBandSupported works in AP mode
-     */
-    @Test
-    public void testGetChannelsForBandSupportedAp() throws Exception {
-        ArrayList<Integer> freq = new ArrayList<>();
-        freq.add(2405);
-
-        setupValidFrequenciesForBand(freq);
-
-        assertFalse(mWifiVendorHal.isGetChannelsForBandSupported());
-
-        assertTrue(mWifiVendorHal.startVendorHalAp());
-
-        assertTrue(mWifiVendorHal.isGetChannelsForBandSupported());
-    }
-
     /**
      * Test translation to WifiManager.WIFI_FEATURE_*
      *
@@ -620,7 +601,7 @@
      */
     @Test
     public void testGetSupportedFeatures() throws Exception {
-        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(mWifiVendorHal.startVendorHalSta());
 
         int staIfaceHidlCaps = (
                 IWifiStaIface.StaIfaceCapabilityMask.BACKGROUND_SCAN
@@ -653,7 +634,7 @@
         when(mHalDeviceManager.getSupportedIfaceTypes())
                 .thenReturn(halDeviceManagerSupportedIfaces);
 
-        assertEquals(expectedFeatureSet, mWifiVendorHal.getSupportedFeatureSet());
+        assertEquals(expectedFeatureSet, mWifiVendorHal.getSupportedFeatureSet(TEST_IFACE_NAME));
     }
 
     /**
@@ -670,13 +651,13 @@
     public void testLinkLayerStatsEnableAfterStartup() throws Exception {
         doNothing().when(mIWifiStaIface).getLinkLayerStats(any());
 
-        assertNull(mWifiVendorHal.getWifiLinkLayerStats());
+        assertNull(mWifiVendorHal.getWifiLinkLayerStats(TEST_IFACE_NAME));
         assertTrue(mWifiVendorHal.startVendorHalSta());
         assertTrue(mWifiVendorHal.isHalStarted());
 
         verify(mHalDeviceManager).start();
-        mWifiVendorHal.getWifiLinkLayerStats();
-        mWifiVendorHal.getWifiLinkLayerStats();
+        mWifiVendorHal.getWifiLinkLayerStats(TEST_IFACE_NAME);
+        mWifiVendorHal.getWifiLinkLayerStats(TEST_IFACE_NAME);
         verify(mIWifiStaIface).enableLinkLayerStatsCollection(false); // mLinkLayerStatsDebug
         verify(mIWifiStaIface, times(2)).getLinkLayerStats(any());
     }
@@ -695,7 +676,7 @@
 
         assertTrue(mWifiVendorHal.startVendorHalAp());
         assertTrue(mWifiVendorHal.isHalStarted());
-        assertNull(mWifiVendorHal.getWifiLinkLayerStats());
+        assertNull(mWifiVendorHal.getWifiLinkLayerStats(TEST_IFACE_NAME));
 
         verify(mHalDeviceManager).start();
 
@@ -722,13 +703,13 @@
         randomizePacketStats(r, stats.iface.wmeVoPktStats);
         randomizeRadioStats(r, stats.radios);
 
-        stats.timeStampInMs = 42; // currently dropped in conversion
+        stats.timeStampInMs = r.nextLong() & 0xFFFFFFFFFFL;
 
-        String expected = numbersOnly(stats.toString());
+        String expected = numbersOnly(stats.toString() + " ");
 
         WifiLinkLayerStats converted = WifiVendorHal.frameworkFromHalLinkLayerStats(stats);
 
-        String actual = numbersOnly(converted.toString());
+        String actual = numbersOnly(converted.toString() + " ");
 
         // Do the required fixups to the both expected and actual
         expected = rmValue(expected, stats.radios.get(0).rxTimeInMs);
@@ -736,7 +717,6 @@
 
         actual = rmValue(actual, stats.radios.get(0).rxTimeInMs);
         actual = rmValue(actual, stats.radios.get(0).onTimeInMsForScan);
-        actual = actual + "42 ";
 
         // The remaining fields should agree
         assertEquals(expected, actual);
@@ -899,19 +879,52 @@
                 0, 64);
     }
 
+    /**
+     * Test translation of framwork RttParams to hal RttConfig
+     */
     @Test
     public void testGetRttStuff() throws Exception {
         RttManager.RttParams params = new RttManager.RttParams();
-        //TODO(b/34901744) populate
+        params.bssid = "03:01:04:01:05:09";
+        params.frequency = 2420;
+        params.channelWidth = ScanResult.CHANNEL_WIDTH_40MHZ;
+        params.centerFreq0 = 2440;
+        params.centerFreq1 = 1;
+        params.num_samples = 2;
+        params.num_retries = 3;
+        params.numberBurst = 4;
+        params.interval = 5;
+        params.numSamplesPerBurst = 8;
+        params.numRetriesPerMeasurementFrame = 6;
+        params.numRetriesPerFTMR = 7;
+        params.LCIRequest = false;
+        params.LCRRequest = false;
+        params.burstTimeout = 15;
+        String frameish = params.toString();
+        assertFalse(frameish.contains("=0,")); // make sure all fields are initialized
         RttConfig config = WifiVendorHal.halRttConfigFromFrameworkRttParams(params);
-        //TODO(b/34901744) check
+        String halish = config.toString();
+        StringBuffer expect = new StringBuffer(200);
+        expect.append("{.addr = [3, 1, 4, 1, 5, 9], .type = ONE_SIDED, .peer = AP, ");
+        expect.append(".channel = {.width = WIDTH_40, .centerFreq = 2420, ");
+        expect.append(".centerFreq0 = 2440, .centerFreq1 = 1}, ");
+        expect.append(".burstPeriod = 5, .numBurst = 4, .numFramesPerBurst = 8, ");
+        expect.append(".numRetriesPerRttFrame = 6, .numRetriesPerFtmr = 7, ");
+        expect.append(".mustRequestLci = false, .mustRequestLcr = false, .burstDuration = 15, ");
+        expect.append(".preamble = HT, .bw = BW_20MHZ}");
+        assertEquals(expect.toString(), halish);
     }
 
+    /**
+     * Test that RTT capabilities are plumbed through
+     */
     @Test
     public void testGetRttCapabilities() throws Exception {
         RttCapabilities capabilities = new RttCapabilities();
-        //TODO(b/34901744) populate
-
+        capabilities.lcrSupported = true;
+        capabilities.preambleSupport = RttPreamble.LEGACY | RttPreamble.VHT;
+        capabilities.bwSupport = RttBw.BW_5MHZ | RttBw.BW_20MHZ;
+        capabilities.mcVersion = 43;
         doAnswer(new AnswerWithArguments() {
             public void answer(IWifiRttController.getCapabilitiesCallback cb)
                     throws RemoteException {
@@ -925,12 +938,21 @@
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
         RttManager.RttCapabilities actual = mWifiVendorHal.getRttCapabilities();
-        //TODO(b/34901744) check
-
+        assertTrue(actual.lcrSupported);
+        assertEquals(RttManager.PREAMBLE_LEGACY | RttManager.PREAMBLE_VHT,
+                actual.preambleSupported);
+        assertEquals(RttManager.RTT_BW_5_SUPPORT | RttManager.RTT_BW_20_SUPPORT,
+                actual.bwSupported);
+        assertEquals(43, (int) capabilities.mcVersion);
     }
 
-    //TODO(b/34901744) negative RTT test cases as well.
-    // e.g. invoke RTT without putting the HAL in the correct mode.
+    /**
+     * Negative test of disableRttResponder
+     */
+    @Test
+    public void testDisableOfUnstartedRtt() throws Exception {
+        assertFalse(mWifiVendorHal.disableRttResponder());
+    }
 
     /**
      * Test that setScanningMacOui is hooked up to the HAL correctly
@@ -942,12 +964,15 @@
 
         when(mIWifiStaIface.setScanningMacOui(any())).thenReturn(mWifiStatusSuccess);
 
-        assertFalse(mWifiVendorHal.setScanningMacOui(oui)); // expect fail - STA not started
+        // expect fail - STA not started
+        assertFalse(mWifiVendorHal.setScanningMacOui(TEST_IFACE_NAME, oui));
         assertTrue(mWifiVendorHal.startVendorHalSta());
-        assertFalse(mWifiVendorHal.setScanningMacOui(null));  // expect fail - null
-        assertFalse(mWifiVendorHal.setScanningMacOui(new byte[]{(byte) 1})); // expect fail - len
-        assertTrue(mWifiVendorHal.setScanningMacOui(oui));
-        assertTrue(mWifiVendorHal.setScanningMacOui(zzz));
+        // expect fail - null
+        assertFalse(mWifiVendorHal.setScanningMacOui(TEST_IFACE_NAME, null));
+        // expect fail - len
+        assertFalse(mWifiVendorHal.setScanningMacOui(TEST_IFACE_NAME, new byte[]{(byte) 1}));
+        assertTrue(mWifiVendorHal.setScanningMacOui(TEST_IFACE_NAME, oui));
+        assertTrue(mWifiVendorHal.setScanningMacOui(TEST_IFACE_NAME, zzz));
 
         verify(mIWifiStaIface).setScanningMacOui(eq(oui));
         verify(mIWifiStaIface).setScanningMacOui(eq(zzz));
@@ -968,7 +993,8 @@
         )).thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
-        assertTrue(0 == mWifiVendorHal.startSendingOffloadedPacket(slot, srcMac, kap, millis));
+        assertTrue(0 == mWifiVendorHal.startSendingOffloadedPacket(
+                TEST_IFACE_NAME, slot, srcMac, kap, millis));
 
         verify(mIWifiStaIface).startSendingKeepAlivePackets(
                 eq(slot), any(), anyShort(), any(), any(), eq(millis));
@@ -981,7 +1007,8 @@
         when(mIWifiStaIface.stopSendingKeepAlivePackets(anyInt())).thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
-        assertTrue(0 == mWifiVendorHal.stopSendingOffloadedPacket(slot));
+        assertTrue(0 == mWifiVendorHal.stopSendingOffloadedPacket(
+                TEST_IFACE_NAME, slot));
 
         verify(mIWifiStaIface).stopSendingKeepAlivePackets(eq(slot));
     }
@@ -1006,20 +1033,26 @@
         handler = ((cur) -> {
             breach.add(cur);
         });
-        assertEquals(-1, mWifiVendorHal.startRssiMonitoring(hi, lo, handler)); // not started
-        assertEquals(-1, mWifiVendorHal.stopRssiMonitoring()); // not started
+        // not started
+        assertEquals(-1, mWifiVendorHal.startRssiMonitoring(TEST_IFACE_NAME, hi, lo, handler));
+        // not started
+        assertEquals(-1, mWifiVendorHal.stopRssiMonitoring(TEST_IFACE_NAME));
         assertTrue(mWifiVendorHal.startVendorHalSta());
-        assertEquals(0, mWifiVendorHal.startRssiMonitoring(hi, lo, handler));
+        assertEquals(0, mWifiVendorHal.startRssiMonitoring(TEST_IFACE_NAME, hi, lo, handler));
         int theCmdId = mWifiVendorHal.sRssiMonCmdId;
         breach.clear();
         mIWifiStaIfaceEventCallback.onRssiThresholdBreached(theCmdId, new byte[6], lower);
         assertEquals(breach.get(0), lower);
-        assertEquals(0, mWifiVendorHal.stopRssiMonitoring());
-        assertEquals(0, mWifiVendorHal.startRssiMonitoring(hi, lo, handler));
-        assertEquals(0, mWifiVendorHal.startRssiMonitoring(med, lo, handler)); // replacing works
-        assertEquals(-1, mWifiVendorHal.startRssiMonitoring(hi, lo, null)); // null handler fails
-        assertEquals(0, mWifiVendorHal.startRssiMonitoring(hi, lo, handler));
-        assertEquals(-1, mWifiVendorHal.startRssiMonitoring(lo, hi, handler)); // empty range
+        assertEquals(0, mWifiVendorHal.stopRssiMonitoring(TEST_IFACE_NAME));
+        assertEquals(0, mWifiVendorHal.startRssiMonitoring(TEST_IFACE_NAME, hi, lo, handler));
+        // replacing works
+        assertEquals(0, mWifiVendorHal.startRssiMonitoring(TEST_IFACE_NAME, med, lo, handler));
+        // null handler fails
+        assertEquals(-1, mWifiVendorHal.startRssiMonitoring(
+                TEST_IFACE_NAME, hi, lo, null));
+        assertEquals(0, mWifiVendorHal.startRssiMonitoring(TEST_IFACE_NAME, hi, lo, handler));
+        // empty range
+        assertEquals(-1, mWifiVendorHal.startRssiMonitoring(TEST_IFACE_NAME, lo, hi, handler));
     }
 
     /**
@@ -1047,11 +1080,12 @@
                 IWifiStaIface.getApfPacketFilterCapabilitiesCallback.class));
 
 
-        assertEquals(0, mWifiVendorHal.getApfCapabilities().apfVersionSupported);
+        assertEquals(0, mWifiVendorHal.getApfCapabilities(TEST_IFACE_NAME)
+                .apfVersionSupported);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
-        ApfCapabilities actual = mWifiVendorHal.getApfCapabilities();
+        ApfCapabilities actual = mWifiVendorHal.getApfCapabilities(TEST_IFACE_NAME);
 
         assertEquals(myVersion, actual.apfVersionSupported);
         assertEquals(myMaxSize, actual.maximumApfProgramSize);
@@ -1073,7 +1107,7 @@
                 .thenReturn(mWifiStatusSuccess);
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
-        assertTrue(mWifiVendorHal.installPacketFilter(filter));
+        assertTrue(mWifiVendorHal.installPacketFilter(TEST_IFACE_NAME, filter));
 
         verify(mIWifiStaIface).installApfPacketFilter(eq(0), eq(expected));
     }
@@ -1090,11 +1124,12 @@
 
         assertTrue(mWifiVendorHal.startVendorHalAp());
 
-        assertFalse(mWifiVendorHal.setCountryCodeHal(null));
-        assertFalse(mWifiVendorHal.setCountryCodeHal(""));
-        assertFalse(mWifiVendorHal.setCountryCodeHal("A"));
-        assertTrue(mWifiVendorHal.setCountryCodeHal("CA")); // Only one expected to succeed
-        assertFalse(mWifiVendorHal.setCountryCodeHal("ZZZ"));
+        assertFalse(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, null));
+        assertFalse(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, ""));
+        assertFalse(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, "A"));
+        // Only one expected to succeed
+        assertTrue(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, "CA"));
+        assertFalse(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, "ZZZ"));
 
         verify(mIWifiApIface).setCountryCode(eq(expected));
     }
@@ -1109,7 +1144,7 @@
         when(mIWifiApIface.setCountryCode(any()))
                 .thenThrow(new RemoteException("oops"));
         assertTrue(mWifiVendorHal.startVendorHalAp());
-        assertFalse(mWifiVendorHal.setCountryCodeHal("CA"));
+        assertFalse(mWifiVendorHal.setCountryCodeHal(TEST_IFACE_NAME, "CA"));
         assertFalse(mWifiVendorHal.isHalStarted());
         verify(mWifiLog).err(any());
     }
@@ -1226,11 +1261,11 @@
     public void testStartPktFateMonitoring() throws Exception {
         when(mIWifiStaIface.startDebugPacketFateMonitoring()).thenReturn(mWifiStatusSuccess);
 
-        assertFalse(mWifiVendorHal.startPktFateMonitoring());
+        assertFalse(mWifiVendorHal.startPktFateMonitoring(TEST_IFACE_NAME));
         verify(mIWifiStaIface, never()).startDebugPacketFateMonitoring();
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
-        assertTrue(mWifiVendorHal.startPktFateMonitoring());
+        assertTrue(mWifiVendorHal.startPktFateMonitoring(TEST_IFACE_NAME));
         verify(mIWifiStaIface).startDebugPacketFateMonitoring();
     }
 
@@ -1259,13 +1294,13 @@
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
 
         WifiNative.TxFateReport[] retrievedFates = new WifiNative.TxFateReport[1];
-        assertFalse(mWifiVendorHal.getTxPktFates(retrievedFates));
+        assertFalse(mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME, retrievedFates));
         verify(mIWifiStaIface, never())
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
-        assertTrue(mWifiVendorHal.getTxPktFates(retrievedFates));
+        assertTrue(mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME, retrievedFates));
         verify(mIWifiStaIface)
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
         assertEquals(WifiLoggerHal.TX_PKT_FATE_DRV_QUEUED, retrievedFates[0].mFate);
@@ -1302,13 +1337,13 @@
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
 
         WifiNative.TxFateReport[] retrievedFates = new WifiNative.TxFateReport[1];
-        assertFalse(mWifiVendorHal.getTxPktFates(retrievedFates));
+        assertFalse(mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME, retrievedFates));
         verify(mIWifiStaIface, never())
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
-        assertTrue(mWifiVendorHal.getTxPktFates(retrievedFates));
+        assertTrue(mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME, retrievedFates));
         verify(mIWifiStaIface)
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
         assertEquals(WifiLoggerHal.TX_PKT_FATE_FW_DROP_OTHER, retrievedFates[0].mFate);
@@ -1343,13 +1378,13 @@
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
 
         WifiNative.RxFateReport[] retrievedFates = new WifiNative.RxFateReport[1];
-        assertFalse(mWifiVendorHal.getRxPktFates(retrievedFates));
+        assertFalse(mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME, retrievedFates));
         verify(mIWifiStaIface, never())
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
-        assertTrue(mWifiVendorHal.getRxPktFates(retrievedFates));
+        assertTrue(mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME, retrievedFates));
         verify(mIWifiStaIface)
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
         assertEquals(WifiLoggerHal.RX_PKT_FATE_SUCCESS, retrievedFates[0].mFate);
@@ -1386,13 +1421,13 @@
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
 
         WifiNative.RxFateReport[] retrievedFates = new WifiNative.RxFateReport[1];
-        assertFalse(mWifiVendorHal.getRxPktFates(retrievedFates));
+        assertFalse(mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME, retrievedFates));
         verify(mIWifiStaIface, never())
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
-        assertTrue(mWifiVendorHal.getRxPktFates(retrievedFates));
+        assertTrue(mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME, retrievedFates));
         verify(mIWifiStaIface)
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
         assertEquals(WifiLoggerHal.RX_PKT_FATE_FW_DROP_FILTER, retrievedFates[0].mFate);
@@ -1408,7 +1443,7 @@
     @Test
     public void testGetTxPktFatesEmptyInputArray() throws Exception {
         assertTrue(mWifiVendorHal.startVendorHalSta());
-        assertFalse(mWifiVendorHal.getTxPktFates(new WifiNative.TxFateReport[0]));
+        assertFalse(mWifiVendorHal.getTxPktFates(TEST_IFACE_NAME, new WifiNative.TxFateReport[0]));
         verify(mIWifiStaIface, never())
                 .getDebugTxPacketFates(any(IWifiStaIface.getDebugTxPacketFatesCallback.class));
     }
@@ -1419,7 +1454,7 @@
     @Test
     public void testGetRxPktFatesEmptyInputArray() throws Exception {
         assertTrue(mWifiVendorHal.startVendorHalSta());
-        assertFalse(mWifiVendorHal.getRxPktFates(new WifiNative.RxFateReport[0]));
+        assertFalse(mWifiVendorHal.getRxPktFates(TEST_IFACE_NAME, new WifiNative.RxFateReport[0]));
         verify(mIWifiStaIface, never())
                 .getDebugRxPacketFates(any(IWifiStaIface.getDebugRxPacketFatesCallback.class));
     }
@@ -1431,14 +1466,14 @@
     public void testEnableDisableNdOffload() throws Exception {
         when(mIWifiStaIface.enableNdOffload(anyBoolean())).thenReturn(mWifiStatusSuccess);
 
-        assertFalse(mWifiVendorHal.configureNeighborDiscoveryOffload(true));
+        assertFalse(mWifiVendorHal.configureNeighborDiscoveryOffload(TEST_IFACE_NAME, true));
         verify(mIWifiStaIface, never()).enableNdOffload(anyBoolean());
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
-        assertTrue(mWifiVendorHal.configureNeighborDiscoveryOffload(true));
+        assertTrue(mWifiVendorHal.configureNeighborDiscoveryOffload(TEST_IFACE_NAME, true));
         verify(mIWifiStaIface).enableNdOffload(eq(true));
-        assertTrue(mWifiVendorHal.configureNeighborDiscoveryOffload(false));
+        assertTrue(mWifiVendorHal.configureNeighborDiscoveryOffload(TEST_IFACE_NAME, false));
         verify(mIWifiStaIface).enableNdOffload(eq(false));
     }
 
@@ -1451,7 +1486,7 @@
 
         assertTrue(mWifiVendorHal.startVendorHalSta());
 
-        assertFalse(mWifiVendorHal.configureNeighborDiscoveryOffload(true));
+        assertFalse(mWifiVendorHal.configureNeighborDiscoveryOffload(TEST_IFACE_NAME, true));
         verify(mIWifiStaIface).enableNdOffload(eq(true));
     }
 
@@ -1667,8 +1702,8 @@
 
         int cmdId = mWifiVendorHal.mScan.cmdId;
 
-        mWifiVendorHal.stopBgScan();
-        mWifiVendorHal.stopBgScan(); // second call should not do anything
+        mWifiVendorHal.stopBgScan(TEST_IFACE_NAME);
+        mWifiVendorHal.stopBgScan(TEST_IFACE_NAME); // second call should not do anything
         verify(mIWifiStaIface).stopBackgroundScan(cmdId); // Should be called just once
     }
 
@@ -1685,8 +1720,8 @@
 
         int cmdId = mWifiVendorHal.mScan.cmdId;
 
-        mWifiVendorHal.pauseBgScan();
-        mWifiVendorHal.restartBgScan();
+        mWifiVendorHal.pauseBgScan(TEST_IFACE_NAME);
+        mWifiVendorHal.restartBgScan(TEST_IFACE_NAME);
         verify(mIWifiStaIface).stopBackgroundScan(cmdId); // Should be called just once
         verify(mIWifiStaIface, times(2)).startBackgroundScan(eq(cmdId), any());
     }
@@ -1849,7 +1884,7 @@
      */
     @Test
     public void testSelectTxPowerScenario() throws RemoteException {
-        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(mWifiVendorHal.startVendorHalSta());
         // Should fail because we exposed the 1.0 IWifiChip.
         assertFalse(
                 mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_VOICE_CALL));
@@ -1860,7 +1895,7 @@
         mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper());
         when(mIWifiChipV11.selectTxPowerScenario(anyInt())).thenReturn(mWifiStatusSuccess);
 
-        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(mWifiVendorHal.startVendorHalSta());
         assertTrue(
                 mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_VOICE_CALL));
         verify(mIWifiChipV11).selectTxPowerScenario(
@@ -1875,7 +1910,7 @@
      */
     @Test
     public void testResetTxPowerScenario() throws RemoteException {
-        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(mWifiVendorHal.startVendorHalSta());
         // Should fail because we exposed the 1.0 IWifiChip.
         assertFalse(mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_NORMAL));
         verify(mIWifiChipV11, never()).resetTxPowerScenario();
@@ -1885,7 +1920,7 @@
         mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper());
         when(mIWifiChipV11.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess);
 
-        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(mWifiVendorHal.startVendorHalSta());
         assertTrue(mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_NORMAL));
         verify(mIWifiChipV11).resetTxPowerScenario();
         verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt());
@@ -1901,13 +1936,73 @@
         mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper());
         when(mIWifiChipV11.selectTxPowerScenario(anyInt())).thenReturn(mWifiStatusSuccess);
 
-        assertTrue(mWifiVendorHal.startVendorHal(true));
+        assertTrue(mWifiVendorHal.startVendorHalSta());
         assertFalse(mWifiVendorHal.selectTxPowerScenario(-6));
         verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt());
         verify(mIWifiChipV11, never()).resetTxPowerScenario();
         mWifiVendorHal.stopVendorHal();
     }
 
+    /**
+     * Test the STA Iface creation failure due to iface name retrieval failure.
+     */
+    @Test
+    public void testCreateStaIfaceFailureInIfaceName() throws RemoteException {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiIface.getNameCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusFailure, "wlan0");
+            }
+        }).when(mIWifiStaIface).getName(any(IWifiIface.getNameCallback.class));
+
+        assertTrue(mWifiVendorHal.startVendorHal());
+        assertNull(mWifiVendorHal.createStaIface(null));
+        verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
+    }
+
+    /**
+     * Test the STA Iface creation failure due to iface name retrieval failure.
+     */
+    @Test
+    public void testCreateApIfaceFailureInIfaceName() throws RemoteException {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(IWifiIface.getNameCallback cb)
+                    throws RemoteException {
+                cb.onValues(mWifiStatusFailure, "wlan0");
+            }
+        }).when(mIWifiApIface).getName(any(IWifiIface.getNameCallback.class));
+
+        assertTrue(mWifiVendorHal.startVendorHal());
+        assertNull(mWifiVendorHal.createApIface(null));
+        verify(mHalDeviceManager).createApIface(eq(null), eq(null));
+    }
+
+    /**
+     * Test the creation and removal of STA Iface.
+     */
+    @Test
+    public void testCreateRemoveStaIface() throws RemoteException {
+        assertTrue(mWifiVendorHal.startVendorHal());
+        String ifaceName = mWifiVendorHal.createStaIface(null);
+        verify(mHalDeviceManager).createStaIface(eq(null), eq(null));
+        assertEquals(TEST_IFACE_NAME, ifaceName);
+        assertTrue(mWifiVendorHal.removeStaIface(ifaceName));
+        verify(mHalDeviceManager).removeIface(eq(mIWifiStaIface));
+    }
+
+    /**
+     * Test the creation and removal of Ap Iface.
+     */
+    @Test
+    public void testCreateRemoveApIface() throws RemoteException {
+        assertTrue(mWifiVendorHal.startVendorHal());
+        String ifaceName = mWifiVendorHal.createApIface(null);
+        verify(mHalDeviceManager).createApIface(eq(null), eq(null));
+        assertEquals(TEST_IFACE_NAME, ifaceName);
+        assertTrue(mWifiVendorHal.removeApIface(ifaceName));
+        verify(mHalDeviceManager).removeIface(eq(mIWifiApIface));
+    }
+
     private void startBgScan(WifiNative.ScanEventHandler eventHandler) throws Exception {
         when(mIWifiStaIface.startBackgroundScan(
                 anyInt(), any(StaBackgroundScanParameters.class))).thenReturn(mWifiStatusSuccess);
@@ -1918,7 +2013,7 @@
         bucketSettings.period_ms = 16000;
         bucketSettings.report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
         settings.buckets = new WifiNative.BucketSettings[] {bucketSettings};
-        assertTrue(mWifiVendorHal.startBgScan(settings, eventHandler));
+        assertTrue(mWifiVendorHal.startBgScan(TEST_IFACE_NAME, settings, eventHandler));
     }
 
     // Create a pair of HIDL scan result and its corresponding framework scan result for
diff --git a/tests/wifitests/src/com/android/server/wifi/WificondControlTest.java b/tests/wifitests/src/com/android/server/wifi/WificondControlTest.java
index cca2045..524324a 100644
--- a/tests/wifitests/src/com/android/server/wifi/WificondControlTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WificondControlTest.java
@@ -16,29 +16,29 @@
 
 package com.android.server.wifi;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static android.net.wifi.WifiConfiguration.KeyMgmt.NONE;
+import static android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK;
+
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.*;
 
 import android.net.wifi.IApInterface;
+import android.net.wifi.IApInterfaceEventCallback;
 import android.net.wifi.IClientInterface;
 import android.net.wifi.IPnoScanEvent;
 import android.net.wifi.IScanEvent;
 import android.net.wifi.IWifiScannerImpl;
 import android.net.wifi.IWificond;
 import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiScanner;
+import android.os.IBinder;
+import android.os.RemoteException;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.wifi.util.NativeUtil;
@@ -52,8 +52,11 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.HashSet;
@@ -64,14 +67,23 @@
  */
 @SmallTest
 public class WificondControlTest {
-    private WifiInjector mWifiInjector;
-    private WifiMonitor mWifiMonitor;
-    private WifiMetrics mWifiMetrics;
-    private CarrierNetworkConfig mCarrierNetworkConfig;
+    @Mock private WifiInjector mWifiInjector;
+    @Mock private WifiMonitor mWifiMonitor;
+    @Mock private WifiMetrics mWifiMetrics;
+    @Mock private IWificond mWificond;
+    @Mock private IBinder mWifiCondBinder;
+    @Mock private IClientInterface mClientInterface;
+    @Mock private IWifiScannerImpl mWifiScannerImpl;
+    @Mock private CarrierNetworkConfig mCarrierNetworkConfig;
+    @Mock private IApInterface mApInterface;
+    @Mock private WifiNative.SoftApListener mSoftApListener;
     private WificondControl mWificondControl;
     private static final String TEST_INTERFACE_NAME = "test_wlan_if";
+    private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1";
     private static final byte[] TEST_SSID =
             new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+    private static final byte[] TEST_PSK =
+            new byte[] {'T', 'e', 's', 't'};
     private static final byte[] TEST_BSSID =
             new byte[] {(byte) 0x12, (byte) 0xef, (byte) 0xa1,
                         (byte) 0x2c, (byte) 0x97, (byte) 0x8b};
@@ -144,109 +156,241 @@
 
     @Before
     public void setUp() throws Exception {
-        mWifiInjector = mock(WifiInjector.class);
-        mWifiMonitor = mock(WifiMonitor.class);
-        mWifiMetrics = mock(WifiMetrics.class);
+        // Setup mocks for successful WificondControl operation. Failure case mocks should be
+        // created in specific tests
+        MockitoAnnotations.initMocks(this);
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.asBinder()).thenReturn(mWifiCondBinder);
+        when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+        when(mWificond.createClientInterface(any())).thenReturn(mClientInterface);
+        when(mWificond.createApInterface(any())).thenReturn(mApInterface);
+        when(mWificond.tearDownClientInterface(any())).thenReturn(true);
+        when(mWificond.tearDownApInterface(any())).thenReturn(true);
+        when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+        when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
         when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
-        mCarrierNetworkConfig = mock(CarrierNetworkConfig.class);
         mWificondControl = new WificondControl(mWifiInjector, mWifiMonitor, mCarrierNetworkConfig);
+        assertEquals(mClientInterface, mWificondControl.setupInterfaceForClientMode(
+                TEST_INTERFACE_NAME));
+        verify(mWifiInjector).makeWificond();
+        verify(mWifiCondBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
     }
 
     /**
-     * Verifies that setupDriverForClientMode() calls Wificond.
+     * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls Wificond.
      */
     @Test
-    public void testSetupDriverForClientMode() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IClientInterface clientInterface = getMockClientInterface();
-
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(clientInterface);
-
-        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
-        assertEquals(clientInterface, returnedClientInterface);
-        verify(wificond).createClientInterface();
+    public void testSetupInterfaceForClientMode() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+        verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
     }
 
     /**
-     * Verifies that setupDriverForClientMode() calls subscribeScanEvents().
+     * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls subscribeScanEvents().
      */
     @Test
-    public void testSetupDriverForClientModeCallsScanEventSubscripiton() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
-        verify(scanner).subscribeScanEvents(any(IScanEvent.class));
+    public void testSetupInterfaceForClientModeCallsScanEventSubscripiton() throws Exception {
+        verify(mWifiScannerImpl).subscribeScanEvents(any(IScanEvent.class));
     }
 
     /**
-     * Verifies that setupDriverForClientMode() returns null when wificond is not started.
+     * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) returns null when wificond is
+     * not started.
      */
     @Test
-    public void testSetupDriverForClientModeErrorWhenWificondIsNotStarted() throws Exception {
+    public void testSetupInterfaceForClientModeErrorWhenWificondIsNotStarted() throws Exception {
+        // Invoke wificond death handler to clear the handle.
+        mWificondControl.binderDied();
         when(mWifiInjector.makeWificond()).thenReturn(null);
+        IClientInterface returnedClientInterface =
+                mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME);
+        assertEquals(null, returnedClientInterface);
+        verify(mWifiInjector, times(2)).makeWificond();
+    }
 
-        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
+    /**
+     * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) returns null when wificond
+     * failed to setup client interface.
+     */
+    @Test
+    public void testSetupInterfaceForClientModeErrorWhenWificondFailedToSetupInterface()
+            throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(null);
+
+        IClientInterface returnedClientInterface =
+                mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME);
         assertEquals(null, returnedClientInterface);
     }
 
     /**
-     * Verifies that setupDriverForClientMode() returns null when wificond failed to setup client
-     * interface.
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
      */
     @Test
-    public void testSetupDriverForClientModeErrorWhenWificondFailedToSetupInterface()
-            throws Exception {
-        IWificond wificond = mock(IWificond.class);
+    public void testTeardownClientInterface() throws Exception {
+        when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
 
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(null);
-
-        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
-        assertEquals(null, returnedClientInterface);
+        assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
+        verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+        verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
     }
 
     /**
-     * Verifies that setupDriverForSoftApMode() calls wificond.
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
      */
     @Test
-    public void testSetupDriverForSoftApMode() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IApInterface apInterface = mock(IApInterface.class);
+    public void testTeardownClientInterfaceFailDueToExceptionScannerUnsubscribe() throws Exception {
+        when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+        doThrow(new RemoteException()).when(mWifiScannerImpl).unsubscribeScanEvents();
 
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createApInterface()).thenReturn(apInterface);
+        assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
+        verify(mWifiScannerImpl, never()).unsubscribePnoScanEvents();
+        verify(mWificond, never()).tearDownClientInterface(TEST_INTERFACE_NAME);
+    }
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownClientInterfaceErrorWhenWificondFailed() throws Exception {
+        when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(false);
 
-        IApInterface returnedApInterface = mWificondControl.setupDriverForSoftApMode();
-        assertEquals(apInterface, returnedApInterface);
-        verify(wificond).createApInterface();
+        assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
+        verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+        verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
     }
 
     /**
-     * Verifies that setupDriverForSoftAp() returns null when wificond is not started.
+     * Verifies that the client handles are cleared after teardown.
      */
     @Test
-    public void testSetupDriverForSoftApModeErrorWhenWificondIsNotStarted() throws Exception {
+    public void testTeardownClientInterfaceClearsHandles() throws Exception {
+        testTeardownClientInterface();
+
+        assertNull(mWificondControl.signalPoll(TEST_INTERFACE_NAME));
+        verify(mClientInterface, never()).signalPoll();
+
+        assertFalse(mWificondControl.scan(
+                TEST_INTERFACE_NAME, SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET));
+        verify(mWifiScannerImpl, never()).scan(any());
+    }
+
+    /**
+     * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) calls wificond.
+     */
+    @Test
+    public void testSetupInterfaceForSoftApMode() throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(mApInterface);
+
+        IApInterface returnedApInterface =
+                mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME);
+        assertEquals(mApInterface, returnedApInterface);
+        verify(mWifiInjector).makeWificond();
+        verify(mWifiCondBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+        verify(mWificond).createApInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that setupInterfaceForSoftAp() returns null when wificond is not started.
+     */
+    @Test
+    public void testSetupInterfaceForSoftApModeErrorWhenWificondIsNotStarted() throws Exception {
+        // Invoke wificond death handler to clear the handle.
+        mWificondControl.binderDied();
         when(mWifiInjector.makeWificond()).thenReturn(null);
 
-        IApInterface returnedApInterface = mWificondControl.setupDriverForSoftApMode();
+        IApInterface returnedApInterface =
+                mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME);
 
         assertEquals(null, returnedApInterface);
+        verify(mWifiInjector, times(2)).makeWificond();
+    }
+
+    /**
+     * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) returns null when wificond
+     * failed to setup AP interface.
+     */
+    @Test
+    public void testSetupInterfaceForSoftApModeErrorWhenWificondFailedToSetupInterface()
+            throws Exception {
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(null);
+
+        IApInterface returnedApInterface =
+                mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME);
+        assertEquals(null, returnedApInterface);
     }
 
     /**
-     * Verifies that setupDriverForSoftApMode() returns null when wificond failed to setup
-     * AP interface.
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
      */
     @Test
-    public void testSetupDriverForSoftApModeErrorWhenWificondFailedToSetupInterface()
-            throws Exception {
-        IWificond wificond = mock(IWificond.class);
+    public void testTeardownSoftApInterface() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(true);
 
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createApInterface()).thenReturn(null);
+        assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+        verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+    }
 
-        IApInterface returnedApInterface = mWificondControl.setupDriverForSoftApMode();
-        assertEquals(null, returnedApInterface);
+    /**
+     * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+     */
+    @Test
+    public void testTeardownSoftApInterfaceErrorWhenWificondFailed() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(false);
+
+        assertFalse(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+        verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+    }
+
+    /**
+     * Verifies that the SoftAp handles are cleared after teardown.
+     */
+    @Test
+    public void testTeardownSoftApInterfaceClearsHandles() throws Exception {
+        testTeardownSoftApInterface();
+
+        assertFalse(mWificondControl.startSoftAp(
+                TEST_INTERFACE_NAME, new WifiConfiguration(), mSoftApListener));
+        verify(mApInterface, never()).writeHostapdConfig(
+                any(byte[].class), anyBoolean(), anyInt(), anyInt(), any(byte[].class));
+        verify(mApInterface, never()).startHostapd(any());
+    }
+
+    /**
+     * Verifies that we can setup concurrent interfaces.
+     */
+    @Test
+    public void testSetupMulitpleInterfaces() throws Exception {
+        when(mWificond.createApInterface(TEST_INTERFACE_NAME1)).thenReturn(mApInterface);
+
+        IApInterface returnedApInterface =
+                mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME1);
+        assertEquals(mApInterface, returnedApInterface);
+        verify(mWifiInjector).makeWificond();
+        verify(mWifiCondBinder).linkToDeath(any(IBinder.DeathRecipient.class), anyInt());
+
+        verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
+        verify(mWificond).createApInterface(TEST_INTERFACE_NAME1);
+    }
+
+    /**
+     * Verifies that we can setup concurrent interfaces.
+     */
+    @Test
+    public void testTeardownMulitpleInterfaces() throws Exception {
+        testSetupMulitpleInterfaces();
+        assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+        assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME1));
+
+        verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+        verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME1);
     }
 
     /**
@@ -254,16 +398,12 @@
      */
     @Test
     public void testEnableSupplicant() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IClientInterface clientInterface = getMockClientInterface();
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.enableSupplicant()).thenReturn(true);
 
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(clientInterface);
-        when(clientInterface.enableSupplicant()).thenReturn(true);
-
-        mWificondControl.setupDriverForClientMode();
         assertTrue(mWificondControl.enableSupplicant());
-        verify(clientInterface).enableSupplicant();
+        verify(mWifiInjector).makeWificond();
+        verify(mWificond).enableSupplicant();
     }
 
     /**
@@ -272,15 +412,13 @@
      */
     @Test
     public void testEnableSupplicantErrorWhenNoClientInterfaceConfigured() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IClientInterface clientInterface = getMockClientInterface();
-
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(clientInterface);
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
 
         // Configure client interface.
-        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
-        assertEquals(clientInterface, returnedClientInterface);
+        IClientInterface returnedClientInterface =
+                mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME);
+        assertEquals(mClientInterface, returnedClientInterface);
 
         // Tear down interfaces.
         assertTrue(mWificondControl.tearDownInterfaces());
@@ -294,16 +432,12 @@
      */
     @Test
     public void testDisableSupplicant() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IClientInterface clientInterface = getMockClientInterface();
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.disableSupplicant()).thenReturn(true);
 
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(clientInterface);
-        when(clientInterface.disableSupplicant()).thenReturn(true);
-
-        mWificondControl.setupDriverForClientMode();
         assertTrue(mWificondControl.disableSupplicant());
-        verify(clientInterface).disableSupplicant();
+        verify(mWifiInjector).makeWificond();
+        verify(mWificond).disableSupplicant();
     }
 
     /**
@@ -312,15 +446,13 @@
      */
     @Test
     public void testDisableSupplicantErrorWhenNoClientInterfaceConfigured() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IClientInterface clientInterface = getMockClientInterface();
-
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(clientInterface);
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
 
         // Configure client interface.
-        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
-        assertEquals(clientInterface, returnedClientInterface);
+        IClientInterface returnedClientInterface =
+                mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME);
+        assertEquals(mClientInterface, returnedClientInterface);
 
         // Tear down interfaces.
         assertTrue(mWificondControl.tearDownInterfaces());
@@ -334,12 +466,9 @@
      */
     @Test
     public void testTearDownInterfaces() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
         assertTrue(mWificondControl.tearDownInterfaces());
-
-        verify(wificond).tearDownInterfaces();
+        verify(mWificond).tearDownInterfaces();
     }
 
     /**
@@ -348,11 +477,8 @@
      */
     @Test
     public void testTearDownInterfacesRemovesScanEventSubscription() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
         assertTrue(mWificondControl.tearDownInterfaces());
-
-        verify(scanner).unsubscribeScanEvents();
+        verify(mWifiScannerImpl).unsubscribeScanEvents();
     }
 
 
@@ -361,8 +487,9 @@
      */
     @Test
     public void testTearDownInterfacesErrorWhenWificondIsNotStarterd() throws Exception {
+        // Invoke wificond death handler to clear the handle.
+        mWificondControl.binderDied();
         when(mWifiInjector.makeWificond()).thenReturn(null);
-
         assertFalse(mWificondControl.tearDownInterfaces());
     }
 
@@ -371,15 +498,12 @@
      */
     @Test
     public void testSignalPoll() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IClientInterface clientInterface = getMockClientInterface();
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
 
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(clientInterface);
-
-        mWificondControl.setupDriverForClientMode();
-        mWificondControl.signalPoll();
-        verify(clientInterface).signalPoll();
+        mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME);
+        mWificondControl.signalPoll(TEST_INTERFACE_NAME);
+        verify(mClientInterface).signalPoll();
     }
 
     /**
@@ -387,21 +511,19 @@
      */
     @Test
     public void testSignalPollErrorWhenNoClientInterfaceConfigured() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IClientInterface clientInterface = getMockClientInterface();
-
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(clientInterface);
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
 
         // Configure client interface.
-        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
-        assertEquals(clientInterface, returnedClientInterface);
+        IClientInterface returnedClientInterface =
+                mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME);
+        assertEquals(mClientInterface, returnedClientInterface);
 
         // Tear down interfaces.
         assertTrue(mWificondControl.tearDownInterfaces());
 
         // Signal poll should fail.
-        assertEquals(null, mWificondControl.signalPoll());
+        assertEquals(null, mWificondControl.signalPoll(TEST_INTERFACE_NAME));
     }
 
     /**
@@ -409,15 +531,12 @@
      */
     @Test
     public void testGetTxPacketCounters() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IClientInterface clientInterface = getMockClientInterface();
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
 
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(clientInterface);
-
-        mWificondControl.setupDriverForClientMode();
-        mWificondControl.getTxPacketCounters();
-        verify(clientInterface).getPacketCounters();
+        mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME);
+        mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME);
+        verify(mClientInterface).getPacketCounters();
     }
 
     /**
@@ -426,21 +545,19 @@
      */
     @Test
     public void testGetTxPacketCountersErrorWhenNoClientInterfaceConfigured() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IClientInterface clientInterface = getMockClientInterface();
-
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(clientInterface);
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
 
         // Configure client interface.
-        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
-        assertEquals(clientInterface, returnedClientInterface);
+        IClientInterface returnedClientInterface =
+                mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME);
+        assertEquals(mClientInterface, returnedClientInterface);
 
         // Tear down interfaces.
         assertTrue(mWificondControl.tearDownInterfaces());
 
         // Signal poll should fail.
-        assertEquals(null, mWificondControl.getTxPacketCounters());
+        assertEquals(null, mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME));
     }
 
     /**
@@ -449,22 +566,21 @@
      */
     @Test
     public void testGetScanResultsErrorWhenNoClientInterfaceConfigured() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IClientInterface clientInterface = getMockClientInterface();
-
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(clientInterface);
+        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+        when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
 
         // Configure client interface.
-        IClientInterface returnedClientInterface = mWificondControl.setupDriverForClientMode();
-        assertEquals(clientInterface, returnedClientInterface);
+        IClientInterface returnedClientInterface =
+                mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME);
+        assertEquals(mClientInterface, returnedClientInterface);
 
         // Tear down interfaces.
         assertTrue(mWificondControl.tearDownInterfaces());
 
         // getScanResults should fail.
         assertEquals(0,
-                mWificondControl.getScanResults(WificondControl.SCAN_TYPE_SINGLE_SCAN).size());
+                mWificondControl.getScanResults(TEST_INTERFACE_NAME,
+                        WificondControl.SCAN_TYPE_SINGLE_SCAN).size());
     }
 
     /**
@@ -472,15 +588,14 @@
      */
     @Test
     public void testGetScanResults() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-        assertNotNull(scanner);
+        assertNotNull(mWifiScannerImpl);
 
         // Mock the returned array of NativeScanResult.
         NativeScanResult[] mockScanResults = {MOCK_NATIVE_SCAN_RESULT};
-        when(scanner.getScanResults()).thenReturn(mockScanResults);
+        when(mWifiScannerImpl.getScanResults()).thenReturn(mockScanResults);
 
         ArrayList<ScanDetail> returnedScanResults = mWificondControl.getScanResults(
-                WificondControl.SCAN_TYPE_SINGLE_SCAN);
+                TEST_INTERFACE_NAME, WificondControl.SCAN_TYPE_SINGLE_SCAN);
         // The test IEs {@link #TEST_INFO_ELEMENT} doesn't contained RSN IE, which means non-EAP
         // AP. So verify carrier network is not checked, since EAP is currently required for a
         // carrier network.
@@ -507,8 +622,7 @@
      */
     @Test
     public void testGetScanResultsForCarrierAp() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-        assertNotNull(scanner);
+        assertNotNull(mWifiScannerImpl);
 
         // Include RSN IE to indicate EAP key management.
         ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -516,7 +630,8 @@
         out.write(TEST_INFO_ELEMENT_RSN);
         NativeScanResult nativeScanResult = new NativeScanResult(MOCK_NATIVE_SCAN_RESULT);
         nativeScanResult.infoElement = out.toByteArray();
-        when(scanner.getScanResults()).thenReturn(new NativeScanResult[] {nativeScanResult});
+        when(mWifiScannerImpl.getScanResults()).thenReturn(
+                new NativeScanResult[] {nativeScanResult});
 
         // AP associated with a carrier network.
         int eapType = WifiEnterpriseConfig.Eap.SIM;
@@ -528,7 +643,7 @@
         when(mCarrierNetworkConfig.getCarrierName(new String(nativeScanResult.ssid)))
                 .thenReturn(carrierName);
         ArrayList<ScanDetail> returnedScanResults = mWificondControl.getScanResults(
-                WificondControl.SCAN_TYPE_SINGLE_SCAN);
+                TEST_INTERFACE_NAME, WificondControl.SCAN_TYPE_SINGLE_SCAN);
         assertEquals(1, returnedScanResults.size());
         // Verify returned scan result.
         ScanResult scanResult = returnedScanResults.get(0).getScanResult();
@@ -542,7 +657,7 @@
         when(mCarrierNetworkConfig.isCarrierNetwork(new String(nativeScanResult.ssid)))
                 .thenReturn(false);
         returnedScanResults = mWificondControl.getScanResults(
-                WificondControl.SCAN_TYPE_SINGLE_SCAN);
+                TEST_INTERFACE_NAME, WificondControl.SCAN_TYPE_SINGLE_SCAN);
         assertEquals(1, returnedScanResults.size());
         // Verify returned scan result.
         scanResult = returnedScanResults.get(0).getScanResult();
@@ -557,12 +672,10 @@
      */
     @Test
     public void testScan() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
-        when(scanner.scan(any(SingleScanSettings.class))).thenReturn(true);
-
-        assertTrue(mWificondControl.scan(SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET));
-        verify(scanner).scan(argThat(new ScanMatcher(
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        assertTrue(mWificondControl.scan(
+                TEST_INTERFACE_NAME, SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET));
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
                 SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET)));
     }
 
@@ -571,12 +684,9 @@
      */
     @Test
     public void testScanNullParameters() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
-        when(scanner.scan(any(SingleScanSettings.class))).thenReturn(true);
-
-        assertTrue(mWificondControl.scan(null, null));
-        verify(scanner).scan(argThat(new ScanMatcher(null, null)));
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        assertTrue(mWificondControl.scan(TEST_INTERFACE_NAME, null, null));
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(null, null)));
     }
 
     /**
@@ -584,11 +694,10 @@
      */
     @Test
     public void testScanFailure() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
-        when(scanner.scan(any(SingleScanSettings.class))).thenReturn(false);
-        assertFalse(mWificondControl.scan(SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET));
-        verify(scanner).scan(any(SingleScanSettings.class));
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(false);
+        assertFalse(mWificondControl.scan(
+                TEST_INTERFACE_NAME, SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_SET));
+        verify(mWifiScannerImpl).scan(any(SingleScanSettings.class));
     }
 
     /**
@@ -596,11 +705,9 @@
      */
     @Test
     public void testStartPnoScan() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
-        when(scanner.startPnoScan(any(PnoSettings.class))).thenReturn(true);
-        assertTrue(mWificondControl.startPnoScan(TEST_PNO_SETTINGS));
-        verify(scanner).startPnoScan(argThat(new PnoScanMatcher(TEST_PNO_SETTINGS)));
+        when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(true);
+        assertTrue(mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS));
+        verify(mWifiScannerImpl).startPnoScan(argThat(new PnoScanMatcher(TEST_PNO_SETTINGS)));
     }
 
     /**
@@ -608,11 +715,9 @@
      */
     @Test
     public void testStopPnoScan() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
-        when(scanner.stopPnoScan()).thenReturn(true);
-        assertTrue(mWificondControl.stopPnoScan());
-        verify(scanner).stopPnoScan();
+        when(mWifiScannerImpl.stopPnoScan()).thenReturn(true);
+        assertTrue(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).stopPnoScan();
     }
 
     /**
@@ -620,11 +725,10 @@
      */
     @Test
     public void testStopPnoScanFailure() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
 
-        when(scanner.stopPnoScan()).thenReturn(false);
-        assertFalse(mWificondControl.stopPnoScan());
-        verify(scanner).stopPnoScan();
+        when(mWifiScannerImpl.stopPnoScan()).thenReturn(false);
+        assertFalse(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+        verify(mWifiScannerImpl).stopPnoScan();
     }
 
     /**
@@ -633,10 +737,8 @@
      */
     @Test
     public void testScanResultEvent() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
         ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
-        verify(scanner).subscribeScanEvents(messageCaptor.capture());
+        verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
         IScanEvent scanEvent = messageCaptor.getValue();
         assertNotNull(scanEvent);
         scanEvent.OnScanResultReady();
@@ -650,10 +752,9 @@
      */
     @Test
     public void testScanFailedEvent() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
 
         ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
-        verify(scanner).subscribeScanEvents(messageCaptor.capture());
+        verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
         IScanEvent scanEvent = messageCaptor.getValue();
         assertNotNull(scanEvent);
         scanEvent.OnScanFailed();
@@ -667,10 +768,8 @@
      */
     @Test
     public void testPnoScanResultEvent() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
         ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
-        verify(scanner).subscribePnoScanEvents(messageCaptor.capture());
+        verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
         IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
         assertNotNull(pnoScanEvent);
         pnoScanEvent.OnPnoNetworkFound();
@@ -682,10 +781,8 @@
      */
     @Test
     public void testPnoScanEventsForMetrics() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
         ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
-        verify(scanner).subscribePnoScanEvents(messageCaptor.capture());
+        verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
         IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
         assertNotNull(pnoScanEvent);
 
@@ -707,10 +804,8 @@
      */
     @Test
     public void testStartPnoScanForMetrics() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
-        when(scanner.startPnoScan(any(PnoSettings.class))).thenReturn(false);
-        assertFalse(mWificondControl.startPnoScan(TEST_PNO_SETTINGS));
+        when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(false);
+        assertFalse(mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS));
         verify(mWifiMetrics).incrementPnoScanStartAttempCount();
         verify(mWifiMetrics).incrementPnoScanFailedCount();
     }
@@ -720,42 +815,281 @@
      */
     @Test
     public void testAbortScan() throws Exception {
-        IWifiScannerImpl scanner = setupClientInterfaceAndCreateMockWificondScanner();
-
-        mWificondControl.abortScan();
-        verify(scanner).abortScan();
+        mWificondControl.abortScan(TEST_INTERFACE_NAME);
+        verify(mWifiScannerImpl).abortScan();
     }
 
     /**
-     * Helper method: create a mock IClientInterface which mocks all neccessary operations.
-     * Returns a mock IClientInterface.
+     * Verifies successful soft ap start.
      */
-    private IClientInterface getMockClientInterface() throws Exception {
-        IClientInterface clientInterface = mock(IClientInterface.class);
-        IWifiScannerImpl scanner = mock(IWifiScannerImpl.class);
+    @Test
+    public void testStartSoftApWithPskConfig() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = new String(TEST_SSID, StandardCharsets.UTF_8);
+        config.allowedKeyManagement.set(WPA_PSK);
+        config.preSharedKey = new String(TEST_PSK, StandardCharsets.UTF_8);
+        config.hiddenSSID = false;
+        config.apChannel = TEST_FREQUENCY;
 
-        when(clientInterface.getWifiScannerImpl()).thenReturn(scanner);
+        when(mApInterface.writeHostapdConfig(
+                any(byte[].class), anyBoolean(), anyInt(), anyInt(), any(byte[].class)))
+                .thenReturn(true);
+        when(mApInterface.startHostapd(any())).thenReturn(true);
 
-        return clientInterface;
+        assertTrue(mWificondControl.startSoftAp(
+                TEST_INTERFACE_NAME, config, mSoftApListener));
+        verify(mApInterface).writeHostapdConfig(
+                eq(TEST_SSID), eq(false), eq(TEST_FREQUENCY),
+                eq(IApInterface.ENCRYPTION_TYPE_WPA), eq(TEST_PSK));
+        verify(mApInterface).startHostapd(any());
     }
 
     /**
-     * Helper method: Setup interface to client mode for mWificondControl.
-     * Returns a mock IWifiScannerImpl.
+     * Verifies successful soft ap start.
      */
-    private IWifiScannerImpl setupClientInterfaceAndCreateMockWificondScanner() throws Exception {
-        IWificond wificond = mock(IWificond.class);
-        IClientInterface clientInterface = mock(IClientInterface.class);
-        IWifiScannerImpl scanner = mock(IWifiScannerImpl.class);
+    @Test
+    public void testStartSoftApWithOpenHiddenConfig() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = new String(TEST_SSID, StandardCharsets.UTF_8);
+        config.allowedKeyManagement.set(NONE);
+        config.hiddenSSID = true;
+        config.apChannel = TEST_FREQUENCY;
 
-        when(mWifiInjector.makeWificond()).thenReturn(wificond);
-        when(wificond.createClientInterface()).thenReturn(clientInterface);
-        when(clientInterface.getWifiScannerImpl()).thenReturn(scanner);
-        when(clientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
+        when(mApInterface.writeHostapdConfig(
+                any(byte[].class), anyBoolean(), anyInt(), anyInt(), any(byte[].class)))
+                .thenReturn(true);
+        when(mApInterface.startHostapd(any())).thenReturn(true);
 
-        assertEquals(clientInterface, mWificondControl.setupDriverForClientMode());
+        assertTrue(mWificondControl.startSoftAp(
+                TEST_INTERFACE_NAME, config, mSoftApListener));
+        verify(mApInterface).writeHostapdConfig(
+                eq(TEST_SSID), eq(true), eq(TEST_FREQUENCY),
+                eq(IApInterface.ENCRYPTION_TYPE_NONE), eq(new byte[0]));
+        verify(mApInterface).startHostapd(any());
+    }
 
-        return scanner;
+    /**
+     * Ensures that the Ap interface callbacks are forwarded to the
+     * SoftApListener used for starting soft AP.
+     */
+    @Test
+    public void testSoftApListenerInvocation() throws Exception {
+        testSetupInterfaceForSoftApMode();
+
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = new String(TEST_SSID, StandardCharsets.UTF_8);
+
+        when(mApInterface.writeHostapdConfig(
+                any(byte[].class), anyBoolean(), anyInt(), anyInt(), any(byte[].class)))
+                .thenReturn(true);
+        when(mApInterface.startHostapd(any())).thenReturn(true);
+
+        final ArgumentCaptor<IApInterfaceEventCallback> apInterfaceCallbackCaptor =
+                ArgumentCaptor.forClass(IApInterfaceEventCallback.class);
+
+        assertTrue(mWificondControl.startSoftAp(
+                TEST_INTERFACE_NAME, config, mSoftApListener));
+        verify(mApInterface).writeHostapdConfig(
+                eq(TEST_SSID), anyBoolean(), anyInt(),
+                anyInt(), any(byte[].class));
+        verify(mApInterface).startHostapd(apInterfaceCallbackCaptor.capture());
+
+        int numStations = 5;
+        apInterfaceCallbackCaptor.getValue().onNumAssociatedStationsChanged(numStations);
+        verify(mSoftApListener).onNumAssociatedStationsChanged(eq(numStations));
+
+    }
+
+    /**
+     * Ensure that soft ap start fails when the interface is not setup.
+     */
+    @Test
+    public void testStartSoftApWithoutSetupInterface() throws Exception {
+        assertFalse(mWificondControl.startSoftAp(
+                TEST_INTERFACE_NAME, new WifiConfiguration(), mSoftApListener));
+        verify(mApInterface, never()).writeHostapdConfig(
+                any(byte[].class), anyBoolean(), anyInt(), anyInt(), any(byte[].class));
+        verify(mApInterface, never()).startHostapd(any());
+    }
+
+    /**
+     * Verifies soft ap start failure.
+     */
+    @Test
+    public void testStartSoftApFailDueToWriteConfigError() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = new String(TEST_SSID, StandardCharsets.UTF_8);
+
+        when(mApInterface.writeHostapdConfig(
+                any(byte[].class), anyBoolean(), anyInt(), anyInt(), any(byte[].class)))
+                .thenReturn(false);
+        when(mApInterface.startHostapd(any())).thenReturn(true);
+
+        assertFalse(mWificondControl.startSoftAp(
+                TEST_INTERFACE_NAME, config, mSoftApListener));
+        verify(mApInterface).writeHostapdConfig(
+                eq(TEST_SSID), anyBoolean(), anyInt(),
+                anyInt(), any(byte[].class));
+        verify(mApInterface, never()).startHostapd(any());
+    }
+
+    /**
+     * Verifies soft ap start failure.
+     */
+    @Test
+    public void testStartSoftApFailDueToStartError() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = new String(TEST_SSID, StandardCharsets.UTF_8);
+
+        when(mApInterface.writeHostapdConfig(
+                any(byte[].class), anyBoolean(), anyInt(), anyInt(), any(byte[].class)))
+                .thenReturn(true);
+        when(mApInterface.startHostapd(any())).thenReturn(false);
+
+        assertFalse(mWificondControl.startSoftAp(
+                TEST_INTERFACE_NAME, config, mSoftApListener));
+        verify(mApInterface).writeHostapdConfig(
+                eq(TEST_SSID), anyBoolean(), anyInt(),
+                anyInt(), any(byte[].class));
+        verify(mApInterface).startHostapd(any());
+    }
+
+    /**
+     * Verifies soft ap start failure.
+     */
+    @Test
+    public void testStartSoftApFailDueToExceptionInStart() throws Exception {
+        testSetupInterfaceForSoftApMode();
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = new String(TEST_SSID, StandardCharsets.UTF_8);
+
+        when(mApInterface.writeHostapdConfig(
+                any(byte[].class), anyBoolean(), anyInt(), anyInt(), any(byte[].class)))
+                .thenReturn(true);
+        doThrow(new RemoteException()).when(mApInterface).startHostapd(any());
+
+        assertFalse(mWificondControl.startSoftAp(
+                TEST_INTERFACE_NAME, config, mSoftApListener));
+        verify(mApInterface).writeHostapdConfig(
+                eq(TEST_SSID), anyBoolean(), anyInt(),
+                anyInt(), any(byte[].class));
+        verify(mApInterface).startHostapd(any());
+    }
+
+    /**
+     * Verifies soft ap stop success.
+     */
+    @Test
+    public void testStopSoftAp() throws Exception {
+        testSetupInterfaceForSoftApMode();
+
+        when(mApInterface.stopHostapd()).thenReturn(true);
+
+        assertTrue(mWificondControl.stopSoftAp(TEST_INTERFACE_NAME));
+        verify(mApInterface).stopHostapd();
+    }
+
+    /**
+     * Ensure that soft ap stop fails when the interface is not setup.
+     */
+    @Test
+    public void testStopSoftApWithOutSetupInterface() throws Exception {
+        when(mApInterface.stopHostapd()).thenReturn(true);
+        assertFalse(mWificondControl.stopSoftAp(TEST_INTERFACE_NAME));
+        verify(mApInterface, never()).stopHostapd();
+    }
+
+    /**
+     * Verifies soft ap stop failure.
+     */
+    @Test
+    public void testStopSoftApFailDueToStopError() throws Exception {
+        testSetupInterfaceForSoftApMode();
+
+        when(mApInterface.stopHostapd()).thenReturn(false);
+
+        assertFalse(mWificondControl.stopSoftAp(TEST_INTERFACE_NAME));
+        verify(mApInterface).stopHostapd();
+    }
+
+    /**
+     * Verifies soft ap stop failure.
+     */
+    @Test
+    public void testStopSoftApFailDueToExceptionInStop() throws Exception {
+        testSetupInterfaceForSoftApMode();
+
+        doThrow(new RemoteException()).when(mApInterface).stopHostapd();
+
+        assertFalse(mWificondControl.stopSoftAp(TEST_INTERFACE_NAME));
+        verify(mApInterface).stopHostapd();
+    }
+
+    /**
+     * Verifies registration and invocation of wificond death handler.
+     */
+    @Test
+    public void testRegisterDeathHandler() throws Exception {
+        WifiNative.WificondDeathEventHandler handler =
+                mock(WifiNative.WificondDeathEventHandler.class);
+        assertTrue(mWificondControl.registerDeathHandler(handler));
+        mWificondControl.binderDied();
+        verify(handler).onDeath();
+    }
+
+    /**
+     * Verifies registration and invocation of 2 wificond death handlers.
+     */
+    @Test
+    public void testRegisterTwoDeathHandlers() throws Exception {
+        WifiNative.WificondDeathEventHandler handler1 =
+                mock(WifiNative.WificondDeathEventHandler.class);
+        WifiNative.WificondDeathEventHandler handler2 =
+                mock(WifiNative.WificondDeathEventHandler.class);
+        assertTrue(mWificondControl.registerDeathHandler(handler1));
+        assertFalse(mWificondControl.registerDeathHandler(handler2));
+        mWificondControl.binderDied();
+        verify(handler1).onDeath();
+        verify(handler2, never()).onDeath();
+    }
+
+    /**
+     * Verifies de-registration of wificond death handler.
+     */
+    @Test
+    public void testDeregisterDeathHandler() throws Exception {
+        WifiNative.WificondDeathEventHandler handler =
+                mock(WifiNative.WificondDeathEventHandler.class);
+        assertTrue(mWificondControl.registerDeathHandler(handler));
+        assertTrue(mWificondControl.deregisterDeathHandler());
+        mWificondControl.binderDied();
+        verify(handler, never()).onDeath();
+    }
+
+    /**
+     * Verifies handling of wificond death and ensures that all internal state is cleared and
+     * handlers are invoked.
+     */
+    @Test
+    public void testDeathHandling() throws Exception {
+        WifiNative.WificondDeathEventHandler handler =
+                mock(WifiNative.WificondDeathEventHandler.class);
+        assertTrue(mWificondControl.registerDeathHandler(handler));
+
+        testSetupInterfaceForClientMode();
+
+        mWificondControl.binderDied();
+        verify(handler).onDeath();
+
+        // The handles should be cleared after death, so these should retrieve new handles.
+        when(mWificond.enableSupplicant()).thenReturn(true);
+        assertTrue(mWificondControl.enableSupplicant());
+        verify(mWifiInjector, times(2)).makeWificond();
+        verify(mWificond).enableSupplicant();
     }
 
     // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
index 61143d9..f168c8c 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareDataPathStateManagerTest.java
@@ -1285,8 +1285,9 @@
         NetworkCapabilities nc = new NetworkCapabilities();
         nc.clearAll();
         nc.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE);
-        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN).addCapability(
-                NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
         nc.setNetworkSpecifier(ns);
         nc.setLinkUpstreamBandwidthKbps(1);
         nc.setLinkDownstreamBandwidthKbps(1);
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeApiTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeApiTest.java
index 9564189..dfec005 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeApiTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeApiTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.anyShort;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -28,9 +29,14 @@
 import android.hardware.wifi.V1_0.NanBandIndex;
 import android.hardware.wifi.V1_0.NanConfigRequest;
 import android.hardware.wifi.V1_0.NanEnableRequest;
+import android.hardware.wifi.V1_0.NanPublishRequest;
+import android.hardware.wifi.V1_0.NanRangingIndication;
+import android.hardware.wifi.V1_0.NanSubscribeRequest;
 import android.hardware.wifi.V1_0.WifiStatus;
 import android.hardware.wifi.V1_0.WifiStatusCode;
 import android.net.wifi.aware.ConfigRequest;
+import android.net.wifi.aware.PublishConfig;
+import android.net.wifi.aware.SubscribeConfig;
 import android.os.RemoteException;
 
 import org.junit.Before;
@@ -67,6 +73,8 @@
         status.code = WifiStatusCode.SUCCESS;
         when(mIWifiNanIfaceMock.enableRequest(anyShort(), any())).thenReturn(status);
         when(mIWifiNanIfaceMock.configRequest(anyShort(), any())).thenReturn(status);
+        when(mIWifiNanIfaceMock.startPublishRequest(anyShort(), any())).thenReturn(status);
+        when(mIWifiNanIfaceMock.startSubscribeRequest(anyShort(), any())).thenReturn(status);
 
         mDut = new WifiAwareNativeApi(mWifiAwareNativeManagerMock);
     }
@@ -164,6 +172,105 @@
                         .validDiscoveryWindowIntervalVal));
     }
 
+    @Test
+    public void testDiscoveryRangingSettings() throws RemoteException {
+        short tid = 666;
+        byte pid = 34;
+        int minDistanceMm = 100;
+        int maxDistanceMm = 555;
+        // TODO: b/69428593 remove once HAL is converted from CM to MM
+        short minDistanceCm = (short) (minDistanceMm / 10);
+        short maxDistanceCm = (short) (maxDistanceMm / 10);
+
+        ArgumentCaptor<NanPublishRequest> pubCaptor = ArgumentCaptor.forClass(
+                NanPublishRequest.class);
+        ArgumentCaptor<NanSubscribeRequest> subCaptor = ArgumentCaptor.forClass(
+                NanSubscribeRequest.class);
+
+        PublishConfig pubDefault = new PublishConfig.Builder().setServiceName("XXX").build();
+        PublishConfig pubWithRanging = new PublishConfig.Builder().setServiceName(
+                "XXX").setRangingEnabled(true).build();
+        SubscribeConfig subDefault = new SubscribeConfig.Builder().setServiceName("XXX").build();
+        SubscribeConfig subWithMin = new SubscribeConfig.Builder().setServiceName(
+                "XXX").setMinDistanceMm(minDistanceMm).build();
+        SubscribeConfig subWithMax = new SubscribeConfig.Builder().setServiceName(
+                "XXX").setMaxDistanceMm(maxDistanceMm).build();
+        SubscribeConfig subWithMinMax = new SubscribeConfig.Builder().setServiceName(
+                "XXX").setMinDistanceMm(minDistanceMm).setMaxDistanceMm(maxDistanceMm).build();
+
+        mDut.publish(tid, pid, pubDefault);
+        mDut.publish(tid, pid, pubWithRanging);
+        mDut.subscribe(tid, pid, subDefault);
+        mDut.subscribe(tid, pid, subWithMin);
+        mDut.subscribe(tid, pid, subWithMax);
+        mDut.subscribe(tid, pid, subWithMinMax);
+
+        verify(mIWifiNanIfaceMock, times(2)).startPublishRequest(eq(tid), pubCaptor.capture());
+        verify(mIWifiNanIfaceMock, times(4)).startSubscribeRequest(eq(tid), subCaptor.capture());
+
+        NanPublishRequest halPubReq;
+        NanSubscribeRequest halSubReq;
+
+        // pubDefault
+        halPubReq = pubCaptor.getAllValues().get(0);
+        collector.checkThat("pubDefault.baseConfigs.sessionId", pid,
+                equalTo(halPubReq.baseConfigs.sessionId));
+        collector.checkThat("pubDefault.baseConfigs.rangingRequired", false,
+                equalTo(halPubReq.baseConfigs.rangingRequired));
+
+        // pubWithRanging
+        halPubReq = pubCaptor.getAllValues().get(1);
+        collector.checkThat("pubWithRanging.baseConfigs.sessionId", pid,
+                equalTo(halPubReq.baseConfigs.sessionId));
+        collector.checkThat("pubWithRanging.baseConfigs.rangingRequired", true,
+                equalTo(halPubReq.baseConfigs.rangingRequired));
+
+        // subDefault
+        halSubReq = subCaptor.getAllValues().get(0);
+        collector.checkThat("subDefault.baseConfigs.sessionId", pid,
+                equalTo(halSubReq.baseConfigs.sessionId));
+        collector.checkThat("subDefault.baseConfigs.rangingRequired", false,
+                equalTo(halSubReq.baseConfigs.rangingRequired));
+
+        // subWithMin
+        halSubReq = subCaptor.getAllValues().get(1);
+        collector.checkThat("subWithMin.baseConfigs.sessionId", pid,
+                equalTo(halSubReq.baseConfigs.sessionId));
+        collector.checkThat("subWithMin.baseConfigs.rangingRequired", true,
+                equalTo(halSubReq.baseConfigs.rangingRequired));
+        collector.checkThat("subWithMin.baseConfigs.configRangingIndications",
+                NanRangingIndication.INGRESS_MET_MASK,
+                equalTo(halSubReq.baseConfigs.configRangingIndications));
+        collector.checkThat("subWithMin.baseConfigs.distanceIngressCm", minDistanceCm,
+                equalTo(halSubReq.baseConfigs.distanceIngressCm));
+
+        // subWithMax
+        halSubReq = subCaptor.getAllValues().get(2);
+        collector.checkThat("subWithMax.baseConfigs.sessionId", pid,
+                equalTo(halSubReq.baseConfigs.sessionId));
+        collector.checkThat("subWithMax.baseConfigs.rangingRequired", true,
+                equalTo(halSubReq.baseConfigs.rangingRequired));
+        collector.checkThat("subWithMax.baseConfigs.configRangingIndications",
+                NanRangingIndication.EGRESS_MET_MASK,
+                equalTo(halSubReq.baseConfigs.configRangingIndications));
+        collector.checkThat("subWithMin.baseConfigs.distanceEgressCm", maxDistanceCm,
+                equalTo(halSubReq.baseConfigs.distanceEgressCm));
+
+        // subWithMinMax
+        halSubReq = subCaptor.getAllValues().get(3);
+        collector.checkThat("subWithMinMax.baseConfigs.sessionId", pid,
+                equalTo(halSubReq.baseConfigs.sessionId));
+        collector.checkThat("subWithMinMax.baseConfigs.rangingRequired", true,
+                equalTo(halSubReq.baseConfigs.rangingRequired));
+        collector.checkThat("subWithMinMax.baseConfigs.configRangingIndications",
+                NanRangingIndication.INGRESS_MET_MASK | NanRangingIndication.EGRESS_MET_MASK,
+                equalTo(halSubReq.baseConfigs.configRangingIndications));
+        collector.checkThat("subWithMin.baseConfigs.distanceIngressCm", minDistanceCm,
+                equalTo(halSubReq.baseConfigs.distanceIngressCm));
+        collector.checkThat("subWithMin.baseConfigs.distanceEgressCm", maxDistanceCm,
+                equalTo(halSubReq.baseConfigs.distanceEgressCm));
+    }
+
     // utilities
 
     private void setPowerConfigurationParams(byte interactive5, byte interactive24, byte idle5,
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java
index ba984b8..122d6e6 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareNativeManagerTest.java
@@ -28,6 +28,7 @@
 import android.hardware.wifi.V1_0.IfaceType;
 import android.hardware.wifi.V1_0.WifiStatus;
 import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.os.Handler;
 
 import com.android.server.wifi.HalDeviceManager;
 
@@ -49,6 +50,7 @@
     @Mock private HalDeviceManager mHalDeviceManager;
     @Mock private WifiAwareNativeCallback mWifiAwareNativeCallback;
     @Mock private IWifiNanIface mWifiNanIfaceMock;
+    @Mock private Handler mHandlerMock;
     private ArgumentCaptor<HalDeviceManager.ManagerStatusListener> mManagerStatusListenerCaptor =
             ArgumentCaptor.forClass(HalDeviceManager.ManagerStatusListener.class);
     private ArgumentCaptor<HalDeviceManager.InterfaceDestroyedListener>
@@ -73,7 +75,7 @@
 
         mDut = new WifiAwareNativeManager(mWifiAwareStateManagerMock,
                 mHalDeviceManager, mWifiAwareNativeCallback);
-        mDut.start();
+        mDut.start(mHandlerMock);
 
         mInOrder = inOrder(mWifiAwareStateManagerMock, mHalDeviceManager);
     }
@@ -106,7 +108,7 @@
 
         mManagerStatusListenerCaptor.getValue().onStatusChanged();
         mInOrder.verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(
-                eq(IfaceType.NAN), mAvailListenerCaptor.capture(), any());
+                eq(IfaceType.NAN), mAvailListenerCaptor.capture(), any(Handler.class));
         mAvailListenerCaptor.getValue().onAvailableForRequest();
 
         mInOrder.verify(mHalDeviceManager).createNanIface(
@@ -137,7 +139,7 @@
 
         mManagerStatusListenerCaptor.getValue().onStatusChanged();
         mInOrder.verify(mHalDeviceManager).registerInterfaceAvailableForRequestListener(
-                eq(IfaceType.NAN), mAvailListenerCaptor.capture(), any());
+                eq(IfaceType.NAN), mAvailListenerCaptor.capture(), any(Handler.class));
         mAvailListenerCaptor.getValue().onAvailableForRequest();
 
         mInOrder.verify(mHalDeviceManager).createNanIface(
@@ -147,7 +149,7 @@
                 equalTo(mWifiNanIfaceMock));
 
         // 8 onDestroyed: disable usage
-        mDestroyedListenerCaptor.getValue().onDestroyed();
+        mDestroyedListenerCaptor.getValue().onDestroyed(new String());
 
         mInOrder.verify(mWifiAwareStateManagerMock).disableUsage();
         collector.checkThat("null interface", mDut.getWifiNanIface(), nullValue());
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareRttStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareRttStateManagerTest.java
deleted file mode 100644
index 3df62f3..0000000
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareRttStateManagerTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2016 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.aware;
-
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.hamcrest.core.IsNull.nullValue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.net.wifi.IRttManager;
-import android.net.wifi.RttManager;
-import android.os.Handler;
-import android.os.Message;
-import android.os.test.TestLooper;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.internal.util.test.BidirectionalAsyncChannelServer;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ErrorCollector;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Unit test harness for WifiAwareManager class.
- */
-@SmallTest
-public class WifiAwareRttStateManagerTest {
-    private WifiAwareRttStateManager mDut;
-    private TestLooper mTestLooper;
-
-    @Mock
-    private Context mMockContext;
-
-    @Mock
-    private Handler mMockHandler;
-
-    @Mock
-    private IRttManager mMockRttService;
-
-    @Rule
-    public ErrorCollector collector = new ErrorCollector();
-
-    /**
-     * Initialize mocks.
-     */
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mDut = new WifiAwareRttStateManager();
-        mTestLooper = new TestLooper();
-        BidirectionalAsyncChannelServer server = new BidirectionalAsyncChannelServer(
-                mMockContext, mTestLooper.getLooper(), mMockHandler);
-        when(mMockRttService.getMessenger(null, new int[1])).thenReturn(server.getMessenger());
-
-        mDut.startWithRttService(mMockContext, mTestLooper.getLooper(), mMockRttService);
-    }
-
-    /**
-     * Validates that startRanging flow works: (1) start ranging, (2) get success callback - pass
-     * to client (while nulling BSSID info), (3) get fail callback - ignored (since client
-     * cleaned-out after first callback).
-     */
-    @Test
-    public void testStartRanging() throws Exception {
-        final int rangingId = 1234;
-        WifiAwareClientState mockClient = mock(WifiAwareClientState.class);
-        RttManager.RttParams[] params = new RttManager.RttParams[1];
-        params[0] = new RttManager.RttParams();
-        RttManager.ParcelableRttResults results =
-                new RttManager.ParcelableRttResults(new RttManager.RttResult[2]);
-        results.mResults[0] = new RttManager.RttResult();
-        results.mResults[0].bssid = "something non-null";
-        results.mResults[1] = new RttManager.RttResult();
-        results.mResults[1].bssid = "really really non-null";
-
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        ArgumentCaptor<RttManager.ParcelableRttResults> rttResultsCaptor =
-                ArgumentCaptor.forClass(RttManager.ParcelableRttResults.class);
-
-        InOrder inOrder = inOrder(mMockHandler, mockClient);
-
-        // (1) start ranging
-        mDut.startRanging(rangingId, mockClient, params);
-        mTestLooper.dispatchAll();
-        inOrder.verify(mMockHandler).handleMessage(messageCaptor.capture());
-        Message msg = messageCaptor.getValue();
-        collector.checkThat("msg.what=RttManager.CMD_OP_START_RANGING", msg.what,
-                equalTo(RttManager.CMD_OP_START_RANGING));
-        collector.checkThat("rangingId", msg.arg2, equalTo(rangingId));
-        collector.checkThat("RTT params", ((RttManager.ParcelableRttParams) msg.obj).mParams,
-                equalTo(params));
-
-        // (2) get success callback - pass to client
-        Message successMessage = Message.obtain();
-        successMessage.what = RttManager.CMD_OP_SUCCEEDED;
-        successMessage.arg2 = rangingId;
-        successMessage.obj = results;
-        msg.replyTo.send(successMessage);
-        mTestLooper.dispatchAll();
-        inOrder.verify(mockClient).onRangingSuccess(eq(rangingId), rttResultsCaptor.capture());
-        collector.checkThat("ParcelableRttResults object", results,
-                equalTo(rttResultsCaptor.getValue()));
-        collector.checkThat("RttResults[0].bssid null",
-                rttResultsCaptor.getValue().mResults[0].bssid, nullValue());
-        collector.checkThat("RttResults[1].bssid null",
-                rttResultsCaptor.getValue().mResults[1].bssid, nullValue());
-
-        // (3) get fail callback - ignored
-        Message failMessage = Message.obtain();
-        failMessage.what = RttManager.CMD_OP_ABORTED;
-        failMessage.arg2 = rangingId;
-        msg.replyTo.send(failMessage);
-        mTestLooper.dispatchAll();
-
-        verifyNoMoreInteractions(mMockHandler, mockClient);
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java
index d666262..f323110 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareServiceImplTest.java
@@ -16,28 +16,31 @@
 
 package com.android.server.wifi.aware;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.net.wifi.RttManager;
 import android.net.wifi.aware.Characteristics;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
 import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -52,6 +55,9 @@
 import org.mockito.MockitoAnnotations;
 
 import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 /**
  * Unit test harness for WifiAwareStateManager.
@@ -82,8 +88,6 @@
     @Mock private WifiAwareMetrics mAwareMetricsMock;
     @Mock private WifiPermissionsWrapper mPermissionsWrapperMock;
 
-    private HandlerThread mHandlerThread;
-
     /**
      * Using instead of spy to avoid native crash failures - possibly due to
      * spy's copying of state.
@@ -510,63 +514,47 @@
                 0);
     }
 
-    /**
-     * Validate startRanging() - correct pass-through args
-     */
     @Test
-    public void testStartRanging() {
-        int clientId = doConnect();
-        int sessionId = 65345;
-        RttManager.ParcelableRttParams params =
-                new RttManager.ParcelableRttParams(new RttManager.RttParams[1]);
-
-        ArgumentCaptor<RttManager.RttParams[]> paramsCaptor =
-                ArgumentCaptor.forClass(RttManager.RttParams[].class);
-
-        int rangingId = mDut.startRanging(clientId, sessionId, params);
-
-        verify(mAwareStateManagerMock).startRanging(eq(clientId), eq(sessionId),
-                paramsCaptor.capture(), eq(rangingId));
-
-        assertArrayEquals(paramsCaptor.getValue(), params.mParams);
-    }
-
-    /**
-     * Validates that sequential startRanging() calls return increasing ranging IDs.
-     */
-    @Test
-    public void testRangingIdIncrementing() {
-        int loopCount = 100;
-        int clientId = doConnect();
-        int sessionId = 65345;
-        RttManager.ParcelableRttParams params =
-                new RttManager.ParcelableRttParams(new RttManager.RttParams[1]);
-
-        int prevRangingId = 0;
-        for (int i = 0; i < loopCount; ++i) {
-            int rangingId = mDut.startRanging(clientId, sessionId, params);
-            if (i != 0) {
-                assertTrue("Client ID incrementing", rangingId > prevRangingId);
+    public void testRequestMacAddress() {
+        int uid = 1005;
+        List<Integer> list = new ArrayList<>();
+        IWifiAwareMacAddressProvider callback = new IWifiAwareMacAddressProvider() { // dummy
+            @Override
+            public void macAddress(Map peerIdToMacMap) throws RemoteException {
+                // empty
             }
-            prevRangingId = rangingId;
-        }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        };
+
+        mDut.requestMacAddresses(uid, list, callback);
+
+        verify(mAwareStateManagerMock).requestMacAddresses(uid, list, callback);
     }
 
-    /**
-     * Validates that startRanging() requires a non-empty list
+    @Test(expected = SecurityException.class)
+    public void testRequestMacAddressWithoutPermission() {
+        doThrow(new SecurityException()).when(mContextMock).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.NETWORK_STACK), anyString());
+
+        mDut.requestMacAddresses(1005, new ArrayList<>(), new IWifiAwareMacAddressProvider() {
+            @Override
+            public void macAddress(Map peerIdToMacMap) throws RemoteException {
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        });
+    }
+
+    /*
+     * Utilities
      */
-    @Test(expected = IllegalArgumentException.class)
-    public void testStartRangingZeroArgs() {
-        int clientId = doConnect();
-        int sessionId = 65345;
-        RttManager.ParcelableRttParams params =
-                new RttManager.ParcelableRttParams(new RttManager.RttParams[0]);
-
-        ArgumentCaptor<RttManager.RttParams[]> paramsCaptor =
-                ArgumentCaptor.forClass(RttManager.RttParams[].class);
-
-        int rangingId = mDut.startRanging(clientId, sessionId, params);
-    }
 
     /*
      * Tests of internal state of WifiAwareServiceImpl: very limited (not usually
@@ -574,7 +562,6 @@
      * appropriately. Alternatively would cause issues with memory leaks or
      * information leak between sessions.
      */
-
     private void validateInternalStateCleanedUp(int clientId) throws Exception {
         int uidEntry = getInternalStateUid(clientId);
         assertEquals(-1, uidEntry);
@@ -583,17 +570,13 @@
         assertEquals(null, dr);
     }
 
-    /*
-     * Utilities
-     */
-
     private void doBadPublishConfiguration(String serviceName, byte[] ssi, byte[] matchFilter)
             throws IllegalArgumentException {
         // using the hidden constructor since may be passing invalid parameters which would be
         // caught by the Builder. Want to test whether service side will catch invalidly
         // constructed configs.
         PublishConfig publishConfig = new PublishConfig(serviceName.getBytes(), ssi, matchFilter,
-                PublishConfig.PUBLISH_TYPE_UNSOLICITED, 0, true);
+                PublishConfig.PUBLISH_TYPE_UNSOLICITED, 0, true, false);
         int clientId = doConnect();
         IWifiAwareDiscoverySessionCallback mockCallback = mock(
                 IWifiAwareDiscoverySessionCallback.class);
@@ -609,7 +592,7 @@
         // caught by the Builder. Want to test whether service side will catch invalidly
         // constructed configs.
         SubscribeConfig subscribeConfig = new SubscribeConfig(serviceName.getBytes(), ssi,
-                matchFilter, SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE, 0, true);
+                matchFilter, SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE, 0, true, false, 0, false, 0);
         int clientId = doConnect();
         IWifiAwareDiscoverySessionCallback mockCallback = mock(
                 IWifiAwareDiscoverySessionCallback.class);
diff --git a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
index faa87df..fb11789 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
@@ -20,6 +20,7 @@
 import static org.hamcrest.core.IsNull.notNullValue;
 import static org.hamcrest.core.IsNull.nullValue;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -45,19 +46,22 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.hardware.wifi.V1_0.NanRangingIndication;
 import android.hardware.wifi.V1_0.NanStatusType;
 import android.net.ConnectivityManager;
-import android.net.wifi.RttManager;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
 import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
 import android.net.wifi.aware.WifiAwareManager;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.IPowerManager;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -80,9 +84,11 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
@@ -99,7 +105,6 @@
     @Mock private WifiAwareNativeApi mMockNative;
     @Mock private Context mMockContext;
     @Mock private AppOpsManager mMockAppOpsManager;
-    @Mock private WifiAwareRttStateManager mMockAwareRttStateManager;
     @Mock private WifiAwareMetrics mAwareMetricsMock;
     @Mock private WifiPermissionsWrapper mPermissionsWrapperMock;
     TestAlarmManager mAlarmManager;
@@ -156,7 +161,7 @@
         mMockLooper.dispatchAll();
         verify(mMockContext).registerReceiver(bcastRxCaptor.capture(), any(IntentFilter.class));
         mPowerBcastReceiver = bcastRxCaptor.getValue();
-        installMocksInStateManager(mDut, mMockAwareRttStateManager, mMockAwareDataPathStatemanager);
+        installMocksInStateManager(mDut, mMockAwareDataPathStatemanager);
 
         when(mMockNative.enableAndConfigure(anyShort(), any(), anyBoolean(),
                 anyBoolean(), anyBoolean(), anyBoolean())).thenReturn(true);
@@ -198,6 +203,128 @@
     }
 
     /**
+     * Test the PeerHandle -> MAC address API:
+     * - Start up discovery of 2 sessions
+     * - Get multiple matches (PeerHandles)
+     * - Request translation as UID of sesssion #1 for PeerHandles of the same UID + of the other
+     *   discovery session (to which we shouldn't have access) + invalid PeerHandle.
+     * -> validate results
+     */
+    @Test
+    public void testRequestMacAddresses() throws Exception {
+        final int clientId1 = 1005;
+        final int clientId2 = 1006;
+        final int uid1 = 1000;
+        final int uid2 = 1001;
+        final int pid1 = 2000;
+        final int pid2 = 2001;
+        final String callingPackage = "com.google.somePackage";
+        final String serviceName = "some-service-name";
+        final byte subscribeId1 = 15;
+        final byte subscribeId2 = 16;
+        final int requestorIdBase = 22;
+        final byte[] peerMac1 = HexEncoding.decode("060708090A0B".toCharArray(), false);
+        final byte[] peerMac2 = HexEncoding.decode("010203040506".toCharArray(), false);
+        final byte[] peerMac3 = HexEncoding.decode("AABBCCDDEEFF".toCharArray(), false);
+        final int distance = 10;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .build();
+
+        IWifiAwareEventCallback mockCallback1 = mock(IWifiAwareEventCallback.class);
+        IWifiAwareEventCallback mockCallback2 = mock(IWifiAwareEventCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback1 = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+        IWifiAwareDiscoverySessionCallback mockSessionCallback2 = mock(
+                IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
+        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Integer> peerIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        InOrder inOrder = inOrder(mockCallback1, mockCallback2, mockSessionCallback1,
+                mockSessionCallback2, mMockNative);
+
+        // (0) enable
+        mDut.enableUsage();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
+        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
+        mMockLooper.dispatchAll();
+
+        // (1) connect 2 clients
+        mDut.connect(clientId1, uid1, pid1, callingPackage, mockCallback1, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(),
+                eq(configRequest), eq(false), eq(true), eq(true), eq(false));
+        mDut.onConfigSuccessResponse(transactionId.getValue());
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback1).onConnectSuccess(clientId1);
+
+        mDut.connect(clientId2, uid2, pid2, callingPackage, mockCallback2, configRequest, false);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback2).onConnectSuccess(clientId2);
+
+        // (2) subscribe both clients
+        mDut.subscribe(clientId1, subscribeConfig, mockSessionCallback1);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId1);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback1).onSessionStarted(sessionId.capture());
+
+        mDut.subscribe(clientId2, subscribeConfig, mockSessionCallback2);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
+                eq(subscribeConfig));
+        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId2);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback2).onSessionStarted(sessionId.capture());
+
+        // (3) 2 matches on session 1 (second one with distance), 1 match on session 2
+        mDut.onMatchNotification(subscribeId1, requestorIdBase, peerMac1, null, null, 0, 0);
+        mDut.onMatchNotification(subscribeId1, requestorIdBase + 1, peerMac2, null, null,
+                NanRangingIndication.INGRESS_MET_MASK, distance);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback1).onMatch(peerIdCaptor.capture(), isNull(),
+                isNull());
+        inOrder.verify(mockSessionCallback1).onMatchWithDistance(peerIdCaptor.capture(), isNull(),
+                isNull(), eq(distance));
+        int peerId1 = peerIdCaptor.getAllValues().get(0);
+        int peerId2 = peerIdCaptor.getAllValues().get(1);
+
+        mDut.onMatchNotification(subscribeId2, requestorIdBase + 2, peerMac3, null, null, 0, 0);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback2).onMatch(peerIdCaptor.capture(), isNull(), isNull());
+        int peerId3 = peerIdCaptor.getAllValues().get(0);
+
+        // request MAC addresses
+        List<Integer> request = new ArrayList<>();
+        request.add(peerId1);
+        request.add(peerId2);
+        request.add(peerId3); // for uid2: so should not be in results
+        request.add(peerId1 * 20 + peerId2 + peerId3); // garbage values != to any
+        Mutable<Map> response = new Mutable<>();
+        mDut.requestMacAddresses(uid1, request, new IWifiAwareMacAddressProvider() {
+            @Override
+            public void macAddress(Map peerIdToMacMap) throws RemoteException {
+                response.value = peerIdToMacMap;
+            }
+
+            @Override
+            public IBinder asBinder() {
+                return null;
+            }
+        });
+        mMockLooper.dispatchAll();
+
+        assertNotEquals("Non-null result", null, response.value);
+        assertEquals("Number of results", 2, response.value.size());
+        assertEquals("Results[peerId1]", peerMac1, response.value.get(peerId1));
+        assertEquals("Results[peerId2]", peerMac2, response.value.get(peerId2));
+    }
+
+    /**
      * Validate that Aware data-path interfaces are brought up and down correctly.
      */
     @Test
@@ -1240,7 +1367,7 @@
 
         // (2) match
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+                peerMatchFilter.getBytes(), 0, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
                 eq(peerMatchFilter.getBytes()));
@@ -1540,7 +1667,7 @@
                 eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+                peerMatchFilter.getBytes(), 0, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
         inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
@@ -1609,7 +1736,7 @@
                 eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+                peerMatchFilter.getBytes(), 0, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
         inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
@@ -1735,7 +1862,7 @@
                 eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+                peerMatchFilter.getBytes(), 0, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
         inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
@@ -1822,7 +1949,7 @@
                 eq(subscribeConfig));
         mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+                peerMatchFilter.getBytes(), 0, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
         inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
@@ -1913,7 +2040,7 @@
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
 
         // (2) match
-        mDut.onMatchNotification(subscribeId, requestorId, peerMac, null, null);
+        mDut.onMatchNotification(subscribeId, requestorId, peerMac, null, null, 0, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), isNull(), isNull());
 
@@ -2010,7 +2137,7 @@
         inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
 
         // (2) match
-        mDut.onMatchNotification(subscribeId, requestorId, peerMac, null, null);
+        mDut.onMatchNotification(subscribeId, requestorId, peerMac, null, null, 0, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), isNull(), isNull());
 
@@ -2143,7 +2270,7 @@
 
         // (2) match
         mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
+                peerMatchFilter.getBytes(), 0, 0);
         mMockLooper.dispatchAll();
         inOrder.verify(mockSessionCallback).onMatch(peerIdCaptor.capture(), eq(peerSsi.getBytes()),
                 eq(peerMatchFilter.getBytes()));
@@ -2296,88 +2423,6 @@
     }
 
     /**
-     * Validate that start ranging function fills-in correct MAC addresses for peer IDs and
-     * passed along to RTT module.
-     */
-    @Test
-    public void testStartRanging() throws Exception {
-        final int clientId = 1005;
-        final int uid = 1000;
-        final int pid = 2000;
-        final String callingPackage = "com.google.somePackage";
-        final byte subscribeId = 15;
-        final int requestorId = 22;
-        final byte[] peerMac = HexEncoding.decode("060708090A0B".toCharArray(), false);
-        final String peerSsi = "some peer ssi data";
-        final String peerMatchFilter = "filter binary array represented as string";
-        final int rangingId = 18423;
-        final RttManager.RttParams[] params = new RttManager.RttParams[2];
-
-        ConfigRequest configRequest = new ConfigRequest.Builder().build();
-        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
-
-        IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
-        IWifiAwareDiscoverySessionCallback mockSessionCallback = mock(
-                IWifiAwareDiscoverySessionCallback.class);
-
-        ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
-        ArgumentCaptor<Integer> sessionId = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<Integer> peerIdIdCaptor = ArgumentCaptor.forClass(Integer.class);
-        ArgumentCaptor<WifiAwareClientState> clientCaptor =
-                ArgumentCaptor.forClass(WifiAwareClientState.class);
-        ArgumentCaptor<RttManager.RttParams[]> rttParamsCaptor =
-                ArgumentCaptor.forClass(RttManager.RttParams[].class);
-
-        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mMockNative,
-                mMockAwareRttStateManager);
-
-        mDut.enableUsage();
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).getCapabilities(transactionId.capture());
-        mDut.onCapabilitiesUpdateResponse(transactionId.getValue(), getCapabilities());
-        mMockLooper.dispatchAll();
-
-        // (1) connect
-        mDut.connect(clientId, uid, pid, callingPackage, mockCallback, configRequest, false);
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).enableAndConfigure(transactionId.capture(), eq(configRequest),
-                eq(false), eq(true), eq(true), eq(false));
-        mDut.onConfigSuccessResponse(transactionId.getValue());
-        mMockLooper.dispatchAll();
-        inOrder.verify(mockCallback).onConnectSuccess(clientId);
-
-        // (2) subscribe & match
-        mDut.subscribe(clientId, subscribeConfig, mockSessionCallback);
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockNative).subscribe(transactionId.capture(), eq((byte) 0),
-                eq(subscribeConfig));
-        mDut.onSessionConfigSuccessResponse(transactionId.getValue(), false, subscribeId);
-        mDut.onMatchNotification(subscribeId, requestorId, peerMac, peerSsi.getBytes(),
-                peerMatchFilter.getBytes());
-        mMockLooper.dispatchAll();
-        inOrder.verify(mockSessionCallback).onSessionStarted(sessionId.capture());
-        inOrder.verify(mockSessionCallback).onMatch(peerIdIdCaptor.capture(),
-                eq(peerSsi.getBytes()), eq(peerMatchFilter.getBytes()));
-
-        // (3) start ranging: pass along a valid peer ID and an invalid one
-        params[0] = new RttManager.RttParams();
-        params[0].bssid = Integer.toString(peerIdIdCaptor.getValue());
-        params[1] = new RttManager.RttParams();
-        params[1].bssid = Integer.toString(peerIdIdCaptor.getValue() + 5);
-
-        mDut.startRanging(clientId, sessionId.getValue(), params, rangingId);
-        mMockLooper.dispatchAll();
-        inOrder.verify(mMockAwareRttStateManager).startRanging(eq(rangingId),
-                clientCaptor.capture(), rttParamsCaptor.capture());
-        collector.checkThat("RttParams[0].bssid", "06:07:08:09:0a:0b",
-                equalTo(rttParamsCaptor.getValue()[0].bssid));
-        collector.checkThat("RttParams[1].bssid", "", equalTo(rttParamsCaptor.getValue()[1].bssid));
-
-        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mMockNative,
-                mMockAwareRttStateManager);
-    }
-
-    /**
      * Test sequence of configuration: (1) config1, (2) config2 - incompatible,
      * (3) config3 - compatible with config1 (requiring upgrade), (4) disconnect
      * config3 (should get a downgrade), (5) disconnect config1 (should get a
@@ -2765,7 +2810,7 @@
         mDut.onMessageSendQueuedSuccessResponse(transactionIdConfig);
         mDut.onMessageSendQueuedFailResponse(transactionIdConfig, -1);
         mDut.onSessionConfigFailResponse(transactionIdConfig, false, -1);
-        mDut.onMatchNotification(-1, -1, new byte[0], new byte[0], new byte[0]);
+        mDut.onMatchNotification(-1, -1, new byte[0], new byte[0], new byte[0], 0, 0);
         mDut.onSessionTerminatedNotification(-1, -1, true);
         mDut.onSessionTerminatedNotification(-1, -1, false);
         mDut.onMessageReceivedNotification(-1, -1, new byte[0], new byte[0]);
@@ -3020,7 +3065,7 @@
         ArgumentCaptor<Short> transactionId = ArgumentCaptor.forClass(Short.class);
         IWifiAwareEventCallback mockCallback = mock(IWifiAwareEventCallback.class);
         InOrder inOrder = inOrder(mMockContext, mMockNativeManager, mMockNative, mockCallback);
-        inOrder.verify(mMockNativeManager).start();
+        inOrder.verify(mMockNativeManager).start(any(Handler.class));
 
         mDut.enableUsage();
         mMockLooper.dispatchAll();
@@ -3150,13 +3195,9 @@
     }
 
     private static void installMocksInStateManager(WifiAwareStateManager awareStateManager,
-            WifiAwareRttStateManager mockRtt, WifiAwareDataPathStateManager mockDpMgr)
+            WifiAwareDataPathStateManager mockDpMgr)
             throws Exception {
-        Field field = WifiAwareStateManager.class.getDeclaredField("mRtt");
-        field.setAccessible(true);
-        field.set(awareStateManager, mockRtt);
-
-        field = WifiAwareStateManager.class.getDeclaredField("mDataPathMgr");
+        Field field = WifiAwareStateManager.class.getDeclaredField("mDataPathMgr");
         field.setAccessible(true);
         field.set(awareStateManager, mockDpMgr);
     }
@@ -3256,5 +3297,17 @@
         cap.maxQueuedTransmitMessages = 6;
         return cap;
     }
+
+    private static class Mutable<E> {
+        public E value;
+
+        Mutable() {
+            value = null;
+        }
+
+        Mutable(E value) {
+            this.value = value;
+        }
+    }
 }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuNetworkConnectionTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuNetworkConnectionTest.java
new file mode 100644
index 0000000..c0d319f
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/OsuNetworkConnectionTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 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.hotspot2;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Handler;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.wifi.TestUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointProvisioner}.
+ */
+@SmallTest
+public class OsuNetworkConnectionTest {
+    private static final String TAG = "OsuNetworkConnectionTest";
+    private static final int ENABLE_LOGGING = 1;
+    private static final int DISABLE_LOGGING = 0;
+
+    private static final int TEST_NETWORK_ID = 6;
+    private static final String TEST_NAI = null;
+    private static final String TEST_NAI_OSEN = "access.test.com";
+    private static final WifiSsid TEST_SSID = WifiSsid.createFromAsciiEncoded("Test SSID");
+
+    private BroadcastReceiver mBroadcastReceiver;
+    private OsuNetworkConnection mNetworkConnection;
+    private TestLooper mLooper;
+    private Handler mHandler;
+
+    @Mock Context mContext;
+    @Mock WifiManager mWifiManager;
+    @Mock OsuNetworkConnection.Callbacks mNetworkCallbacks;
+    @Mock NetworkInfo mNwInfo;
+    @Mock WifiInfo mWifiInfo;
+    @Mock Network mCurrentNetwork;
+
+    ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor = ArgumentCaptor.forClass(
+            BroadcastReceiver.class);
+    ArgumentCaptor<IntentFilter> mIntentFilterCaptor = ArgumentCaptor.forClass(
+            IntentFilter.class);
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doReturn(mWifiManager).when(mContext)
+                .getSystemService(eq(Context.WIFI_SERVICE));
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        when(mWifiManager.enableNetwork(TEST_NETWORK_ID, true)).thenReturn(true);
+        when(mWifiManager.addNetwork(any(WifiConfiguration.class))).thenReturn(TEST_NETWORK_ID);
+        when(mWifiManager.getCurrentNetwork()).thenReturn(mCurrentNetwork);
+        when(mWifiInfo.getNetworkId()).thenReturn(TEST_NETWORK_ID);
+        mLooper = new TestLooper();
+        mHandler = new Handler(mLooper.getLooper());
+        mNetworkConnection = new OsuNetworkConnection(mContext);
+        mNetworkConnection.enableVerboseLogging(ENABLE_LOGGING);
+    }
+
+    /**
+     * Verify that the class registers for receiving the necessary broadcast intents upon init.
+     * Verify that the initialization only occurs once even if init() is called  multiple times.
+     */
+    @Test
+    public void verifyBroadcastIntentRegistration() {
+        mNetworkConnection.init(mHandler);
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                mIntentFilterCaptor.capture(), any(), eq(mHandler));
+        verify(mWifiManager).isWifiEnabled();
+        mLooper.dispatchAll();
+        IntentFilter intentFilter = mIntentFilterCaptor.getValue();
+        assertEquals(intentFilter.countActions(), 2);
+    }
+
+    /**
+     * Verifies that onWifiEnabled() callback is invoked when the relevant intent is
+     * received and the caller is subscribed to receive the callback.
+     */
+    @Test
+    public void verifyWifiStateCallbacks() {
+        when(mWifiManager.isWifiEnabled()).thenReturn(false);
+        mNetworkConnection.init(mHandler);
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                any(IntentFilter.class), any(), eq(mHandler));
+        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
+        mLooper.dispatchAll();
+        mNetworkConnection.setEventCallback(mNetworkCallbacks);
+        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext,
+                WifiManager.WIFI_STATE_ENABLED);
+        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext,
+                WifiManager.WIFI_STATE_DISABLED);
+        mNetworkConnection.setEventCallback(null);
+        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext,
+                WifiManager.WIFI_STATE_ENABLED);
+        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext,
+                WifiManager.WIFI_STATE_DISABLED);
+        verify(mNetworkCallbacks, times(1)).onWifiEnabled();
+        verify(mNetworkCallbacks, times(1)).onWifiDisabled();
+    }
+
+    /**
+     * Verifies that connect() API returns false when Wifi is not enabled
+     */
+    @Test
+    public void verifyNetworkConnectionWhenWifiIsDisabled() {
+        when(mWifiManager.isWifiEnabled()).thenReturn(false);
+        mNetworkConnection.init(mHandler);
+        assertEquals(false, mNetworkConnection.connect(TEST_SSID, TEST_NAI));
+    }
+
+    /**
+     * Verifies that connect() API returns false when OSU AP is a part of an OSEN
+     */
+    @Test
+    public void verifyOSENUnsupported() {
+        mNetworkConnection.init(mHandler);
+        assertEquals(false, mNetworkConnection.connect(TEST_SSID, TEST_NAI_OSEN));
+    }
+
+    /**
+     * Verifies that connect() API returns false when WifiManager's addNetwork()
+     * returns an invalid network ID
+     */
+    @Test
+    public void verifyNetworkConnectionWhenAddNetworkFails() {
+        when(mWifiManager.addNetwork(any(WifiConfiguration.class))).thenReturn(-1);
+        mNetworkConnection.init(mHandler);
+        assertEquals(false, mNetworkConnection.connect(TEST_SSID, TEST_NAI));
+    }
+
+    /**
+     * Verifies that connect() API returns false when WifiManager's enableNetwork()
+     * fails for the given network ID corresponding to the OSU AP
+     */
+    @Test
+    public void verifyNetworkConnectionWhenEnableNetworkFails() {
+        when(mWifiManager.enableNetwork(TEST_NETWORK_ID, true)).thenReturn(false);
+        mNetworkConnection.init(mHandler);
+        assertEquals(false, mNetworkConnection.connect(TEST_SSID, TEST_NAI));
+        verify(mWifiManager).removeNetwork(TEST_NETWORK_ID);
+    }
+
+    /**
+     * Verifies that network state callbacks are invoked when the NETWORK_STATE_CHANGED intent
+     * is received and when WifiManager has successfully requested connection to the OSU AP.
+     */
+    @Test
+    public void verifyNetworkCallbackInvokedWhenRegistered() {
+        mNetworkConnection.init(mHandler);
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                any(IntentFilter.class), any(), eq(mHandler));
+        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
+
+        mNetworkConnection.setEventCallback(mNetworkCallbacks);
+        assertEquals(true, mNetworkConnection.connect(TEST_SSID, TEST_NAI));
+        mLooper.dispatchAll();
+
+        when(mNwInfo.getDetailedState()).thenReturn(NetworkInfo.DetailedState.CONNECTED);
+        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
+                mNwInfo, mWifiInfo);
+        verify(mNetworkCallbacks).onConnected(mCurrentNetwork);
+
+        when(mNwInfo.getDetailedState()).thenReturn(NetworkInfo.DetailedState.DISCONNECTED);
+        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext, mNwInfo, mWifiInfo);
+        verify(mNetworkCallbacks).onDisconnected();
+        verify(mWifiManager).removeNetwork(TEST_NETWORK_ID);
+    }
+
+    /**
+     * Verifies that the onConnected() callback is not invoked when the Network State Changed
+     * intent is called when WifiManager has successfully connected to a network that's not the
+     * OSU AP.
+     */
+    @Test
+    public void verifyNetworkDisconnectedCallbackConnectedToAnotherNetwork() {
+        when(mWifiInfo.getNetworkId()).thenReturn(TEST_NETWORK_ID + 1);
+        when(mNwInfo.getDetailedState()).thenReturn(NetworkInfo.DetailedState.CONNECTED);
+        mNetworkConnection.init(mHandler);
+        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+                any(IntentFilter.class), any(), eq(mHandler));
+        mLooper.dispatchAll();
+
+        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
+        mNetworkConnection.setEventCallback(mNetworkCallbacks);
+        assertEquals(true, mNetworkConnection.connect(TEST_SSID, TEST_NAI));
+        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
+                mNwInfo, mWifiInfo);
+        verify(mNetworkCallbacks, never()).onConnected(any(Network.class));
+        verify(mWifiManager).removeNetwork(TEST_NETWORK_ID);
+    }
+
+    /**
+     * Verifies that WifiManager's removeNetwork() is called when disconnectIfNeeded() is called
+     * on the OSU AP's network ID.
+     */
+    @Test
+    public void verifyNetworkTearDown() {
+        mNetworkConnection.init(mHandler);
+        assertEquals(true, mNetworkConnection.connect(TEST_SSID, TEST_NAI));
+        mNetworkConnection.disconnectIfNeeded();
+        verify(mWifiManager).removeNetwork(TEST_NETWORK_ID);
+    }
+}
+
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
index 01566c2..e0e6df1 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
@@ -33,6 +33,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
 import static org.mockito.Mockito.anyMap;
 import static org.mockito.Mockito.doThrow;
@@ -52,12 +53,16 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiSsid;
+import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
+import android.os.Looper;
 import android.os.UserHandle;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
 import android.util.Pair;
@@ -101,7 +106,7 @@
 public class PasspointManagerTest {
     private static final long BSSID = 0x112233445566L;
     private static final String ICON_FILENAME = "test";
-    private static final String TEST_FQDN = "test1.test.com";
+    private static final String  TEST_FQDN = "test1.test.com";
     private static final String TEST_FRIENDLY_NAME = "friendly name";
     private static final String TEST_REALM = "realm.test.com";
     private static final String TEST_IMSI = "1234*";
@@ -115,6 +120,7 @@
     private static final ANQPNetworkKey TEST_ANQP_KEY = ANQPNetworkKey.buildKey(
             TEST_SSID, TEST_BSSID, TEST_HESSID, TEST_ANQP_DOMAIN_ID);
     private static final int TEST_CREATOR_UID = 1234;
+    private static final int TEST_UID = 1500;
 
     @Mock Context mContext;
     @Mock WifiNative mWifiNative;
@@ -130,6 +136,11 @@
     @Mock WifiConfigStore mWifiConfigStore;
     @Mock PasspointConfigStoreData.DataSource mDataSource;
     @Mock WifiMetrics mWifiMetrics;
+    @Mock OsuNetworkConnection mOsuNetworkConnection;
+    @Mock PasspointProvisioner mPasspointProvisioner;
+    @Mock IProvisioningCallback mCallback;
+
+    TestLooper mLooper;
     PasspointManager mManager;
 
     /** Sets up test. */
@@ -140,6 +151,10 @@
         when(mObjectFactory.makeANQPRequestManager(any(), eq(mClock)))
                 .thenReturn(mAnqpRequestManager);
         when(mObjectFactory.makeCertificateVerifier()).thenReturn(mCertVerifier);
+        when(mObjectFactory.makeOsuNetworkConnection(any(Context.class)))
+                .thenReturn(mOsuNetworkConnection);
+        when(mObjectFactory.makePasspointProvisioner(any(Context.class),
+                any(OsuNetworkConnection.class))).thenReturn(mPasspointProvisioner);
         mManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock,
                 mSimAccessor, mObjectFactory, mWifiConfigManager, mWifiConfigStore, mWifiMetrics);
         ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks =
@@ -152,6 +167,7 @@
                 any(WifiKeyStore.class), any(SIMAccessor.class), dataSource.capture());
         mCallbacks = callbacks.getValue();
         mDataSource = dataSource.getValue();
+        mLooper = new TestLooper();
     }
 
     /**
@@ -208,10 +224,10 @@
      *
      * @return {@link PasspointConfiguration}
      */
-    private PasspointConfiguration createTestConfigWithUserCredential() {
+    private PasspointConfiguration createTestConfigWithUserCredential(String fqdn) {
         PasspointConfiguration config = new PasspointConfiguration();
         HomeSp homeSp = new HomeSp();
-        homeSp.setFqdn(TEST_FQDN);
+        homeSp.setFqdn(fqdn);
         homeSp.setFriendlyName(TEST_FRIENDLY_NAME);
         config.setHomeSp(homeSp);
         Credential credential = new Credential();
@@ -254,8 +270,8 @@
      *
      * @return {@link PasspointProvider}
      */
-    private PasspointProvider addTestProvider() {
-        PasspointConfiguration config = createTestConfigWithUserCredential();
+    private PasspointProvider addTestProvider(String fqdn) {
+        PasspointConfiguration config = createTestConfigWithUserCredential(fqdn);
         PasspointProvider provider = createMockProvider(config);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID))).thenReturn(provider);
@@ -437,7 +453,7 @@
      */
     @Test
     public void addProviderWithInvalidCredential() throws Exception {
-        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointConfiguration config = createTestConfigWithUserCredential(TEST_FQDN);
         // EAP-TLS not allowed for user credential.
         config.getCredential().getUserCredential().setEapType(EAPConstants.EAP_TLS);
         assertFalse(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
@@ -452,7 +468,7 @@
      */
     @Test
     public void addRemoveProviderWithValidUserCredential() throws Exception {
-        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointConfiguration config = createTestConfigWithUserCredential(TEST_FQDN);
         PasspointProvider provider = createMockProvider(config);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
                 eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID))).thenReturn(provider);
@@ -555,7 +571,7 @@
 
         // Add another provider with the same base domain as the existing provider.
         // This should replace the existing provider with the new configuration.
-        PasspointConfiguration newConfig = createTestConfigWithUserCredential();
+        PasspointConfiguration newConfig = createTestConfigWithUserCredential(TEST_FQDN);
         PasspointProvider newProvider = createMockProvider(newConfig);
         when(mObjectFactory.makePasspointProvider(eq(newConfig), eq(mWifiKeyStore),
                 eq(mSimAccessor), anyLong(), eq(TEST_CREATOR_UID))).thenReturn(newProvider);
@@ -580,7 +596,7 @@
      */
     @Test
     public void addProviderOnKeyInstallationFailiure() throws Exception {
-        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointConfiguration config = createTestConfigWithUserCredential(TEST_FQDN);
         PasspointProvider provider = mock(PasspointProvider.class);
         when(provider.installCertsAndKeys()).thenReturn(false);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
@@ -597,7 +613,7 @@
      */
     @Test
     public void addProviderWithInvalidCaCert() throws Exception {
-        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointConfiguration config = createTestConfigWithUserCredential(TEST_FQDN);
         doThrow(new GeneralSecurityException())
                 .when(mCertVerifier).verifyCaCert(any(X509Certificate.class));
         assertFalse(mManager.addOrUpdateProvider(config, TEST_CREATOR_UID));
@@ -613,7 +629,7 @@
      */
     @Test
     public void addProviderWithR2Config() throws Exception {
-        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointConfiguration config = createTestConfigWithUserCredential(TEST_FQDN);
         config.setUpdateIdentifier(1);
         PasspointProvider provider = createMockProvider(config);
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
@@ -654,7 +670,7 @@
      */
     @Test
     public void matchProviderWithAnqpCacheMissed() throws Exception {
-        addTestProvider();
+        addTestProvider(TEST_FQDN);
 
         when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(null);
         assertNull(mManager.matchProvider(createTestScanResult()));
@@ -670,7 +686,7 @@
      */
     @Test
     public void matchProviderAsHomeProvider() throws Exception {
-        PasspointProvider provider = addTestProvider();
+        PasspointProvider provider = addTestProvider(TEST_FQDN);
         ANQPData entry = new ANQPData(mClock, null);
 
         when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
@@ -688,7 +704,7 @@
      */
     @Test
     public void matchProviderAsRoamingProvider() throws Exception {
-        PasspointProvider provider = addTestProvider();
+        PasspointProvider provider = addTestProvider(TEST_FQDN);
         ANQPData entry = new ANQPData(mClock, null);
 
         when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
@@ -706,7 +722,7 @@
      */
     @Test
     public void matchProviderWithNoMatch() throws Exception {
-        PasspointProvider provider = addTestProvider();
+        PasspointProvider provider = addTestProvider(TEST_FQDN);
         ANQPData entry = new ANQPData(mClock, null);
 
         when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
@@ -761,7 +777,7 @@
      */
     @Test
     public void getMatchingWifiConfigForHomeProviderAP() throws Exception {
-        PasspointProvider provider = addTestProvider();
+        PasspointProvider provider = addTestProvider(TEST_FQDN);
         ANQPData entry = new ANQPData(mClock, null);
 
         when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
@@ -780,7 +796,7 @@
      */
     @Test
     public void getMatchingWifiConfigForRoamingProviderAP() throws Exception {
-        PasspointProvider provider = addTestProvider();
+        PasspointProvider provider = addTestProvider(TEST_FQDN);
         ANQPData entry = new ANQPData(mClock, null);
 
         when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
@@ -799,7 +815,7 @@
      */
     @Test
     public void getMatchingWifiConfigWithNoMatchingProvider() throws Exception {
-        PasspointProvider provider = addTestProvider();
+        PasspointProvider provider = addTestProvider(TEST_FQDN);
         ANQPData entry = new ANQPData(mClock, null);
 
         when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
@@ -859,6 +875,97 @@
     }
 
     /**
+     * Verify that an expected set of {@link WifiConfiguration} will be returned when a
+     * {@link ScanResult} is matched to a provider.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getAllMatchingWifiConfigsForProviderAP() throws Exception {
+        PasspointProvider providerHome = addTestProvider(TEST_FQDN + 0);
+        PasspointProvider providerRoaming = addTestProvider(TEST_FQDN + 1);
+        PasspointProvider providerNone = addTestProvider(TEST_FQDN + 2);
+        ANQPData entry = new ANQPData(mClock, null);
+
+        when(mAnqpCache.getEntry(TEST_ANQP_KEY)).thenReturn(entry);
+
+        when(providerHome.match(anyMap())).thenReturn(PasspointMatch.HomeProvider);
+        when(providerRoaming.match(anyMap())).thenReturn(PasspointMatch.RoamingProvider);
+        when(providerNone.match(anyMap())).thenReturn(PasspointMatch.None);
+
+        when(providerHome.getWifiConfig()).thenReturn(new WifiConfiguration());
+        when(providerRoaming.getWifiConfig()).thenReturn(new WifiConfiguration());
+        when(providerNone.getWifiConfig()).thenReturn(new WifiConfiguration());
+
+        List<WifiConfiguration> configs = mManager.getAllMatchingWifiConfigs(
+                createTestScanResult());
+
+        assertEquals(2, configs.size());
+        int observedHome = 0;
+        int observedRoaming = 0;
+        for (WifiConfiguration config : configs) {
+            assertEquals(ScanResultUtil.createQuotedSSID(TEST_SSID), config.SSID);
+            if (config.isHomeProviderNetwork) {
+                observedHome++;
+            } else {
+                observedRoaming++;
+            }
+        }
+        assertEquals(1, observedHome);
+        assertEquals(1, observedRoaming);
+    }
+
+    /**
+     * Verify that an empty list will be returned when trying to get all matching
+     * {@link WifiConfiguration} for a {@code null} {@link ScanResult}.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getAllMatchingWifiConfigsWithNullScanResult() throws Exception {
+        assertEquals(0, mManager.getAllMatchingWifiConfigs(null).size());
+    }
+
+    /**
+     * Verify that an empty list will be returned when trying to get a all matching
+     * {@link WifiConfiguration} for a {@link ScanResult} with a {@code null} BSSID.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getAllMatchingWifiConfigWithNullBSSID() throws Exception {
+        ScanResult scanResult = createTestScanResult();
+        scanResult.BSSID = null;
+        assertEquals(0, mManager.getAllMatchingWifiConfigs(scanResult).size());
+    }
+
+    /**
+     * Verify that an empty list will be returned when trying to get all matching
+     * {@link WifiConfiguration} for a {@link ScanResult} with an invalid BSSID.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getAllMatchingWifiConfigWithInvalidBSSID() throws Exception {
+        ScanResult scanResult = createTestScanResult();
+        scanResult.BSSID = "asdfdasfas";
+        assertEquals(0, mManager.getAllMatchingWifiConfigs(scanResult).size());
+    }
+
+    /**
+     * Verify that an empty list will be returned when trying to get all matching
+     * {@link WifiConfiguration} for a non-Passpoint AP.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getAllMatchingWifiConfigForNonPasspointAP() throws Exception {
+        ScanResult scanResult = createTestScanResult();
+        scanResult.flags = 0;
+        assertEquals(0, mManager.getAllMatchingWifiConfigs(scanResult).size());
+    }
+
+    /**
      * Verify that an empty list will be returned when retrieving OSU providers for an AP with
      * null scan result.
      *
@@ -954,7 +1061,7 @@
     @Test
     public void verifyProvidersAfterDataSourceUpdate() throws Exception {
         // Update the provider list in the data source.
-        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointConfiguration config = createTestConfigWithUserCredential(TEST_FQDN);
         PasspointProvider provider = createMockProvider(config);
         List<PasspointProvider> providers = new ArrayList<>();
         providers.add(provider);
@@ -978,7 +1085,7 @@
         assertEquals(providerIndex, mDataSource.getProviderIndex());
 
         // Add a provider.
-        PasspointConfiguration config = createTestConfigWithUserCredential();
+        PasspointConfiguration config = createTestConfigWithUserCredential(TEST_FQDN);
         PasspointProvider provider = createMockProvider(config);
         // Verify the provider ID used to create the new provider.
         when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
@@ -1225,7 +1332,7 @@
      */
     @Test
     public void providerNetworkConnectedFirstTime() throws Exception {
-        PasspointProvider provider = addTestProvider();
+        PasspointProvider provider = addTestProvider(TEST_FQDN);
         when(provider.getHasEverConnected()).thenReturn(false);
         mManager.onPasspointNetworkConnected(TEST_FQDN);
         verify(provider).setHasEverConnected(eq(true));
@@ -1240,7 +1347,7 @@
      */
     @Test
     public void providerNetworkConnectedNotFirstTime() throws Exception {
-        PasspointProvider provider = addTestProvider();
+        PasspointProvider provider = addTestProvider(TEST_FQDN);
         when(provider.getHasEverConnected()).thenReturn(true);
         mManager.onPasspointNetworkConnected(TEST_FQDN);
         verify(provider, never()).setHasEverConnected(anyBoolean());
@@ -1254,7 +1361,7 @@
      */
     @Test
     public void updateMetrics() throws Exception {
-        PasspointProvider provider = addTestProvider();
+        PasspointProvider provider = addTestProvider(TEST_FQDN);
 
         // Provider have not provided a successful network connection.
         int expectedInstalledProviders = 1;
@@ -1273,4 +1380,18 @@
         verify(mWifiMetrics).updateSavedPasspointProfiles(
                 eq(expectedInstalledProviders), eq(expectedConnectedProviders));
     }
+    /**
+     * Verify Passpoint Manager's provisioning APIs by invoking methods in PasspointProvisioner for
+     * initiailization and provisioning a provider.
+     */
+    @Test
+    public void verifyPasspointProvisioner() {
+        mManager.initializeProvisioner(mLooper.getLooper());
+        verify(mPasspointProvisioner).init(any(Looper.class));
+        when(mPasspointProvisioner.startSubscriptionProvisioning(anyInt(), any(OsuProvider.class),
+                any(IProvisioningCallback.class))).thenReturn(true);
+        OsuProvider osuProvider = PasspointProvisioningTestUtil.generateOsuProvider(true);
+        assertEquals(true,
+                mManager.startSubscriptionProvisioning(TEST_UID, osuProvider, mCallback));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
new file mode 100644
index 0000000..4583531
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointProvisionerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 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.hotspot2;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.OsuProvider;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointProvisioner}.
+ */
+@SmallTest
+public class PasspointProvisionerTest {
+    private static final String TAG = "PasspointProvisionerTest";
+
+    private static final int TEST_UID = 1500;
+
+    private PasspointProvisioner mPasspointProvisioner;
+    private TestLooper mLooper = new TestLooper();
+
+    @Mock Context mContext;
+    @Mock WifiManager mWifiManager;
+    @Mock IProvisioningCallback mCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mWifiManager.isWifiEnabled()).thenReturn(true);
+        doReturn(mWifiManager).when(mContext)
+                .getSystemService(eq(Context.WIFI_SERVICE));
+        OsuNetworkConnection osuNetworkConnection = new OsuNetworkConnection(mContext);
+        mPasspointProvisioner = new PasspointProvisioner(mContext, osuNetworkConnection);
+    }
+
+    /**
+     * Verifies that initialization is required before starting subscription
+     * provisioning with a provider
+     */
+    @Test
+    public void verifyInitBeforeStartProvisioning() {
+        OsuProvider osuProvider = PasspointProvisioningTestUtil.generateOsuProvider(true);
+        mPasspointProvisioner.init(mLooper.getLooper());
+        assertTrue(mPasspointProvisioner.startSubscriptionProvisioning(
+                TEST_UID, osuProvider, mCallback));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java b/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java
new file mode 100644
index 0000000..5043c85
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.rtt;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.hardware.wifi.V1_0.IWifiRttController;
+import android.hardware.wifi.V1_0.RttConfig;
+import android.hardware.wifi.V1_0.RttPeerType;
+import android.hardware.wifi.V1_0.RttResult;
+import android.hardware.wifi.V1_0.RttStatus;
+import android.hardware.wifi.V1_0.RttType;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.net.MacAddress;
+import android.net.wifi.rtt.RangingRequest;
+
+import com.android.server.wifi.HalDeviceManager;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test harness for the RttNative class.
+ */
+public class RttNativeTest {
+    private RttNative mDut;
+
+    private ArgumentCaptor<ArrayList> mRttConfigCaptor = ArgumentCaptor.forClass(ArrayList.class);
+    private ArgumentCaptor<List> mRttResultCaptor = ArgumentCaptor.forClass(List.class);
+    private ArgumentCaptor<HalDeviceManager.ManagerStatusListener> mHdmStatusListener =
+            ArgumentCaptor.forClass(HalDeviceManager.ManagerStatusListener.class);
+
+    @Rule
+    public ErrorCollector collector = new ErrorCollector();
+
+    @Mock
+    public RttServiceImpl mockRttServiceImpl;
+
+    @Mock
+    public HalDeviceManager mockHalDeviceManager;
+
+    @Mock
+    public IWifiRttController mockRttController;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mockHalDeviceManager.isStarted()).thenReturn(true);
+        when(mockHalDeviceManager.createRttController()).thenReturn(mockRttController);
+
+        WifiStatus status = new WifiStatus();
+        status.code = WifiStatusCode.SUCCESS;
+        when(mockRttController.registerEventCallback(any())).thenReturn(status);
+        when(mockRttController.rangeRequest(anyInt(), any(ArrayList.class))).thenReturn(status);
+        when(mockRttController.rangeCancel(anyInt(), any(ArrayList.class))).thenReturn(status);
+
+        mDut = new RttNative(mockRttServiceImpl, mockHalDeviceManager);
+        mDut.start();
+        verify(mockHalDeviceManager).registerStatusListener(mHdmStatusListener.capture(), any());
+        verify(mockRttController).registerEventCallback(any());
+        verify(mockRttServiceImpl).enable();
+        assertTrue(mDut.isReady());
+    }
+
+    /**
+     * Validate successful ranging flow.
+     */
+    @Test
+    public void testRangeRequest() throws Exception {
+        int cmdId = 55;
+        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
+
+        // (1) issue range request
+        mDut.rangeRequest(cmdId, request);
+
+        // (2) verify HAL call and parameters
+        verify(mockRttController).rangeRequest(eq(cmdId), mRttConfigCaptor.capture());
+
+        // verify contents of HAL request (hard codes knowledge from getDummyRangingRequest()).
+        ArrayList<RttConfig> halRequest = mRttConfigCaptor.getValue();
+
+        collector.checkThat("number of entries", halRequest.size(),
+                equalTo(request.mRttPeers.size()));
+
+        RttConfig rttConfig = halRequest.get(0);
+        collector.checkThat("entry 0: MAC", rttConfig.addr,
+                equalTo(MacAddress.fromString("00:01:02:03:04:00").toByteArray()));
+        collector.checkThat("entry 0: MAC", rttConfig.type, equalTo(RttType.TWO_SIDED));
+        collector.checkThat("entry 0: MAC", rttConfig.peer, equalTo(RttPeerType.AP));
+
+        rttConfig = halRequest.get(1);
+        collector.checkThat("entry 1: MAC", rttConfig.addr,
+                equalTo(MacAddress.fromString("0A:0B:0C:0D:0E:00").toByteArray()));
+        collector.checkThat("entry 1: MAC", rttConfig.type, equalTo(RttType.ONE_SIDED));
+        collector.checkThat("entry 1: MAC", rttConfig.peer, equalTo(RttPeerType.AP));
+
+        rttConfig = halRequest.get(2);
+        collector.checkThat("entry 2: MAC", rttConfig.addr,
+                equalTo(MacAddress.fromString("08:09:08:07:06:05").toByteArray()));
+        collector.checkThat("entry 2: MAC", rttConfig.type, equalTo(RttType.TWO_SIDED));
+        collector.checkThat("entry 2: MAC", rttConfig.peer, equalTo(RttPeerType.NAN));
+
+        verifyNoMoreInteractions(mockRttController);
+    }
+
+    /**
+     * Validate no range request when Wi-Fi is down
+     */
+    @Test
+    public void testWifiDown() throws Exception {
+        int cmdId = 55;
+        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
+
+        // (1) configure Wi-Fi down and send a status change indication
+        when(mockHalDeviceManager.isStarted()).thenReturn(false);
+        mHdmStatusListener.getValue().onStatusChanged();
+        verify(mockRttServiceImpl).disable();
+        assertFalse(mDut.isReady());
+
+        // (2) issue range request
+        mDut.rangeRequest(cmdId, request);
+
+        verifyNoMoreInteractions(mockRttServiceImpl, mockRttController);
+    }
+
+    /**
+     * Validate ranging cancel flow.
+     */
+    @Test
+    public void testRangeCancel() throws Exception {
+        int cmdId = 66;
+        ArrayList<byte[]> macAddresses = new ArrayList<>();
+        byte[] mac1 = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05};
+        byte[] mac2 = {0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};
+        macAddresses.add(mac1);
+        macAddresses.add(mac2);
+
+        // (1) issue cancel request
+        mDut.rangeCancel(cmdId, macAddresses);
+
+        // (2) verify HAL call and parameters
+        verify(mockRttController).rangeCancel(cmdId, macAddresses);
+
+        verifyNoMoreInteractions(mockRttController);
+    }
+
+    /**
+     * Validate correct result conversion from HAL to framework.
+     */
+    @Test
+    public void testRangeResults() throws Exception {
+        int cmdId = 55;
+        ArrayList<RttResult> results = new ArrayList<>();
+        RttResult res = new RttResult();
+        res.addr[0] = 5;
+        res.addr[1] = 6;
+        res.addr[2] = 7;
+        res.addr[3] = 8;
+        res.addr[4] = 9;
+        res.addr[5] = 10;
+        res.status = RttStatus.SUCCESS;
+        res.distanceInMm = 1500;
+        res.timeStampInUs = 666;
+        results.add(res);
+
+        // (1) have HAL call native with results
+        mDut.onResults(cmdId, results);
+
+        // (2) verify call to framework
+        verify(mockRttServiceImpl).onRangingResults(eq(cmdId), mRttResultCaptor.capture());
+
+        // verify contents of the framework results
+        List<RttResult> rttR = mRttResultCaptor.getValue();
+
+        collector.checkThat("number of entries", rttR.size(), equalTo(1));
+
+        RttResult rttResult = rttR.get(0);
+        collector.checkThat("status", rttResult.status,
+                equalTo(RttStatus.SUCCESS));
+        collector.checkThat("mac", rttResult.addr,
+                equalTo(MacAddress.fromString("05:06:07:08:09:0A").toByteArray()));
+        collector.checkThat("distanceCm", rttResult.distanceInMm, equalTo(1500));
+        collector.checkThat("timestamp", rttResult.timeStampInUs, equalTo(666L));
+
+        verifyNoMoreInteractions(mockRttController);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java
new file mode 100644
index 0000000..7e71069
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java
@@ -0,0 +1,1157 @@
+/*
+ * 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.rtt;
+
+import static com.android.server.wifi.rtt.RttTestUtils.compareListContentsNoOrdering;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.test.MockAnswerUtil;
+import android.app.test.TestAlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.wifi.V1_0.RttResult;
+import android.net.MacAddress;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
+import android.net.wifi.aware.IWifiAwareManager;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.rtt.IRttCallback;
+import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.RangingResultCallback;
+import android.net.wifi.rtt.ResponderConfig;
+import android.net.wifi.rtt.WifiRttManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.os.test.TestLooper;
+import android.util.Pair;
+
+import com.android.server.wifi.Clock;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Unit test harness for the RttServiceImpl class.
+ */
+public class RttServiceImplTest {
+    private RttServiceImplSpy mDut;
+    private TestLooper mMockLooper;
+    private TestAlarmManager mAlarmManager;
+    private PowerManager mMockPowerManager;
+    private BroadcastReceiver mPowerBcastReceiver;
+
+    private final String mPackageName = "some.package.name.for.rtt.app";
+    private int mDefaultUid = 1500;
+
+    private ArgumentCaptor<Integer> mIntCaptor = ArgumentCaptor.forClass(Integer.class);
+    private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor = ArgumentCaptor
+            .forClass(IBinder.DeathRecipient.class);
+    private ArgumentCaptor<RangingRequest> mRequestCaptor = ArgumentCaptor.forClass(
+            RangingRequest.class);
+    private ArgumentCaptor<List> mListCaptor = ArgumentCaptor.forClass(List.class);
+
+    private BinderLinkToDeathAnswer mBinderLinkToDeathCounter = new BinderLinkToDeathAnswer();
+    private BinderUnlinkToDeathAnswer mBinderUnlinkToDeathCounter = new BinderUnlinkToDeathAnswer();
+
+    private InOrder mInOrder;
+
+    @Mock
+    public Context mockContext;
+
+    @Mock
+    public ActivityManager mockActivityManager;
+
+    @Mock
+    public Clock mockClock;
+
+    @Mock
+    public RttNative mockNative;
+
+    @Mock
+    public IWifiAwareManager mockAwareManagerBinder;
+
+    @Mock
+    public WifiPermissionsUtil mockPermissionUtil;
+
+    @Mock
+    public IBinder mockIbinder;
+
+    @Mock
+    public IRttCallback mockCallback;
+
+    /**
+     * Using instead of spy to avoid native crash failures - possibly due to
+     * spy's copying of state.
+     */
+    private class RttServiceImplSpy extends RttServiceImpl {
+        public int fakeUid;
+
+        RttServiceImplSpy(Context context) {
+            super(context);
+        }
+
+        /**
+         * Return the fake UID instead of the real one: pseudo-spy
+         * implementation.
+         */
+        @Override
+        public int getMockableCallingUid() {
+            return fakeUid;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mDut = new RttServiceImplSpy(mockContext);
+        mDut.fakeUid = mDefaultUid;
+        mMockLooper = new TestLooper();
+
+        mAlarmManager = new TestAlarmManager();
+        when(mockContext.getSystemService(Context.ALARM_SERVICE))
+                .thenReturn(mAlarmManager.getAlarmManager());
+        mInOrder = inOrder(mAlarmManager.getAlarmManager(), mockContext);
+
+        when(mockContext.getSystemService(Context.ACTIVITY_SERVICE)).thenReturn(
+                mockActivityManager);
+        when(mockActivityManager.getUidImportance(anyInt())).thenReturn(
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE);
+
+        when(mockPermissionUtil.checkCallersLocationPermission(eq(mPackageName),
+                anyInt())).thenReturn(true);
+        when(mockNative.isReady()).thenReturn(true);
+        when(mockNative.rangeRequest(anyInt(), any(RangingRequest.class))).thenReturn(true);
+
+        mMockPowerManager = new PowerManager(mockContext, mock(IPowerManager.class),
+                new Handler(mMockLooper.getLooper()));
+        when(mMockPowerManager.isDeviceIdleMode()).thenReturn(false);
+        when(mockContext.getSystemServiceName(PowerManager.class)).thenReturn(
+                Context.POWER_SERVICE);
+        when(mockContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
+
+        doAnswer(mBinderLinkToDeathCounter).when(mockIbinder).linkToDeath(any(), anyInt());
+        doAnswer(mBinderUnlinkToDeathCounter).when(mockIbinder).unlinkToDeath(any(), anyInt());
+
+        mDut.start(mMockLooper.getLooper(), mockClock, mockAwareManagerBinder, mockNative,
+                mockPermissionUtil);
+        mMockLooper.dispatchAll();
+        ArgumentCaptor<BroadcastReceiver> bcastRxCaptor = ArgumentCaptor.forClass(
+                BroadcastReceiver.class);
+        verify(mockContext).registerReceiver(bcastRxCaptor.capture(), any(IntentFilter.class));
+        verify(mockNative).start();
+        mPowerBcastReceiver = bcastRxCaptor.getValue();
+
+        assertTrue(mDut.isAvailable());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        assertEquals("Binder links != unlinks to death (size)",
+                mBinderLinkToDeathCounter.mUniqueExecs.size(),
+                mBinderUnlinkToDeathCounter.mUniqueExecs.size());
+        assertEquals("Binder links != unlinks to death", mBinderLinkToDeathCounter.mUniqueExecs,
+                mBinderUnlinkToDeathCounter.mUniqueExecs);
+    }
+
+    /**
+     * Validate successful ranging flow.
+     */
+    @Test
+    public void testRangingFlow() throws Exception {
+        int numIter = 10;
+        RangingRequest[] requests = new RangingRequest[numIter];
+        List<Pair<List<RttResult>, List<RangingResult>>> results = new ArrayList<>();
+
+        for (int i = 0; i < numIter; ++i) {
+            requests[i] = RttTestUtils.getDummyRangingRequest((byte) i);
+            results.add(RttTestUtils.getDummyRangingResults(requests[i]));
+        }
+
+        // (1) request 10 ranging operations
+        for (int i = 0; i < numIter; ++i) {
+            mDut.startRanging(mockIbinder, mPackageName, null, requests[i], mockCallback);
+        }
+        mMockLooper.dispatchAll();
+
+        for (int i = 0; i < numIter; ++i) {
+            // (2) verify that request issued to native
+            verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(requests[i]));
+            verifyWakeupSet();
+
+            // (3) native calls back with result
+            mDut.onRangingResults(mIntCaptor.getValue(), results.get(i).first);
+            mMockLooper.dispatchAll();
+
+            // (4) verify that results dispatched
+            verify(mockCallback).onRangingResults(results.get(i).second);
+            verifyWakeupCancelled();
+
+            // (5) replicate results - shouldn't dispatch another callback
+            mDut.onRangingResults(mIntCaptor.getValue(), results.get(i).first);
+            mMockLooper.dispatchAll();
+        }
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate a successful ranging flow with PeerHandles (i.e. verify translations)
+     */
+    @Test
+    public void testRangingFlowUsingAwarePeerHandles() throws Exception {
+        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0xA);
+        PeerHandle peerHandle1 = new PeerHandle(1022);
+        PeerHandle peerHandle2 = new PeerHandle(1023);
+        request.mRttPeers.add(ResponderConfig.fromWifiAwarePeerHandleWithDefaults(peerHandle1));
+        request.mRttPeers.add(ResponderConfig.fromWifiAwarePeerHandleWithDefaults(peerHandle2));
+        Map<Integer, MacAddress> peerHandleToMacMap = new HashMap<>();
+        MacAddress macAwarePeer1 = MacAddress.fromString("AA:BB:CC:DD:EE:FF");
+        MacAddress macAwarePeer2 = MacAddress.fromString("BB:BB:BB:EE:EE:EE");
+        peerHandleToMacMap.put(peerHandle1.peerId, macAwarePeer1);
+        peerHandleToMacMap.put(peerHandle2.peerId, macAwarePeer2);
+
+        AwareTranslatePeerHandlesToMac answer = new AwareTranslatePeerHandlesToMac(mDefaultUid,
+                peerHandleToMacMap);
+        doAnswer(answer).when(mockAwareManagerBinder).requestMacAddresses(anyInt(), any(), any());
+
+        // issue request
+        mDut.startRanging(mockIbinder, mPackageName, null, request, mockCallback);
+        mMockLooper.dispatchAll();
+
+        // verify that requested with MAC address translated from the PeerHandle issued to Native
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), mRequestCaptor.capture());
+        verifyWakeupSet();
+
+        RangingRequest finalRequest = mRequestCaptor.getValue();
+        assertNotEquals("Request to native is not null", null, finalRequest);
+        assertEquals("Size of request", request.mRttPeers.size(), finalRequest.mRttPeers.size());
+        assertEquals("Aware peer 1 MAC", macAwarePeer1,
+                finalRequest.mRttPeers.get(finalRequest.mRttPeers.size() - 2).macAddress);
+        assertEquals("Aware peer 2 MAC", macAwarePeer2,
+                finalRequest.mRttPeers.get(finalRequest.mRttPeers.size() - 1).macAddress);
+
+        // issue results - but remove the one for peer #2
+        Pair<List<RttResult>, List<RangingResult>> results =
+                RttTestUtils.getDummyRangingResults(mRequestCaptor.getValue());
+        results.first.remove(results.first.size() - 1);
+        RangingResult removed = results.second.remove(results.second.size() - 1);
+        results.second.add(
+                new RangingResult(RangingResult.STATUS_FAIL, removed.getPeerHandle(), 0, 0, 0, 0));
+        mDut.onRangingResults(mIntCaptor.getValue(), results.first);
+        mMockLooper.dispatchAll();
+
+        // verify that results with MAC addresses filtered out and replaced by PeerHandles issued
+        // to callback
+        verify(mockCallback).onRangingResults(mListCaptor.capture());
+        verifyWakeupCancelled();
+
+        assertTrue(compareListContentsNoOrdering(results.second, mListCaptor.getValue()));
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate failed ranging flow (native failure).
+     */
+    @Test
+    public void testRangingFlowNativeFailure() throws Exception {
+        int numIter = 10;
+        RangingRequest[] requests = new RangingRequest[numIter];
+        List<Pair<List<RttResult>, List<RangingResult>>> results = new ArrayList<>();
+
+        for (int i = 0; i < numIter; ++i) {
+            requests[i] = RttTestUtils.getDummyRangingRequest((byte) i);
+            results.add(RttTestUtils.getDummyRangingResults(requests[i]));
+        }
+
+        // (1) request 10 ranging operations: fail the first one
+        when(mockNative.rangeRequest(anyInt(), any(RangingRequest.class))).thenReturn(false);
+        mDut.startRanging(mockIbinder, mPackageName, null, requests[0], mockCallback);
+        mMockLooper.dispatchAll();
+
+        when(mockNative.rangeRequest(anyInt(), any(RangingRequest.class))).thenReturn(true);
+        for (int i = 1; i < numIter; ++i) {
+            mDut.startRanging(mockIbinder, mPackageName, null, requests[i], mockCallback);
+        }
+        mMockLooper.dispatchAll();
+
+        for (int i = 0; i < numIter; ++i) {
+            // (2) verify that request issued to native
+            verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(requests[i]));
+
+            // (3) verify that failure callback dispatched (for the HAL failure)
+            if (i == 0) {
+                verify(mockCallback).onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+            } else {
+                verifyWakeupSet();
+            }
+
+            // (4) on failed HAL: even if native calls back with result we shouldn't dispatch
+            // callback, otherwise expect result
+            mDut.onRangingResults(mIntCaptor.getValue(), results.get(i).first);
+            mMockLooper.dispatchAll();
+
+            if (i != 0) {
+                verify(mockCallback).onRangingResults(results.get(i).second);
+                verifyWakeupCancelled();
+            }
+        }
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate a ranging flow for an app whose LOCATION runtime permission is revoked.
+     */
+    @Test
+    public void testRangingRequestWithoutRuntimePermission() throws Exception {
+        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
+        Pair<List<RttResult>, List<RangingResult>> results = RttTestUtils.getDummyRangingResults(
+                request);
+
+        // (1) request ranging operation
+        mDut.startRanging(mockIbinder, mPackageName, null, request, mockCallback);
+        mMockLooper.dispatchAll();
+
+        // (2) verify that request issued to native
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+        verifyWakeupSet();
+
+        // (3) native calls back with result - should get a FAILED callback
+        when(mockPermissionUtil.checkCallersLocationPermission(eq(mPackageName),
+                anyInt())).thenReturn(false);
+
+        mDut.onRangingResults(mIntCaptor.getValue(), results.first);
+        mMockLooper.dispatchAll();
+
+        verify(mockCallback).onRangingFailure(eq(RangingResultCallback.STATUS_CODE_FAIL));
+        verifyWakeupCancelled();
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate that the ranging app's binder death clears record of request - no callbacks are
+     * attempted.
+     */
+    @Test
+    public void testBinderDeathOfRangingApp() throws Exception {
+        int numIter = 10;
+        RangingRequest[] requests = new RangingRequest[numIter];
+        List<Pair<List<RttResult>, List<RangingResult>>> results = new ArrayList<>();
+
+        for (int i = 0; i < numIter; ++i) {
+            requests[i] = RttTestUtils.getDummyRangingRequest((byte) i);
+            results.add(RttTestUtils.getDummyRangingResults(requests[i]));
+        }
+
+        // (1) request 10 ranging operations: even/odd with different UIDs
+        for (int i = 0; i < numIter; ++i) {
+            mDut.fakeUid = mDefaultUid + i % 2;
+            mDut.startRanging(mockIbinder, mPackageName, null, requests[i], mockCallback);
+        }
+        mMockLooper.dispatchAll();
+
+        // (2) capture death listeners
+        verify(mockIbinder, times(numIter)).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
+
+        for (int i = 0; i < numIter; ++i) {
+            // (3) verify first request and all odd requests issued to HAL
+            if (i == 0 || i % 2 == 1) {
+                verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(requests[i]));
+                verifyWakeupSet();
+            }
+
+            // (4) trigger first death recipient (which will map to the even UID)
+            if (i == 0) {
+                mDeathRecipientCaptor.getAllValues().get(0).binderDied();
+                mMockLooper.dispatchAll();
+
+                verify(mockNative).rangeCancel(eq(mIntCaptor.getValue()),
+                        (ArrayList) mListCaptor.capture());
+                RangingRequest request0 = requests[0];
+                assertEquals(request0.mRttPeers.size(), mListCaptor.getValue().size());
+                assertArrayEquals(MacAddress.fromString("00:01:02:03:04:00").toByteArray(),
+                        (byte[]) mListCaptor.getValue().get(0));
+                assertArrayEquals(MacAddress.fromString("0A:0B:0C:0D:0E:00").toByteArray(),
+                        (byte[]) mListCaptor.getValue().get(1));
+                assertArrayEquals(MacAddress.fromString("08:09:08:07:06:05").toByteArray(),
+                        (byte[]) mListCaptor.getValue().get(2));
+            }
+
+            // (5) native calls back with all results - should get requests for the odd attempts and
+            // should only get callbacks for the odd attempts (the non-dead UID), but this simulates
+            // invalid results (or possibly the firmware not cancelling some requests)
+            mDut.onRangingResults(mIntCaptor.getValue(), results.get(i).first);
+            mMockLooper.dispatchAll();
+            if (i == 0) {
+                verifyWakeupCancelled(); // as the first (dispatched) request is aborted
+            }
+            if (i % 2 == 1) {
+                verify(mockCallback).onRangingResults(results.get(i).second);
+                verifyWakeupCancelled();
+            }
+        }
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate that a ranging app which uses WorkSource and dies (binder death) results in the
+     * request cleanup.
+     */
+    @Test
+    public void testBinderDeathWithWorkSource() throws Exception {
+        WorkSource ws = new WorkSource(100);
+
+        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
+        Pair<List<RttResult>, List<RangingResult>> results = RttTestUtils.getDummyRangingResults(
+                request);
+
+        // (1) request ranging operation
+        mDut.startRanging(mockIbinder, mPackageName, ws, request, mockCallback);
+        mMockLooper.dispatchAll();
+
+        verify(mockIbinder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+        verifyWakeupSet();
+
+        // (2) execute binder death
+        mDeathRecipientCaptor.getValue().binderDied();
+        mMockLooper.dispatchAll();
+
+        verify(mockNative).rangeCancel(eq(mIntCaptor.getValue()), any());
+        verifyWakeupCancelled();
+
+        // (3) provide results back - should be ignored
+        mDut.onRangingResults(mIntCaptor.getValue(), results.first);
+        mMockLooper.dispatchAll();
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate that when a cancelRanging is called, using the same work source specification as the
+     * request, that the request is cancelled.
+     */
+    @Test
+    public void testCancelRangingFullMatch() throws Exception {
+        int uid1 = 10;
+        int uid2 = 20;
+        int uid3 = 30;
+        WorkSource worksourceRequest = new WorkSource(uid1);
+        worksourceRequest.add(uid2);
+        worksourceRequest.add(uid3);
+        WorkSource worksourceCancel = new WorkSource(uid2);
+        worksourceCancel.add(uid3);
+        worksourceCancel.add(uid1);
+
+        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
+        Pair<List<RttResult>, List<RangingResult>> results = RttTestUtils.getDummyRangingResults(
+                request);
+
+        // (1) request ranging operation
+        mDut.startRanging(mockIbinder, mPackageName, worksourceRequest, request, mockCallback);
+        mMockLooper.dispatchAll();
+
+        // (2) verify that request issued to native
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+        verifyWakeupSet();
+
+        // (3) cancel the request
+        mDut.cancelRanging(worksourceCancel);
+        mMockLooper.dispatchAll();
+
+        verify(mockNative).rangeCancel(eq(mIntCaptor.getValue()), any());
+        verifyWakeupCancelled();
+
+        // (4) send results back from native
+        mDut.onRangingResults(mIntCaptor.getValue(), results.first);
+        mMockLooper.dispatchAll();
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate that when a cancelRanging is called - but specifies a subset of the WorkSource
+     * uids then the ranging proceeds.
+     */
+    @Test
+    public void testCancelRangingPartialMatch() throws Exception {
+        int uid1 = 10;
+        int uid2 = 20;
+        int uid3 = 30;
+        WorkSource worksourceRequest = new WorkSource(uid1);
+        worksourceRequest.add(uid2);
+        worksourceRequest.add(uid3);
+        WorkSource worksourceCancel = new WorkSource(uid1);
+        worksourceCancel.add(uid2);
+
+        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
+        Pair<List<RttResult>, List<RangingResult>> results = RttTestUtils.getDummyRangingResults(
+                request);
+
+        // (1) request ranging operation
+        mDut.startRanging(mockIbinder, mPackageName, worksourceRequest, request, mockCallback);
+        mMockLooper.dispatchAll();
+
+        // (2) verify that request issued to native
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+        verifyWakeupSet();
+
+        // (3) cancel the request
+        mDut.cancelRanging(worksourceCancel);
+
+        // (4) send results back from native
+        mDut.onRangingResults(mIntCaptor.getValue(), results.first);
+        mMockLooper.dispatchAll();
+
+        verify(mockCallback).onRangingResults(results.second);
+        verifyWakeupCancelled();
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate that when an unexpected result is provided by the Native it is not propagated to
+     * caller (unexpected = different command ID).
+     */
+    @Test
+    public void testUnexpectedResult() throws Exception {
+        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
+        Pair<List<RttResult>, List<RangingResult>> results = RttTestUtils.getDummyRangingResults(
+                request);
+
+        // (1) request ranging operation
+        mDut.startRanging(mockIbinder, mPackageName, null, request, mockCallback);
+        mMockLooper.dispatchAll();
+
+        // (2) verify that request issued to native
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+        verifyWakeupSet();
+
+        // (3) native calls back with result - but wrong ID
+        mDut.onRangingResults(mIntCaptor.getValue() + 1,
+                RttTestUtils.getDummyRangingResults(null).first);
+        mMockLooper.dispatchAll();
+
+        // (4) now send results with correct ID (different set of results to differentiate)
+        mDut.onRangingResults(mIntCaptor.getValue(), results.first);
+        mMockLooper.dispatchAll();
+
+        // (5) verify that results dispatched
+        verify(mockCallback).onRangingResults(results.second);
+        verifyWakeupCancelled();
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate that the HAL returns results with "missing" entries (i.e. some requests don't get
+     * results) they are filled-in with FAILED results.
+     */
+    @Test
+    public void testMissingResults() throws Exception {
+        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 0);
+        Pair<List<RttResult>, List<RangingResult>> results = RttTestUtils.getDummyRangingResults(
+                request);
+        results.first.remove(2); // remove a direct AWARE request
+        RangingResult removed = results.second.remove(2);
+        results.second.add(
+                new RangingResult(RangingResult.STATUS_FAIL, removed.getMacAddress(), 0, 0, 0, 0));
+        results.first.remove(0); // remove an AP request
+        removed = results.second.remove(0);
+        results.second.add(
+                new RangingResult(RangingResult.STATUS_FAIL, removed.getMacAddress(), 0, 0, 0, 0));
+
+        // (1) request ranging operation
+        mDut.startRanging(mockIbinder, mPackageName, null, request, mockCallback);
+        mMockLooper.dispatchAll();
+
+        // (2) verify that request issued to native
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+        verifyWakeupSet();
+
+        // (3) return results with missing entries
+        mDut.onRangingResults(mIntCaptor.getValue(), results.first);
+        mMockLooper.dispatchAll();
+
+        // (5) verify that (full) results dispatched
+        verify(mockCallback).onRangingResults(mListCaptor.capture());
+        assertTrue(compareListContentsNoOrdering(results.second, mListCaptor.getValue()));
+        verifyWakeupCancelled();
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate that when the HAL times out we fail, clean-up the queue and move to the next
+     * request.
+     */
+    @Test
+    public void testRangingTimeout() throws Exception {
+        RangingRequest request1 = RttTestUtils.getDummyRangingRequest((byte) 1);
+        RangingRequest request2 = RttTestUtils.getDummyRangingRequest((byte) 2);
+        Pair<List<RttResult>, List<RangingResult>> result1 = RttTestUtils.getDummyRangingResults(
+                request1);
+        Pair<List<RttResult>, List<RangingResult>> result2 = RttTestUtils.getDummyRangingResults(
+                request2);
+
+        // (1) request 2 ranging operation
+        mDut.startRanging(mockIbinder, mPackageName, null, request1, mockCallback);
+        mDut.startRanging(mockIbinder, mPackageName, null, request2, mockCallback);
+        mMockLooper.dispatchAll();
+
+        // verify that request 1 issued to native
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request1));
+        int cmdId1 = mIntCaptor.getValue();
+        verifyWakeupSet();
+
+        // (2) time-out
+        mAlarmManager.dispatch(RttServiceImpl.HAL_RANGING_TIMEOUT_TAG);
+        mMockLooper.dispatchAll();
+
+        // verify that: failure callback + request 2 issued to native
+        verify(mockNative).rangeCancel(eq(cmdId1), any());
+        verify(mockCallback).onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request2));
+        verifyWakeupSet();
+
+        // (3) send both result 1 and result 2
+        mDut.onRangingResults(cmdId1, result1.first);
+        mDut.onRangingResults(mIntCaptor.getValue(), result2.first);
+        mMockLooper.dispatchAll();
+
+        // verify that only result 2 is forwarded to client
+        verify(mockCallback).onRangingResults(result2.second);
+        verifyWakeupCancelled();
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate that ranging requests from background apps are throttled. The sequence is:
+     * - Time 1: Background request -> ok
+     * - Time 2 = t1 + 0.5gap: Background request -> fail (throttled)
+     * - Time 3 = t1 + 1.1gap: Background request -> ok
+     * - Time 4 = t3 + small: Foreground request -> ok
+     * - Time 5 = t4 + small: Background request -> fail (throttled)
+     */
+    @Test
+    public void testRangingThrottleBackground() throws Exception {
+        RangingRequest request1 = RttTestUtils.getDummyRangingRequest((byte) 1);
+        RangingRequest request2 = RttTestUtils.getDummyRangingRequest((byte) 2);
+        RangingRequest request3 = RttTestUtils.getDummyRangingRequest((byte) 3);
+        RangingRequest request4 = RttTestUtils.getDummyRangingRequest((byte) 4);
+        RangingRequest request5 = RttTestUtils.getDummyRangingRequest((byte) 5);
+
+        Pair<List<RttResult>, List<RangingResult>> result1 = RttTestUtils.getDummyRangingResults(
+                request1);
+        Pair<List<RttResult>, List<RangingResult>> result3 = RttTestUtils.getDummyRangingResults(
+                request3);
+        Pair<List<RttResult>, List<RangingResult>> result4 = RttTestUtils.getDummyRangingResults(
+                request4);
+
+        InOrder cbInorder = inOrder(mockCallback);
+
+        ClockAnswer clock = new ClockAnswer();
+        doAnswer(clock).when(mockClock).getElapsedSinceBootMillis();
+        when(mockActivityManager.getUidImportance(anyInt())).thenReturn(
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE); // far background
+
+        // (1) issue a request at time t1: should be dispatched since first one!
+        clock.time = 100;
+        mDut.startRanging(mockIbinder, mPackageName, null, request1, mockCallback);
+        mMockLooper.dispatchAll();
+
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request1));
+        verifyWakeupSet();
+
+        // (1.1) get result
+        mDut.onRangingResults(mIntCaptor.getValue(), result1.first);
+        mMockLooper.dispatchAll();
+
+        cbInorder.verify(mockCallback).onRangingResults(result1.second);
+        verifyWakeupCancelled();
+
+        // (2) issue a request at time t2 = t1 + 0.5 gap: should be rejected (throttled)
+        clock.time = 100 + RttServiceImpl.BACKGROUND_PROCESS_EXEC_GAP_MS / 2;
+        mDut.startRanging(mockIbinder, mPackageName, null, request2, mockCallback);
+        mMockLooper.dispatchAll();
+
+        cbInorder.verify(mockCallback).onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+
+        // (3) issue a request at time t3 = t1 + 1.1 gap: should be dispatched since enough time
+        clock.time = 100 + RttServiceImpl.BACKGROUND_PROCESS_EXEC_GAP_MS * 11 / 10;
+        mDut.startRanging(mockIbinder, mPackageName, null, request3, mockCallback);
+        mMockLooper.dispatchAll();
+
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request3));
+        verifyWakeupSet();
+
+        // (3.1) get result
+        mDut.onRangingResults(mIntCaptor.getValue(), result3.first);
+        mMockLooper.dispatchAll();
+
+        cbInorder.verify(mockCallback).onRangingResults(result3.second);
+        verifyWakeupCancelled();
+
+        // (4) issue a foreground request at t4 = t3 + small: should be dispatched (foreground!)
+        when(mockActivityManager.getUidImportance(anyInt())).thenReturn(
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+
+        clock.time = clock.time + 5;
+        mDut.startRanging(mockIbinder, mPackageName, null, request4, mockCallback);
+        mMockLooper.dispatchAll();
+
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request4));
+        verifyWakeupSet();
+
+        // (4.1) get result
+        mDut.onRangingResults(mIntCaptor.getValue(), result4.first);
+        mMockLooper.dispatchAll();
+
+        cbInorder.verify(mockCallback).onRangingResults(result4.second);
+        verifyWakeupCancelled();
+
+        // (5) issue a background request at t5 = t4 + small: should be rejected (throttled)
+        when(mockActivityManager.getUidImportance(anyInt())).thenReturn(
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE);
+
+        clock.time = clock.time + 5;
+        mDut.startRanging(mockIbinder, mPackageName, null, request5, mockCallback);
+        mMockLooper.dispatchAll();
+
+        cbInorder.verify(mockCallback).onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate that throttling of background request handles multiple work source correctly:
+     * - Time t1: background request uid=10: ok
+     * - Time t2 = t1+small: background request ws={10,20}: ok
+     * - Time t3 = t1+gap: background request uid=10: fail (throttled)
+     */
+    @Test
+    public void testRangingThrottleBackgroundWorkSources() throws Exception {
+        WorkSource wsReq1 = new WorkSource(10);
+        WorkSource wsReq2 = new WorkSource(10);
+        wsReq2.add(20);
+
+        RangingRequest request1 = RttTestUtils.getDummyRangingRequest((byte) 1);
+        RangingRequest request2 = RttTestUtils.getDummyRangingRequest((byte) 2);
+        RangingRequest request3 = RttTestUtils.getDummyRangingRequest((byte) 3);
+
+        Pair<List<RttResult>, List<RangingResult>> result1 = RttTestUtils.getDummyRangingResults(
+                request1);
+        Pair<List<RttResult>, List<RangingResult>> result2 = RttTestUtils.getDummyRangingResults(
+                request2);
+
+        InOrder cbInorder = inOrder(mockCallback);
+
+        ClockAnswer clock = new ClockAnswer();
+        doAnswer(clock).when(mockClock).getElapsedSinceBootMillis();
+        when(mockActivityManager.getUidImportance(anyInt())).thenReturn(
+                ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE); // far background
+
+        // (1) issue a request at time t1 for {10}: should be dispatched since first one!
+        clock.time = 100;
+        mDut.startRanging(mockIbinder, mPackageName, wsReq1, request1, mockCallback);
+        mMockLooper.dispatchAll();
+
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request1));
+        verifyWakeupSet();
+
+        // (1.1) get result
+        mDut.onRangingResults(mIntCaptor.getValue(), result1.first);
+        mMockLooper.dispatchAll();
+
+        cbInorder.verify(mockCallback).onRangingResults(result1.second);
+        verifyWakeupCancelled();
+
+        // (2) issue a request at time t2 = t1 + 0.5 gap for {10,20}: should be dispatched since
+        //     uid=20 should not be throttled
+        clock.time = 100 + RttServiceImpl.BACKGROUND_PROCESS_EXEC_GAP_MS / 2;
+        mDut.startRanging(mockIbinder, mPackageName, wsReq2, request2, mockCallback);
+        mMockLooper.dispatchAll();
+
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request2));
+        verifyWakeupSet();
+
+        // (2.1) get result
+        mDut.onRangingResults(mIntCaptor.getValue(), result2.first);
+        mMockLooper.dispatchAll();
+
+        cbInorder.verify(mockCallback).onRangingResults(result2.second);
+        verifyWakeupCancelled();
+
+        // (3) issue a request at t3 = t1 + 1.1 * gap for {10}: should be rejected (throttled)
+        clock.time = 100 + RttServiceImpl.BACKGROUND_PROCESS_EXEC_GAP_MS * 11 / 10;
+        mDut.startRanging(mockIbinder, mPackageName, wsReq1, request3, mockCallback);
+        mMockLooper.dispatchAll();
+
+        cbInorder.verify(mockCallback).onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate that flooding the service with ranging requests will cause it to start rejecting
+     * rejects from the flooding uid. Single UID.
+     */
+    @Test
+    public void testRejectFloodingRequestsSingleUid() throws Exception {
+        runFloodRequestsTest(true);
+    }
+
+    /**
+     * Validate that flooding the service with ranging requests will cause it to start rejecting
+     * rejects from the flooding uid. WorkSource (all identical).
+     */
+    @Test
+    public void testRejectFloodingRequestsIdenticalWorksources() throws Exception {
+        runFloodRequestsTest(false);
+    }
+
+    /**
+     * Validate that flooding the service with ranging requests will cause it to start rejecting
+     * rejects from the flooding uid. WorkSource (with one constant UID but other varying UIDs -
+     * the varying UIDs should prevent the flood throttle)
+     */
+    @Test
+    public void testDontRejectFloodingRequestsVariousUids() throws Exception {
+        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 1);
+        WorkSource ws = new WorkSource(10);
+
+        // 1. issue a request
+        mDut.startRanging(mockIbinder, mPackageName, ws, request, mockCallback);
+        mMockLooper.dispatchAll();
+
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+        verifyWakeupSet();
+
+        // 2. issue FLOOD LEVEL requests + 10 at various UIDs - no failure expected
+        for (int i = 0; i < RttServiceImpl.MAX_QUEUED_PER_UID + 10; ++i) {
+            WorkSource wsExtra = new WorkSource(ws);
+            wsExtra.add(11 + i);
+            mDut.startRanging(mockIbinder, mPackageName, wsExtra, request, mockCallback);
+        }
+        mMockLooper.dispatchAll();
+
+        // 3. clear queue
+        mDut.disable();
+        mMockLooper.dispatchAll();
+
+        verifyWakeupCancelled();
+        verify(mockNative).rangeCancel(eq(mIntCaptor.getValue()), any());
+        verify(mockCallback, times(RttServiceImpl.MAX_QUEUED_PER_UID + 11)).onRangingFailure(
+                RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Utility to run configurable tests for flooding range requests.
+     * - Execute a single request
+     * - Flood service with requests: using same ID or same WorkSource
+     * - Provide results (to clear queue) and execute another test: validate succeeds
+     */
+    private void runFloodRequestsTest(boolean useUids) throws Exception {
+        RangingRequest request = RttTestUtils.getDummyRangingRequest((byte) 1);
+        Pair<List<RttResult>, List<RangingResult>> result = RttTestUtils.getDummyRangingResults(
+                request);
+
+        WorkSource ws = new WorkSource();
+        ws.add(10);
+        ws.add(20);
+        ws.add(30);
+
+        InOrder cbInorder = inOrder(mockCallback);
+        InOrder nativeInorder = inOrder(mockNative);
+
+        // 1. issue a request
+        mDut.startRanging(mockIbinder, mPackageName, useUids ? null : ws, request, mockCallback);
+        mMockLooper.dispatchAll();
+
+        nativeInorder.verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+        verifyWakeupSet();
+
+        // 2. issue FLOOD LEVEL requests + 10: should get 11 failures (10 extra + 1 original)
+        for (int i = 0; i < RttServiceImpl.MAX_QUEUED_PER_UID + 10; ++i) {
+            mDut.startRanging(mockIbinder, mPackageName, useUids ? null : ws, request,
+                    mockCallback);
+        }
+        mMockLooper.dispatchAll();
+
+        cbInorder.verify(mockCallback, times(11)).onRangingFailure(
+                RangingResultCallback.STATUS_CODE_FAIL);
+
+        // 3. provide results
+        mDut.onRangingResults(mIntCaptor.getValue(), result.first);
+        mMockLooper.dispatchAll();
+
+        cbInorder.verify(mockCallback).onRangingResults(result.second);
+        verifyWakeupCancelled();
+
+        nativeInorder.verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request));
+        verifyWakeupSet();
+
+        // 4. issue a request: don't expect a failure
+        mDut.startRanging(mockIbinder, mPackageName, useUids ? null : ws, request, mockCallback);
+        mMockLooper.dispatchAll();
+
+        // 5. clear queue
+        mDut.disable();
+        mMockLooper.dispatchAll();
+
+        verifyWakeupCancelled();
+        nativeInorder.verify(mockNative).rangeCancel(eq(mIntCaptor.getValue()), any());
+        cbInorder.verify(mockCallback, times(RttServiceImpl.MAX_QUEUED_PER_UID)).onRangingFailure(
+                RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
+    }
+
+    /**
+     * Validate that when Wi-Fi gets disabled (HAL level) the ranging queue gets cleared.
+     */
+    @Test
+    public void testDisableWifiFlow() throws Exception {
+        runDisableRttFlow(true);
+    }
+
+    /**
+     * Validate that when Doze mode starts, RTT gets disabled and the ranging queue gets cleared.
+     */
+    @Test
+    public void testDozeModeFlow() throws Exception {
+        runDisableRttFlow(false);
+    }
+
+    /**
+     * Actually execute the disable RTT flow: either by disabling Wi-Fi or enabling doze.
+     *
+     * @param disableWifi true to disable Wi-Fi, false to enable doze
+     */
+    private void runDisableRttFlow(boolean disableWifi) throws Exception {
+        RangingRequest request1 = RttTestUtils.getDummyRangingRequest((byte) 1);
+        RangingRequest request2 = RttTestUtils.getDummyRangingRequest((byte) 2);
+        RangingRequest request3 = RttTestUtils.getDummyRangingRequest((byte) 3);
+
+        IRttCallback mockCallback2 = mock(IRttCallback.class);
+        IRttCallback mockCallback3 = mock(IRttCallback.class);
+
+        // (1) request 2 ranging operations: request 1 should be sent to HAL
+        mDut.startRanging(mockIbinder, mPackageName, null, request1, mockCallback);
+        mDut.startRanging(mockIbinder, mPackageName, null, request2, mockCallback2);
+        mMockLooper.dispatchAll();
+
+        verify(mockNative).rangeRequest(mIntCaptor.capture(), eq(request1));
+        verifyWakeupSet();
+
+        // (2) disable RTT: all requests should "fail"
+        if (disableWifi) {
+            when(mockNative.isReady()).thenReturn(false);
+            mDut.disable();
+        } else {
+            simulatePowerStateChangeDoze(true);
+        }
+        mMockLooper.dispatchAll();
+
+        assertFalse(mDut.isAvailable());
+        validateCorrectRttStatusChangeBroadcast(false);
+        verify(mockNative).rangeCancel(eq(mIntCaptor.getValue()), any());
+        verify(mockCallback).onRangingFailure(
+                RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+        verify(mockCallback2).onRangingFailure(
+                RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+        verifyWakeupCancelled();
+
+        // (3) issue another request: it should fail
+        mDut.startRanging(mockIbinder, mPackageName, null, request3, mockCallback3);
+        mMockLooper.dispatchAll();
+
+        verify(mockCallback3).onRangingFailure(
+                RangingResultCallback.STATUS_CODE_FAIL_RTT_NOT_AVAILABLE);
+
+        // (4) enable RTT: nothing should happen (no requests in queue!)
+        if (disableWifi) {
+            when(mockNative.isReady()).thenReturn(true);
+            mDut.enable();
+        } else {
+            simulatePowerStateChangeDoze(false);
+        }
+        mMockLooper.dispatchAll();
+
+        assertTrue(mDut.isAvailable());
+        validateCorrectRttStatusChangeBroadcast(true);
+        verify(mockNative, atLeastOnce()).isReady();
+        verifyNoMoreInteractions(mockNative, mockCallback, mockCallback2, mockCallback3,
+                mAlarmManager.getAlarmManager());
+    }
+
+    /*
+     * Utilities
+     */
+
+    /**
+     * Simulate power state change due to doze. Changes the power manager return values and
+     * dispatches a broadcast.
+     */
+    private void simulatePowerStateChangeDoze(boolean isDozeOn) {
+        when(mMockPowerManager.isDeviceIdleMode()).thenReturn(isDozeOn);
+
+        Intent intent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+        mPowerBcastReceiver.onReceive(mockContext, intent);
+    }
+
+    private void verifyWakeupSet() {
+        mInOrder.verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(RttServiceImpl.HAL_RANGING_TIMEOUT_TAG), any(AlarmManager.OnAlarmListener.class),
+                any(Handler.class));
+    }
+
+    private void verifyWakeupCancelled() {
+        mInOrder.verify(mAlarmManager.getAlarmManager()).cancel(
+                any(AlarmManager.OnAlarmListener.class));
+    }
+
+    /**
+     * Validates that the broadcast sent on RTT status change is correct.
+     *
+     * @param expectedEnabled The expected change status - i.e. are we expected to announce that
+     *                        RTT is enabled (true) or disabled (false).
+     */
+    private void validateCorrectRttStatusChangeBroadcast(boolean expectedEnabled) {
+        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+        mInOrder.verify(mockContext).sendBroadcastAsUser(intent.capture(), eq(UserHandle.ALL));
+        assertEquals(intent.getValue().getAction(), WifiRttManager.ACTION_WIFI_RTT_STATE_CHANGED);
+    }
+
+    private class AwareTranslatePeerHandlesToMac extends MockAnswerUtil.AnswerWithArguments {
+        private int mExpectedUid;
+        private Map<Integer, MacAddress> mPeerIdToMacMap;
+
+        AwareTranslatePeerHandlesToMac(int expectedUid, Map<Integer, MacAddress> peerIdToMacMap) {
+            mExpectedUid = expectedUid;
+            mPeerIdToMacMap = peerIdToMacMap;
+        }
+
+        public void answer(int uid, List<Integer> peerIds, IWifiAwareMacAddressProvider callback) {
+            assertEquals("Invalid UID", mExpectedUid, uid);
+
+            Map<Integer, byte[]> result = new HashMap<>();
+            for (Integer peerId: peerIds) {
+                byte[] mac = mPeerIdToMacMap.get(peerId).toByteArray();
+                if (mac == null) {
+                    continue;
+                }
+
+                result.put(peerId, mac);
+            }
+
+            try {
+                callback.macAddress(result);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private class BinderDeathAnswerBase extends MockAnswerUtil.AnswerWithArguments {
+        protected Set<IBinder.DeathRecipient> mUniqueExecs = new HashSet<>();
+    }
+
+    private class BinderLinkToDeathAnswer extends BinderDeathAnswerBase {
+        public void answer(IBinder.DeathRecipient recipient, int flags) {
+            mUniqueExecs.add(recipient);
+        }
+    }
+
+    private class BinderUnlinkToDeathAnswer extends BinderDeathAnswerBase {
+        public boolean answer(IBinder.DeathRecipient recipient, int flags) {
+            mUniqueExecs.add(recipient);
+            return true;
+        }
+    }
+
+    private class ClockAnswer extends MockAnswerUtil.AnswerWithArguments {
+        public long time;
+
+        public long answer() {
+            return time;
+        }
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/rtt/RttTestUtils.java b/tests/wifitests/src/com/android/server/wifi/rtt/RttTestUtils.java
new file mode 100644
index 0000000..ec56322
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/rtt/RttTestUtils.java
@@ -0,0 +1,139 @@
+/*
+ * 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.rtt;
+
+import android.hardware.wifi.V1_0.RttResult;
+import android.hardware.wifi.V1_0.RttStatus;
+import android.net.MacAddress;
+import android.net.wifi.ScanResult;
+import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.ResponderConfig;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utilities for the Rtt unit test suite.
+ */
+public class RttTestUtils {
+    /**
+     * Compare the two lists and return true for equality, false otherwise. The two lists are
+     * considered identical if they have the same number of elements and contain equal elements
+     * (equality of elements using the equal() operator of the component objects).
+     *
+     * Note: null != empty list
+     */
+    public static boolean compareListContentsNoOrdering(List a, List b) {
+        if (a == b) {
+            return true;
+        }
+        if (a == null || b == null) {
+            return false; // at this point they're not both null
+        }
+        if (a.size() != b.size()) { // at this point neither is null
+            return false;
+        }
+        return a.containsAll(b) && b.containsAll(a);
+    }
+
+    /**
+     * Returns a dummy ranging request with 2 requests:
+     * - First: 802.11mc capable
+     * - Second: 802.11mc not capable
+     */
+    public static RangingRequest getDummyRangingRequest(byte lastMacByte) {
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+
+        ScanResult scan1 = new ScanResult();
+        scan1.BSSID = "00:01:02:03:04:" + String.format("%02d", lastMacByte);
+        scan1.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
+        ScanResult scan2 = new ScanResult();
+        scan2.BSSID = "0A:0B:0C:0D:0E:" + String.format("%02d", lastMacByte);
+        MacAddress mac1 = MacAddress.fromString("08:09:08:07:06:05");
+
+        builder.addAccessPoint(scan1);
+        builder.addAccessPoint(scan2);
+        builder.addWifiAwarePeer(mac1);
+
+        return builder.build();
+    }
+
+    /**
+     * Returns a matched set of dummy ranging results: HAL RttResult and the public API
+     * RangingResult.
+     *
+     * @param request If non-null will be used as a template (BSSID) for the range results.
+     */
+    public static Pair<List<RttResult>, List<RangingResult>> getDummyRangingResults(
+            RangingRequest request) {
+        int rangeCmBase = 15;
+        int rangeStdDevCmBase = 3;
+        int rssiBase = -20;
+        long rangeTimestampBase = 666;
+        List<RttResult> halResults = new ArrayList<>();
+        List<RangingResult> results = new ArrayList<>();
+
+        if (request != null) {
+            for (ResponderConfig peer: request.mRttPeers) {
+                RangingResult rangingResult;
+                if (peer.peerHandle == null) {
+                    rangingResult = new RangingResult(RangingResult.STATUS_SUCCESS,
+                            peer.macAddress, rangeCmBase++, rangeStdDevCmBase++, rssiBase++,
+                            rangeTimestampBase++);
+                } else {
+                    rangingResult = new RangingResult(RangingResult.STATUS_SUCCESS,
+                            peer.peerHandle, rangeCmBase++, rangeStdDevCmBase++, rssiBase++,
+                            rangeTimestampBase++);
+                }
+                results.add(rangingResult);
+                halResults.add(getMatchingRttResult(rangingResult, peer.macAddress));
+            }
+        } else {
+            results.add(new RangingResult(RangingResult.STATUS_SUCCESS,
+                    MacAddress.fromString("10:01:02:03:04:05"), rangeCmBase++,
+                    rangeStdDevCmBase++, rssiBase++, rangeTimestampBase++));
+            results.add(new RangingResult(RangingResult.STATUS_SUCCESS,
+                    MacAddress.fromString("1A:0B:0C:0D:0E:0F"), rangeCmBase++,
+                    rangeStdDevCmBase++, rssiBase++, rangeTimestampBase++));
+            results.add(new RangingResult(RangingResult.STATUS_SUCCESS,
+                    MacAddress.fromString("08:09:08:07:06:05"), rangeCmBase++,
+                    rangeStdDevCmBase++, rssiBase++, rangeTimestampBase++));
+            halResults.add(getMatchingRttResult(results.get(0), null));
+            halResults.add(getMatchingRttResult(results.get(1), null));
+            halResults.add(getMatchingRttResult(results.get(2), null));
+        }
+
+        return new Pair<>(halResults, results);
+    }
+
+    private static RttResult getMatchingRttResult(RangingResult rangingResult,
+            MacAddress overrideMac) {
+        RttResult rttResult = new RttResult();
+        rttResult.status = rangingResult.getStatus() == RangingResult.STATUS_SUCCESS
+                ? RttStatus.SUCCESS : RttStatus.FAILURE;
+        System.arraycopy(overrideMac == null ? rangingResult.getMacAddress().toByteArray()
+                : overrideMac.toByteArray(), 0, rttResult.addr, 0, 6);
+        rttResult.distanceInMm = rangingResult.getDistanceMm();
+        rttResult.distanceSdInMm = rangingResult.getDistanceStdDevMm();
+        rttResult.rssi = rangingResult.getRssi();
+        rttResult.timeStampInUs = rangingResult.getRangingTimestampUs();
+
+        return rttResult;
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/BackgroundScanSchedulerTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/BackgroundScanSchedulerTest.java
index c728d5f..e02fe77 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/BackgroundScanSchedulerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/BackgroundScanSchedulerTest.java
@@ -650,113 +650,6 @@
         assertChannels(combinedBucketChannelSet, expectedBucketChannelSet);
     }
 
-    /**
-     * Add 2 background scan requests with different time intervals, but one of the setting channels
-     * is totally contained in the other setting. Ensure that the requests are collapsed into a
-     * common bucket with the lower time period setting.
-     * This is done with NoBandChannelHelper.
-     */
-    @Test
-    public void optimalScheduleFullyCollapsesDuplicateChannelsInBandWithNoBandChannelHelper() {
-        BackgroundScanScheduler scheduler = createSchedulerWithNoBandChannelHelper();
-
-        ArrayList<ScanSettings> requests = new ArrayList<>();
-        requests.add(createRequest(channelsToSpec(2400, 2450), 160000, 0, 20,
-                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
-        requests.add(createRequest(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, 10000, 0, 20,
-                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
-
-        scheduler.setMaxBuckets(2);
-        scheduler.setMaxChannelsPerBucket(2);
-        scheduler.updateSchedule(requests);
-        WifiNative.ScanSettings schedule = scheduler.getSchedule();
-
-        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
-        assertBuckets(schedule, 1);
-
-        assertEquals("scheduled bucket", 0, scheduler.getScheduledBucket(requests.get(0)));
-        assertEquals("scheduled bucket", 0, scheduler.getScheduledBucket(requests.get(1)));
-
-        assertEquals("band", schedule.buckets[0].band, WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
-    }
-
-    /**
-     * Add 2 background scan requests with different time intervals, but one of the setting channels
-     * is partially contained in the other setting. Ensure that the requests are partially split
-     * across the lower time period bucket.
-     */
-    @Test
-    public void optimalSchedulePartiallyCollapsesDuplicateChannelsWithNoBandChannelHelper() {
-        BackgroundScanScheduler scheduler = createSchedulerWithNoBandChannelHelper();
-
-        ArrayList<ScanSettings> requests = new ArrayList<>();
-        requests.add(createRequest(channelsToSpec(2400, 2450), 10000, 0, 20,
-                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
-        requests.add(createRequest(channelsToSpec(2400, 2450, 5175), 240000, 0, 20,
-                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
-
-        scheduler.setMaxBuckets(2);
-        scheduler.setMaxChannelsPerBucket(3);
-        scheduler.updateSchedule(requests);
-        WifiNative.ScanSettings schedule = scheduler.getSchedule();
-
-        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
-        assertBuckets(schedule, 2);
-
-        assertEquals("scheduled bucket", 0, scheduler.getScheduledBucket(requests.get(0)));
-        assertEquals("scheduled bucket", 1, scheduler.getScheduledBucket(requests.get(1)));
-
-        Set<Integer> expectedBucketChannelSet = new ArraySet<>();
-        expectedBucketChannelSet.add(2400);
-        expectedBucketChannelSet.add(2450);
-        assertBucketChannels(schedule.buckets[0], expectedBucketChannelSet);
-
-        expectedBucketChannelSet.clear();
-        expectedBucketChannelSet.add(5175);
-        assertBucketChannels(schedule.buckets[1], expectedBucketChannelSet);
-    }
-
-    /**
-     * Add 2 background scan requests with the second scan request having channels more than the
-     * max, ensure that the last bucket is split.
-     */
-    @Test
-    public void optimalScheduleShouldSplitBucketsWithNoBandChannelHelper() {
-        BackgroundScanScheduler scheduler = createSchedulerWithNoBandChannelHelper();
-
-        ArrayList<ScanSettings> requests = new ArrayList<>();
-        requests.add(createRequest(channelsToSpec(2400, 2450), 10000, 0, 20,
-                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
-        requests.add(createRequest(channelsToSpec(5150, 5175, 5600, 5650), 240000, 0, 20,
-                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN));
-
-        scheduler.setMaxBuckets(3);
-        scheduler.setMaxChannelsPerBucket(2);
-        scheduler.updateSchedule(requests);
-        WifiNative.ScanSettings schedule = scheduler.getSchedule();
-
-        assertEquals("base_period_ms", 10000, schedule.base_period_ms);
-        assertBuckets(schedule, 3);
-
-        assertEquals("scheduled bucket", 0, scheduler.getScheduledBucket(requests.get(0)));
-        assertEquals("scheduled bucket", 1, scheduler.getScheduledBucket(requests.get(1)));
-
-        Set<Integer> expectedBucketChannelSet = new ArraySet<>();
-        expectedBucketChannelSet.add(2400);
-        expectedBucketChannelSet.add(2450);
-        assertBucketChannels(schedule.buckets[0], expectedBucketChannelSet);
-
-        expectedBucketChannelSet.clear();
-        expectedBucketChannelSet.add(5150);
-        expectedBucketChannelSet.add(5175);
-        assertBucketChannels(schedule.buckets[1], expectedBucketChannelSet);
-
-        expectedBucketChannelSet.clear();
-        expectedBucketChannelSet.add(5600);
-        expectedBucketChannelSet.add(5650);
-        assertBucketChannels(schedule.buckets[2], expectedBucketChannelSet);
-    }
-
     protected Set<Integer> getAllChannels(BucketSettings bucket) {
         KnownBandsChannelCollection collection = mChannelHelper.createChannelCollection();
         collection.addChannels(bucket);
@@ -930,16 +823,6 @@
                 + expectedChannelSet, channelSet.containsAll(expectedChannelSet));
     }
 
-    private BackgroundScanScheduler createSchedulerWithNoBandChannelHelper() {
-        NoBandChannelHelper channelHelper = new NoBandChannelHelper();
-        BackgroundScanScheduler scheduler = new BackgroundScanScheduler(channelHelper);
-        scheduler.setMaxBuckets(DEFAULT_MAX_BUCKETS);
-        scheduler.setMaxChannelsPerBucket(DEFAULT_MAX_CHANNELS_PER_BUCKET);
-        scheduler.setMaxBatch(DEFAULT_MAX_BATCH);
-        scheduler.setMaxApPerScan(DEFAULT_MAX_AP_PER_SCAN);
-        return scheduler;
-    }
-
     private static int[] getPredefinedBuckets() {
         try {
             Field f = BackgroundScanScheduler.class.getDeclaredField("PREDEFINED_BUCKET_PERIODS");
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/ChannelHelperTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/ChannelHelperTest.java
index e2fd875..941c357 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/ChannelHelperTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/ChannelHelperTest.java
@@ -92,6 +92,27 @@
 
     /**
      * Unit tests for
+     * {@link com.android.server.wifi.scanner.ChannelHelper#bandToString}.
+     */
+    @Test
+    public void bandToString_ShouldReturnApproapriateString() {
+        assertEquals("unspecified", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_UNSPECIFIED));
+        assertEquals("24Ghz", ChannelHelper.bandToString(WifiScanner.WIFI_BAND_24_GHZ));
+        assertEquals("5Ghz (no DFS)", ChannelHelper.bandToString(WifiScanner.WIFI_BAND_5_GHZ));
+        assertEquals("5Ghz (DFS only)", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY));
+        assertEquals("5Ghz (DFS incl)", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_5_GHZ_WITH_DFS));
+        assertEquals("24Ghz & 5Ghz (no DFS)", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_BOTH));
+        assertEquals("24Ghz & 5Ghz (DFS incl)", ChannelHelper.bandToString(
+                WifiScanner.WIFI_BAND_BOTH_WITH_DFS));
+        assertEquals("invalid band", ChannelHelper.bandToString(-235342));
+    }
+
+    /**
+     * Unit tests for
      * {@link com.android.server.wifi.scanner.ChannelHelper.ChannelCollection}.
      */
     @SmallTest
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/NoBandChannelHelperTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/NoBandChannelHelperTest.java
deleted file mode 100644
index 145b214..0000000
--- a/tests/wifitests/src/com/android/server/wifi/scanner/NoBandChannelHelperTest.java
+++ /dev/null
@@ -1,402 +0,0 @@
-/*
- * Copyright (C) 2016 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.scanner;
-
-import static com.android.server.wifi.ScanTestUtil.bandIs;
-import static com.android.server.wifi.ScanTestUtil.channelsAre;
-import static com.android.server.wifi.ScanTestUtil.channelsToSpec;
-import static com.android.server.wifi.ScanTestUtil.createRequest;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.net.wifi.WifiScanner;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.wifi.WifiNative;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.experimental.runners.Enclosed;
-import org.junit.runner.RunWith;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-
-/**
- * Unit tests for {@link com.android.server.wifi.scanner.NoBandChannelHelper}.
- */
-@RunWith(Enclosed.class) // WARNING: tests cannot be declared in the outer class
-public class NoBandChannelHelperTest {
-    private static final int ALL_BANDS = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
-
-    /**
-     * Unit tests for
-     * {@link com.android.server.wifi.scanner.NoBandChannelHelper.estimateScanDuration}.
-     */
-    @SmallTest
-    public static class EstimateScanDurationTest {
-        NoBandChannelHelper mChannelHelper;
-
-        /**
-         * Called before each test
-         * Create a channel helper
-         */
-        @Before
-        public void setUp() throws Exception {
-            mChannelHelper = new NoBandChannelHelper();
-        }
-
-        /**
-         * check a settings object with a few channels
-         */
-        @Test
-        public void fewChannels() {
-            WifiScanner.ScanSettings testSettings = createRequest(channelsToSpec(2400, 2450, 5100),
-                    10000, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
-
-            assertEquals(ChannelHelper.SCAN_PERIOD_PER_CHANNEL_MS * 3,
-                    mChannelHelper.estimateScanDuration(testSettings));
-        }
-
-        /**
-         * check a settings object with a band
-         */
-        @Test
-        public void band() {
-            WifiScanner.ScanSettings testSettings = createRequest(WifiScanner.WIFI_BAND_24_GHZ,
-                    10000, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
-
-            assertTrue("Expected scan to take some time",
-                    mChannelHelper.estimateScanDuration(testSettings)
-                    >= ChannelHelper.SCAN_PERIOD_PER_CHANNEL_MS);
-        }
-    }
-
-    /**
-     * Unit tests for
-     * {@link com.android.server.wifi.scanner.NoBandChannelHelper.getAvailableScanChannels}.
-     */
-    @SmallTest
-    public static class GetAvailableScanChannelsTest {
-        NoBandChannelHelper mChannelHelper;
-
-        /**
-         * Called before each test
-         * Create a channel helper
-         */
-        @Before
-        public void setUp() throws Exception {
-            mChannelHelper = new NoBandChannelHelper();
-        }
-
-        /**
-         * Test that getting the channels for each band results in the expected empty list
-         */
-        @Test
-        public void eachBandValue() {
-            for (int band = WifiScanner.WIFI_BAND_24_GHZ;
-                    band <= WifiScanner.WIFI_BAND_BOTH_WITH_DFS; ++band) {
-                WifiScanner.ChannelSpec[] channels =
-                        mChannelHelper.getAvailableScanChannels(band);
-                assertEquals("expected zero channels", 0, channels.length);
-            }
-        }
-    }
-
-    /**
-     * Unit tests for
-     * {@link com.android.server.wifi.scanner.NoBandChannelHelper.settingsContainChannel}.
-     */
-    @SmallTest
-    public static class SettingsContainChannelTest {
-        NoBandChannelHelper mChannelHelper;
-
-        /**
-         * Called before each test
-         * Create a channel helper
-         */
-        @Before
-        public void setUp() throws Exception {
-            mChannelHelper = new NoBandChannelHelper();
-        }
-
-        /**
-         * check a settings object with no channels
-         */
-        @Test
-        public void emptySettings() {
-            WifiScanner.ScanSettings testSettings = createRequest(channelsToSpec(),
-                    10000, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
-
-            assertFalse(mChannelHelper.settingsContainChannel(testSettings, 2400));
-            assertFalse(mChannelHelper.settingsContainChannel(testSettings, 5150));
-            assertFalse(mChannelHelper.settingsContainChannel(testSettings, 5650));
-        }
-
-        /**
-         * check a settings object with some channels
-         */
-        @Test
-        public void settingsWithChannels() {
-            WifiScanner.ScanSettings testSettings = createRequest(channelsToSpec(2400, 5650),
-                    10000, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
-
-            assertTrue(mChannelHelper.settingsContainChannel(testSettings, 2400));
-            assertFalse(mChannelHelper.settingsContainChannel(testSettings, 5150));
-            assertTrue(mChannelHelper.settingsContainChannel(testSettings, 5650));
-        }
-
-        /**
-         * check a settings object with a band specified
-         */
-        @Test
-        public void settingsWithBand() {
-            WifiScanner.ScanSettings testSettings = createRequest(WifiScanner.WIFI_BAND_24_GHZ,
-                    10000, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
-
-            assertTrue(mChannelHelper.settingsContainChannel(testSettings, 2400));
-            assertTrue(mChannelHelper.settingsContainChannel(testSettings, 2450));
-            assertTrue(mChannelHelper.settingsContainChannel(testSettings, 5150));
-            assertTrue(mChannelHelper.settingsContainChannel(testSettings, 5650));
-        }
-
-        /**
-         * check a settings object with multiple bands specified
-         */
-        @Test
-        public void settingsWithMultiBand() {
-            WifiScanner.ScanSettings testSettings = createRequest(WifiScanner.WIFI_BAND_BOTH,
-                    10000, 0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
-
-            assertTrue(mChannelHelper.settingsContainChannel(testSettings, 2400));
-            assertTrue(mChannelHelper.settingsContainChannel(testSettings, 2450));
-            assertTrue(mChannelHelper.settingsContainChannel(testSettings, 5150));
-            assertTrue(mChannelHelper.settingsContainChannel(testSettings, 5650));
-        }
-    }
-
-    /**
-     * Unit tests for
-     * {@link com.android.server.wifi.scanner.NoBandChannelHelper.NoBandChannelCollection}.
-     */
-    @SmallTest
-    public static class NoBandsChannelCollectionTest {
-        ChannelHelper.ChannelCollection mChannelCollection;
-
-        /**
-         * Called before each test
-         * Create a collection to use for each test
-         */
-        @Before
-        public void setUp() throws Exception {
-            mChannelCollection = new NoBandChannelHelper().createChannelCollection();
-        }
-
-        /**
-         * Create an empty collection
-         */
-        @Test
-        public void empty() {
-            WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
-            mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
-            assertThat(bucketSettings, channelsAre());
-
-            assertEquals(Collections.<Integer>emptySet(),
-                    mChannelCollection.getScanFreqs());
-
-            assertTrue(mChannelCollection.isEmpty());
-            assertFalse(mChannelCollection.containsChannel(2400));
-            assertFalse(mChannelCollection.isAllChannels());
-        }
-
-        /**
-         * Add something to a collection and then clear it and make sure nothing is in it
-         */
-        @Test
-        public void clear() {
-            mChannelCollection.addBand(WifiScanner.WIFI_BAND_24_GHZ);
-            mChannelCollection.clear();
-
-            WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
-            mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
-            assertThat(bucketSettings, channelsAre());
-
-            assertEquals(Collections.<Integer>emptySet(),
-                    mChannelCollection.getScanFreqs());
-
-            assertTrue(mChannelCollection.isEmpty());
-            assertFalse(mChannelCollection.containsChannel(2400));
-            assertFalse(mChannelCollection.isAllChannels());
-        }
-
-        /**
-         * Add a single band to the collection
-         */
-        @Test
-        public void addBand() {
-            mChannelCollection.addBand(WifiScanner.WIFI_BAND_24_GHZ);
-
-            WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
-            mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
-            assertThat(bucketSettings, bandIs(ALL_BANDS));
-
-            assertNull(mChannelCollection.getScanFreqs());
-
-            assertFalse(mChannelCollection.isEmpty());
-            assertTrue(mChannelCollection.containsChannel(2400));
-            assertTrue(mChannelCollection.containsChannel(5150));
-            assertTrue(mChannelCollection.isAllChannels());
-        }
-
-        /**
-         * Add a single channel to the collection
-         */
-        @Test
-        public void addChannel_single() {
-            mChannelCollection.addChannel(2400);
-
-            WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
-            mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
-            assertThat(bucketSettings, channelsAre(2400));
-
-            assertEquals(new HashSet<Integer>(Arrays.asList(2400)),
-                    mChannelCollection.getScanFreqs());
-
-            assertFalse(mChannelCollection.isEmpty());
-            assertTrue(mChannelCollection.containsChannel(2400));
-            assertFalse(mChannelCollection.containsChannel(5150));
-            assertFalse(mChannelCollection.isAllChannels());
-        }
-
-        /**
-         * Add a multiple channels to the collection
-         */
-        @Test
-        public void addChannel_multiple() {
-            mChannelCollection.addChannel(2400);
-            mChannelCollection.addChannel(2450);
-
-            WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
-            mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
-            assertThat(bucketSettings, channelsAre(2400, 2450));
-
-            assertEquals(new HashSet<Integer>(Arrays.asList(2400, 2450)),
-                    mChannelCollection.getScanFreqs());
-
-            assertFalse(mChannelCollection.isEmpty());
-            assertTrue(mChannelCollection.containsChannel(2400));
-            assertFalse(mChannelCollection.containsChannel(5150));
-            assertFalse(mChannelCollection.isAllChannels());
-        }
-
-        /**
-         * Add a band and channel that is on that band
-         */
-        @Test
-        public void addChannel_and_addBand_sameBand() {
-            mChannelCollection.addBand(WifiScanner.WIFI_BAND_24_GHZ);
-            mChannelCollection.addChannel(2400);
-
-            WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
-            mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
-            assertThat(bucketSettings, bandIs(ALL_BANDS));
-
-            assertNull(mChannelCollection.getScanFreqs());
-
-            assertFalse(mChannelCollection.isEmpty());
-            assertTrue(mChannelCollection.containsChannel(2400));
-            assertTrue(mChannelCollection.containsChannel(5150));
-            assertTrue(mChannelCollection.isAllChannels());
-        }
-
-        /**
-         * Add a band and channel that is not that band
-         */
-        @Test
-        public void addChannel_and_addBand_withDifferentBandChannel() {
-            mChannelCollection.addBand(WifiScanner.WIFI_BAND_24_GHZ);
-            mChannelCollection.addChannel(5150);
-
-            WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
-            mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
-            assertThat(bucketSettings, bandIs(ALL_BANDS));
-
-            assertNull(mChannelCollection.getScanFreqs());
-
-            assertFalse(mChannelCollection.isEmpty());
-            assertTrue(mChannelCollection.containsChannel(2400));
-            assertTrue(mChannelCollection.containsChannel(5150));
-            assertTrue(mChannelCollection.isAllChannels());
-        }
-
-        /**
-         * Add a band that should contain all channels
-         */
-        @Test
-        public void addChannel_and_addBand_all() {
-            mChannelCollection.addBand(WifiScanner.WIFI_BAND_BOTH_WITH_DFS);
-            mChannelCollection.addChannel(5150);
-
-            WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
-            mChannelCollection.fillBucketSettings(bucketSettings, Integer.MAX_VALUE);
-            assertThat(bucketSettings, bandIs(WifiScanner.WIFI_BAND_BOTH_WITH_DFS));
-
-            assertNull(mChannelCollection.getScanFreqs());
-
-            assertFalse(mChannelCollection.isEmpty());
-            assertTrue(mChannelCollection.containsChannel(2400));
-            assertTrue(mChannelCollection.containsChannel(5150));
-            assertTrue(mChannelCollection.containsChannel(5600));
-            assertTrue(mChannelCollection.isAllChannels());
-        }
-
-        /**
-         * Add enough channels on a single band that the max channels is exceeded
-         */
-        @Test
-        public void addChannel_exceedMaxChannels() {
-            mChannelCollection.addChannel(5600);
-            mChannelCollection.addChannel(5650);
-            mChannelCollection.addChannel(5660);
-
-            WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
-            mChannelCollection.fillBucketSettings(bucketSettings, 2);
-            assertThat(bucketSettings, bandIs(ALL_BANDS));
-            assertFalse(mChannelCollection.isAllChannels()); // can't determine from just channels
-        }
-
-        /**
-         * Add enough channels across multiple bands that the max channels is exceeded
-         */
-        @Test
-        public void addChannel_exceedMaxChannelsOnMultipleBands() {
-            mChannelCollection.addChannel(2400);
-            mChannelCollection.addChannel(2450);
-            mChannelCollection.addChannel(5150);
-
-            WifiNative.BucketSettings bucketSettings = new WifiNative.BucketSettings();
-            mChannelCollection.fillBucketSettings(bucketSettings, 2);
-            assertThat(bucketSettings, bandIs(ALL_BANDS));
-            assertFalse(mChannelCollection.isAllChannels()); // can't determine from just channels
-        }
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
index 01835cd..4ffc66d 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
@@ -74,7 +74,6 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.regex.Pattern;
 
@@ -1722,21 +1721,6 @@
         return Pair.create(requestSettings, nativeSettings);
     }
 
-    private Pair<WifiScanner.ScanSettings, WifiNative.ScanSettings> createScanSettingsForSwPno()
-            throws Exception {
-        Pair<WifiScanner.ScanSettings, WifiNative.ScanSettings> settingsPair =
-                createScanSettingsForHwPno();
-
-        WifiScanner.ScanSettings requestSettings = settingsPair.first;
-        WifiNative.ScanSettings nativeSettings = settingsPair.second;
-        // reportEvents field is overridden for SW PNO
-        for (int i = 0; i < nativeSettings.buckets.length; i++) {
-            nativeSettings.buckets[i].report_events = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
-                    | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
-        }
-        return Pair.create(requestSettings, nativeSettings);
-    }
-
     private Pair<WifiScanner.PnoSettings, WifiNative.PnoSettings> createPnoSettings(
             ScanResults results)
             throws Exception {
@@ -1832,10 +1816,9 @@
         mLooper.dispatchAll();
     }
 
-    private void expectHwPnoScanWithNoBackgroundScan(InOrder order, Handler handler, int requestId,
+    private void expectHwPnoScan(InOrder order, Handler handler, int requestId,
             WifiNative.PnoSettings nativeSettings, ScanResults results) {
         when(mWifiScannerImpl.isHwPnoSupported(anyBoolean())).thenReturn(true);
-        when(mWifiScannerImpl.shouldScheduleBackgroundScanForHwPno()).thenReturn(false);
 
         when(mWifiScannerImpl.setHwPnoList(any(WifiNative.PnoSettings.class),
                 any(WifiNative.PnoEventHandler.class))).thenReturn(true);
@@ -1846,47 +1829,6 @@
         mLooper.dispatchAll();
     }
 
-    private void expectHwPnoScanWithBackgroundScan(InOrder order, Handler handler, int requestId,
-            WifiNative.ScanSettings nativeScanSettings,
-            WifiNative.PnoSettings nativePnoSettings, ScanResults results) {
-        when(mWifiScannerImpl.isHwPnoSupported(anyBoolean())).thenReturn(true);
-        when(mWifiScannerImpl.shouldScheduleBackgroundScanForHwPno()).thenReturn(true);
-
-        when(mWifiScannerImpl.setHwPnoList(any(WifiNative.PnoSettings.class),
-                any(WifiNative.PnoEventHandler.class))).thenReturn(true);
-        when(mWifiScannerImpl.startBatchedScan(any(WifiNative.ScanSettings.class),
-                any(WifiNative.ScanEventHandler.class))).thenReturn(true);
-        mLooper.dispatchAll();
-        WifiNative.PnoEventHandler eventHandler = verifyHwPno(order, nativePnoSettings);
-        verifySuccessfulResponse(order, handler, requestId);
-        verifyStartBackgroundScan(order, nativeScanSettings);
-        eventHandler.onPnoNetworkFound(results.getRawScanResults());
-        mLooper.dispatchAll();
-    }
-
-    private void expectHwPnoScanWithBackgroundScanWithNoIE(InOrder order, Handler handler,
-            int requestId, WifiNative.ScanSettings nativeBackgroundScanSettings,
-            WifiNative.ScanSettings nativeSingleScanSettings,
-            WifiNative.PnoSettings nativePnoSettings, ScanResults results) {
-        when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
-                any(WifiNative.ScanEventHandler.class))).thenReturn(true);
-
-        expectHwPnoScanWithBackgroundScan(order, handler, requestId, nativeBackgroundScanSettings,
-                nativePnoSettings, results);
-        WifiNative.ScanEventHandler eventHandler =
-                verifyStartSingleScan(order, nativeSingleScanSettings);
-        when(mWifiScannerImpl.getLatestSingleScanResults()).thenReturn(results.getScanData());
-        eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
-        mLooper.dispatchAll();
-    }
-    private void expectSwPnoScan(InOrder order, WifiNative.ScanSettings nativeScanSettings,
-            ScanResults results) {
-        when(mWifiScannerImpl.isHwPnoSupported(anyBoolean())).thenReturn(false);
-        when(mWifiScannerImpl.shouldScheduleBackgroundScanForHwPno()).thenReturn(true);
-
-        expectSuccessfulBackgroundScan(order, nativeScanSettings, results);
-    }
-
     /**
      * Tests wificond PNO scan when the PNO scan results contain IE info. This ensures that the
      * PNO scan results are plumbed back to the client as a PNO network found event.
@@ -1907,88 +1849,7 @@
                 createPnoSettings(scanResults);
 
         sendPnoScanRequest(controlChannel, requestId, scanSettings.first, pnoSettings.first);
-        expectHwPnoScanWithNoBackgroundScan(order, handler, requestId, pnoSettings.second,
-                scanResults);
-        verifyPnoNetworkFoundReceived(order, handler, requestId, scanResults.getRawScanResults());
-    }
-
-    /**
-     * Tests Hal ePNO scan when the PNO scan results contain IE info. This ensures that the
-     * PNO scan results are plumbed back to the client as a PNO network found event.
-     */
-    @Test
-    public void testSuccessfulHwPnoScanWithBackgroundScan() throws Exception {
-        startServiceAndLoadDriver();
-        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
-        Handler handler = mock(Handler.class);
-        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
-        InOrder order = inOrder(handler, mWifiScannerImpl);
-        int requestId = 12;
-
-        ScanResults scanResults = createScanResultsForPno();
-        Pair<WifiScanner.ScanSettings, WifiNative.ScanSettings> scanSettings =
-                createScanSettingsForHwPno();
-        Pair<WifiScanner.PnoSettings, WifiNative.PnoSettings> pnoSettings =
-                createPnoSettings(scanResults);
-
-        sendPnoScanRequest(controlChannel, requestId, scanSettings.first, pnoSettings.first);
-        expectHwPnoScanWithBackgroundScan(order, handler, requestId, scanSettings.second,
-                pnoSettings.second, scanResults);
-        verifyPnoNetworkFoundReceived(order, handler, requestId, scanResults.getRawScanResults());
-    }
-
-    /**
-     * Tests Hal ePNO scan when the PNO scan results don't contain IE info. This ensures that the
-     * single scan results are plumbed back to the client as a PNO network found event.
-     */
-    @Test
-    public void testSuccessfulHwPnoScanWithBackgroundScanWithNoIE() throws Exception {
-        startServiceAndLoadDriver();
-        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
-        Handler handler = mock(Handler.class);
-        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
-        InOrder order = inOrder(handler, mWifiScannerImpl);
-        int requestId = 12;
-
-        ScanResults scanResults = createScanResultsForPnoWithNoIE();
-        Pair<WifiScanner.ScanSettings, WifiNative.ScanSettings> scanSettings =
-                createScanSettingsForHwPno();
-        Pair<WifiScanner.PnoSettings, WifiNative.PnoSettings> pnoSettings =
-                createPnoSettings(scanResults);
-
-        sendPnoScanRequest(controlChannel, requestId, scanSettings.first, pnoSettings.first);
-        expectHwPnoScanWithBackgroundScanWithNoIE(order, handler, requestId, scanSettings.second,
-                computeSingleScanNativeSettings(scanSettings.first), pnoSettings.second,
-                scanResults);
-
-        ArrayList<ScanResult> sortScanList =
-                new ArrayList<ScanResult>(Arrays.asList(scanResults.getRawScanResults()));
-        Collections.sort(sortScanList, WifiScannerImpl.SCAN_RESULT_SORT_COMPARATOR);
-        verifyPnoNetworkFoundReceived(order, handler, requestId,
-                sortScanList.toArray(new ScanResult[sortScanList.size()]));
-    }
-
-    /**
-     * Tests SW PNO scan. This ensures that the background scan results are plumbed back to the
-     * client as a PNO network found event.
-     */
-    @Test
-    public void testSuccessfulSwPnoScan() throws Exception {
-        startServiceAndLoadDriver();
-        mWifiScanningServiceImpl.setWifiHandlerLogForTest(mLog);
-        Handler handler = mock(Handler.class);
-        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
-        InOrder order = inOrder(handler, mWifiScannerImpl);
-        int requestId = 12;
-
-        ScanResults scanResults = createScanResultsForPno();
-        Pair<WifiScanner.ScanSettings, WifiNative.ScanSettings> scanSettings =
-                createScanSettingsForSwPno();
-        Pair<WifiScanner.PnoSettings, WifiNative.PnoSettings> pnoSettings =
-                createPnoSettings(scanResults);
-
-        sendPnoScanRequest(controlChannel, requestId, scanSettings.first, pnoSettings.first);
-        expectSwPnoScan(order, scanSettings.second, scanResults);
+        expectHwPnoScan(order, handler, requestId, pnoSettings.second, scanResults);
         verifyPnoNetworkFoundReceived(order, handler, requestId, scanResults.getRawScanResults());
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
index 974cf66..15dfccc 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WificondPnoScannerTest.java
@@ -17,7 +17,7 @@
 package com.android.server.wifi.scanner;
 
 import static com.android.server.wifi.ScanTestUtil.NativeScanSettingsBuilder;
-import static com.android.server.wifi.ScanTestUtil.assertScanDataEquals;
+import static com.android.server.wifi.ScanTestUtil.setupMockChannels;
 
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
@@ -44,7 +44,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Arrays;
 import java.util.Set;
 
 /**
@@ -71,6 +70,11 @@
         mWifiMonitor = new MockWifiMonitor();
         mResources = new MockResources();
 
+        setupMockChannels(mWifiNative,
+                new int[]{2400, 2450},
+                new int[]{5150, 5175},
+                new int[]{5600, 5650});
+
         when(mWifiNative.getInterfaceName()).thenReturn("a_test_interface_name");
         when(mContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
@@ -98,119 +102,6 @@
     }
 
     /**
-     * Verify that we pause & resume HW PNO scan when a single scan is scheduled and invokes the
-     * OnPnoNetworkFound callback when the scan results are received.
-     */
-    @Test
-    public void pauseResumeHwDisconnectedPnoScanForSingleScan() {
-        createScannerWithHwPnoScanSupport();
-
-        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
-        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(false);
-        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
-        WifiNative.ScanSettings settings = createDummyScanSettings();
-        ScanResults scanResults = createDummyScanResults(true);
-
-        InOrder order = inOrder(eventHandler, mWifiNative);
-        // Start PNO scan
-        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
-        // Start single scan
-        assertTrue(mScanner.startSingleScan(settings, eventHandler));
-        // Verify that the PNO scan was paused and single scan runs successfully
-        expectSuccessfulSingleScanWithHwPnoEnabled(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), scanResults);
-        verifyNoMoreInteractions(eventHandler);
-
-        order = inOrder(pnoEventHandler, mWifiNative);
-        // Resume PNO scan after the single scan results are received and PNO monitor debounce
-        // alarm fires.
-        assertTrue("dispatch pno monitor alarm",
-                mAlarmManager.dispatch(
-                        WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
-        assertEquals("dispatch message after alarm", 1, mLooper.dispatchAll());
-        // Now verify that PNO scan is resumed successfully
-        expectSuccessfulHwDisconnectedPnoScan(order, pnoSettings, pnoEventHandler, scanResults);
-        verifyNoMoreInteractions(pnoEventHandler);
-    }
-
-    /**
-     * Verify that the SW disconnected PNO scan triggers a background scan and invokes the
-     * background scan callbacks when scan results are received.
-     */
-    @Test
-    public void startSwDisconnectedPnoScan() {
-        createScannerWithSwPnoScanSupport();
-        doSuccessfulSwPnoScanTest(false);
-    }
-
-    /**
-     * Verify that the HW connected PNO scan triggers a background scan and invokes the
-     * background scan callbacks when scan results are received.
-     */
-    @Test
-    public void startHwConnectedPnoScan() {
-        createScannerWithHwPnoScanSupport();
-        doSuccessfulSwPnoScanTest(true);
-    }
-
-    /**
-     * Verify that the SW connected PNO scan triggers a background scan and invokes the
-     * background scan callbacks when scan results are received.
-     */
-    @Test
-    public void startSwConnectedPnoScan() {
-        createScannerWithSwPnoScanSupport();
-        doSuccessfulSwPnoScanTest(true);
-    }
-
-    /**
-     * Verify that the HW PNO delayed failure cleans up the scan settings cleanly.
-     * 1. Start Hw PNO.
-     * 2. Start Single Scan which should pause PNO scan.
-     * 3. Fail the PNO scan resume and verify that the OnPnoScanFailed callback is invoked.
-     * 4. Now restart a new PNO scan to ensure that the failure was cleanly handled.
-     */
-    @Test
-    public void delayedHwDisconnectedPnoScanFailure() {
-        createScannerWithHwPnoScanSupport();
-
-        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
-        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(false);
-        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
-        WifiNative.ScanSettings settings = createDummyScanSettings();
-        ScanResults scanResults = createDummyScanResults(true);
-
-        InOrder order = inOrder(eventHandler, mWifiNative);
-        // Start PNO scan
-        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
-        // Start single scan
-        assertTrue(mScanner.startSingleScan(settings, eventHandler));
-        // Verify that the PNO scan was paused and single scan runs successfully
-        expectSuccessfulSingleScanWithHwPnoEnabled(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ), scanResults);
-        verifyNoMoreInteractions(eventHandler);
-
-        // Fail the PNO resume and check that the OnPnoScanFailed callback is invoked.
-        order = inOrder(pnoEventHandler, mWifiNative);
-        when(mWifiNative.startPnoScan(any(WifiNative.PnoSettings.class))).thenReturn(false);
-        assertTrue("dispatch pno monitor alarm",
-                mAlarmManager.dispatch(
-                        WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
-        assertEquals("dispatch message after alarm", 1, mLooper.dispatchAll());
-        order.verify(pnoEventHandler).onPnoScanFailed();
-        verifyNoMoreInteractions(pnoEventHandler);
-
-        // Add a new PNO scan request
-        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
-        assertTrue("dispatch pno monitor alarm",
-                mAlarmManager.dispatch(
-                        WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
-        assertEquals("dispatch message after alarm", 1, mLooper.dispatchAll());
-        expectSuccessfulHwDisconnectedPnoScan(order, pnoSettings, pnoEventHandler, scanResults);
-        verifyNoMoreInteractions(pnoEventHandler);
-    }
-
-    /**
      * Verify that the HW PNO scan stop failure still resets the PNO scan state.
      * 1. Start Hw PNO.
      * 2. Stop Hw PNO scan which raises a stop command to WifiNative which is failed.
@@ -229,17 +120,11 @@
         // Fail the PNO stop.
         when(mWifiNative.stopPnoScan()).thenReturn(false);
         assertTrue(mScanner.resetHwPnoList());
-        assertTrue("dispatch pno monitor alarm",
-                mAlarmManager.dispatch(
-                        WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
         mLooper.dispatchAll();
         verify(mWifiNative).stopPnoScan();
 
         // Add a new PNO scan request and ensure it runs successfully.
         startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
-        assertTrue("dispatch pno monitor alarm",
-                mAlarmManager.dispatch(
-                        WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
         mLooper.dispatchAll();
         InOrder order = inOrder(pnoEventHandler, mWifiNative);
         ScanResults scanResults = createDummyScanResults(false);
@@ -247,111 +132,10 @@
         verifyNoMoreInteractions(pnoEventHandler);
     }
 
-    /**
-     * Verify that the HW PNO scan is forcefully stopped (bypass debounce logic) and restarted when
-     * settings change.
-     * 1. Start Hw PNO.
-     * 2. Stop Hw PNO .
-     * 3. Now restart a new PNO scan with different settings.
-     * 4. Ensure that the stop was issued before we start again.
-     */
-    @Test
-    public void forceRestartHwDisconnectedPnoScanWhenSettingsChange() {
-        createScannerWithHwPnoScanSupport();
-
-        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
-        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(false);
-        InOrder order = inOrder(pnoEventHandler, mWifiNative);
-
-        // Start PNO scan
-        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
-        expectHwDisconnectedPnoScanStart(order, pnoSettings);
-
-        // Stop PNO now. This should trigger the debounce timer and not stop PNO.
-        assertTrue(mScanner.resetHwPnoList());
-        assertTrue(mAlarmManager.isPending(
-                WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
-        order.verify(mWifiNative, never()).stopPnoScan();
-
-        // Now restart PNO scan with an extra network in settings.
-        pnoSettings.networkList =
-                Arrays.copyOf(pnoSettings.networkList, pnoSettings.networkList.length + 1);
-        pnoSettings.networkList[pnoSettings.networkList.length - 1] =
-                createDummyPnoNetwork("ssid_pno_new");
-        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
-
-        // This should bypass the debounce timer and stop PNO scan immediately and then start
-        // a new debounce timer for the start.
-        order.verify(mWifiNative).stopPnoScan();
-
-        // Trigger the debounce timer and ensure we start PNO scan again.
-        mAlarmManager.dispatch(WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG);
-        mLooper.dispatchAll();
-        order.verify(mWifiNative).startPnoScan(pnoSettings);
-    }
-
-    /**
-     * Verify that the HW PNO scan is not forcefully stopped (bypass debounce logic) when
-     * settings don't change.
-     * 1. Start Hw PNO.
-     * 2. Stop Hw PNO .
-     * 3. Now restart a new PNO scan with same settings.
-     * 4. Ensure that the stop was never issued.
-     */
-    @Test
-    public void noForceRestartHwDisconnectedPnoScanWhenNoSettingsChange() {
-        createScannerWithHwPnoScanSupport();
-
-        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
-        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(false);
-        InOrder order = inOrder(pnoEventHandler, mWifiNative);
-
-        // Start PNO scan
-        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
-        expectHwDisconnectedPnoScanStart(order, pnoSettings);
-
-        // Stop PNO now. This should trigger the debounce timer and not stop PNO.
-        assertTrue(mScanner.resetHwPnoList());
-        assertTrue(mAlarmManager.isPending(
-                WificondScannerImpl.HwPnoDebouncer.PNO_DEBOUNCER_ALARM_TAG));
-        order.verify(mWifiNative, never()).stopPnoScan();
-
-        // Now restart PNO scan with the same settings.
-        startSuccessfulPnoScan(null, pnoSettings, null, pnoEventHandler);
-
-        // Trigger the debounce timer and ensure that we neither stop/start.
-        mLooper.dispatchAll();
-        order.verify(mWifiNative, never()).startPnoScan(any(WifiNative.PnoSettings.class));
-        order.verify(mWifiNative, never()).stopPnoScan();
-    }
-
-    private void doSuccessfulSwPnoScanTest(boolean isConnectedPno) {
-        WifiNative.PnoEventHandler pnoEventHandler = mock(WifiNative.PnoEventHandler.class);
-        WifiNative.PnoSettings pnoSettings = createDummyPnoSettings(isConnectedPno);
-        WifiNative.ScanEventHandler scanEventHandler = mock(WifiNative.ScanEventHandler.class);
-        WifiNative.ScanSettings scanSettings = createDummyScanSettings();
-        ScanResults scanResults = createDummyScanResults(false);
-
-        InOrder order = inOrder(scanEventHandler, mWifiNative);
-
-        // Start PNO scan
-        startSuccessfulPnoScan(scanSettings, pnoSettings, scanEventHandler, pnoEventHandler);
-
-        expectSuccessfulSwPnoScan(order, scanEventHandler, scanResults);
-
-        verifyNoMoreInteractions(pnoEventHandler);
-    }
-
     private void createScannerWithHwPnoScanSupport() {
         mResources.setBoolean(R.bool.config_wifi_background_scan_support, true);
         mScanner = new WificondScannerImpl(mContext, mWifiNative, mWifiMonitor,
-                mLooper.getLooper(), mClock);
-    }
-
-    private void createScannerWithSwPnoScanSupport() {
-        mResources.setBoolean(R.bool.config_wifi_background_scan_support, false);
-        mScanner = new WificondScannerImpl(mContext, mWifiNative, mWifiMonitor,
-                mLooper.getLooper(), mClock);
+                new WificondChannelHelper(mWifiNative), mLooper.getLooper(), mClock);
     }
 
     private WifiNative.PnoNetwork createDummyPnoNetwork(String ssid) {
@@ -369,12 +153,15 @@
         return pnoSettings;
     }
 
-    private WifiNative.ScanSettings createDummyScanSettings() {
+    private WifiNative.ScanSettings createDummyScanSettings(boolean allChannelsScanned) {
         WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
                 .withBasePeriod(10000)
                 .withMaxApPerScan(10)
-                .addBucketWithBand(10000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_24_GHZ)
+                .addBucketWithBand(
+                    10000,
+                    WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
+                    allChannelsScanned
+                            ? WifiScanner.WIFI_BAND_BOTH_WITH_DFS : WifiScanner.WIFI_BAND_24_GHZ)
                 .build();
         return settings;
     }
@@ -393,14 +180,7 @@
         when(mWifiNative.startPnoScan(any(WifiNative.PnoSettings.class))).thenReturn(true);
         when(mWifiNative.stopPnoScan()).thenReturn(true);
 
-        if (mScanner.isHwPnoSupported(pnoSettings.isConnected)) {
-            // This should happen only for HW PNO scan
-            assertTrue(mScanner.setHwPnoList(pnoSettings, pnoEventHandler));
-        } else {
-            // This should happen only for SW PNO scan
-            assertTrue(mScanner.startBatchedScan(scanSettings, scanEventHandler));
-
-        }
+        assertTrue(mScanner.setHwPnoList(pnoSettings, pnoEventHandler));
     }
 
     private Set<Integer> expectedBandScanFreqs(int band) {
@@ -440,56 +220,4 @@
         order.verify(eventHandler).onPnoNetworkFound(scanResults.getRawScanResults());
     }
 
-    /**
-     * Verify that the single scan results were delivered and that the PNO scan was paused and
-     * resumed either side of it.
-     */
-    private void expectSuccessfulSingleScanWithHwPnoEnabled(InOrder order,
-            WifiNative.ScanEventHandler eventHandler, Set<Integer> expectedScanFreqs,
-            ScanResults scanResults) {
-        // Pause PNO scan first
-        order.verify(mWifiNative).stopPnoScan();
-
-        order.verify(mWifiNative).scan(eq(expectedScanFreqs), any(Set.class));
-
-        when(mWifiNative.getPnoScanResults()).thenReturn(scanResults.getScanDetailArrayList());
-        when(mWifiNative.getScanResults()).thenReturn(scanResults.getScanDetailArrayList());
-
-        // Notify scan has finished
-        mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_RESULTS_EVENT);
-        assertEquals("dispatch message after results event", 1, mLooper.dispatchAll());
-
-        order.verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
-        assertScanDataEquals(scanResults.getScanData(), mScanner.getLatestSingleScanResults());
-    }
-
-    /**
-     * Verify that the SW PNO scan was successfully started. This could either be disconnected
-     * or connected PNO.
-     * This is basically ensuring that the background scan runs successfully and returns the
-     * expected result.
-     */
-    private void expectSuccessfulSwPnoScan(InOrder order,
-            WifiNative.ScanEventHandler eventHandler, ScanResults scanResults) {
-
-        // Verify scan started
-        order.verify(mWifiNative).scan(any(), any(Set.class));
-
-        // Make sure that HW PNO scan was not started
-        verify(mWifiNative, never()).startPnoScan(any(WifiNative.PnoSettings.class));
-
-        // Setup scan results
-        when(mWifiNative.getScanResults()).thenReturn(scanResults.getScanDetailArrayList());
-        when(mWifiNative.getPnoScanResults()).thenReturn(scanResults.getScanDetailArrayList());
-
-        // Notify scan has finished
-        mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_RESULTS_EVENT);
-        assertEquals("dispatch message after results event", 1, mLooper.dispatchAll());
-
-        // Verify background scan results delivered
-        order.verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
-        WifiScanner.ScanData[] scanData = mScanner.getLatestBatchedScanResults(true);
-        WifiScanner.ScanData lastScanData = scanData[scanData.length - 1];
-        assertScanDataEquals(scanResults.getScanData(), lastScanData);
-    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
index d337cf1..3657e50 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WificondScannerTest.java
@@ -17,31 +17,23 @@
 package com.android.server.wifi.scanner;
 
 import static com.android.server.wifi.ScanTestUtil.NativeScanSettingsBuilder;
-import static com.android.server.wifi.ScanTestUtil.assertScanDatasEquals;
-import static com.android.server.wifi.ScanTestUtil.createFreqSet;
+import static com.android.server.wifi.ScanTestUtil.setupMockChannels;
 
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
-import android.net.wifi.ScanResult;
 import android.net.wifi.WifiScanner;
-import android.net.wifi.WifiSsid;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.server.wifi.ScanDetail;
-import com.android.server.wifi.ScanResults;
-import com.android.server.wifi.WifiMonitor;
 import com.android.server.wifi.WifiNative;
 import com.android.server.wifi.scanner.ChannelHelper.ChannelCollection;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.InOrder;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
-import java.util.ArrayList;
 import java.util.Set;
 import java.util.regex.Pattern;
 
@@ -53,8 +45,12 @@
 
     @Before
     public void setup() throws Exception {
+        setupMockChannels(mWifiNative,
+                new int[]{2400, 2450},
+                new int[]{5150, 5175},
+                new int[]{5600, 5650});
         mScanner = new WificondScannerImpl(mContext, mWifiNative, mWifiMonitor,
-                mLooper.getLooper(), mClock);
+                new WificondChannelHelper(mWifiNative), mLooper.getLooper(), mClock);
     }
 
     /**
@@ -90,506 +86,6 @@
         verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_FAILED);
     }
 
-    @Test
-    public void backgroundScanSuccessSingleBucket() {
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(10000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .build();
-
-        ScanPeriod[] expectedPeriods = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(0, 2400),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(1, 2450),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ))
-        };
-
-        doSuccessfulTest(settings, expectedPeriods);
-    }
-
-    @Test
-    public void backgroundScanMaxApExceeded() {
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(2)
-                .addBucketWithBand(10000,
-                        WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
-                        | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .build();
-
-        ScanPeriod[] expectedPeriods = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.FULL_AND_RESULT,
-                    ScanResults.createOverflowing(0, 2,
-                            new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 1"),
-                                    "00:00:00:00:00:00", "", -70, 2450, Long.MAX_VALUE, 0),
-                            new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 2"),
-                                    "AA:BB:CC:DD:EE:FF", "", -66, 2400, Long.MAX_VALUE, 0),
-                            new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 3"),
-                                    "00:00:00:00:00:00", "", -80, 2450, Long.MAX_VALUE, 0),
-                            new ScanDetail(WifiSsid.createFromAsciiEncoded("TEST AP 4"),
-                                    "AA:BB:CC:11:22:33", "", -65, 2450, Long.MAX_VALUE, 0)),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ))
-        };
-
-        doSuccessfulTest(settings, expectedPeriods);
-    }
-
-    @Test
-    public void backgroundScanSuccessWithFullScanResults() {
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(10000,
-                        WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
-                        | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .build();
-
-        ScanPeriod[] expectedPeriods = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.FULL_AND_RESULT,
-                    ScanResults.create(0, 2400, 2450, 2400, 2400),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.FULL_AND_RESULT,
-                    ScanResults.create(1, 2450, 2400, 2450, 2400),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ))
-        };
-
-        doSuccessfulTest(settings, expectedPeriods);
-    }
-
-    @Test
-    public void backgroundScanSuccessWithMixedFullResultsAndNot() {
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(10000,
-                        WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .addBucketWithBand(20000,
-                        WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
-                        | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT,
-                        WifiScanner.WIFI_BAND_5_GHZ)
-                .build();
-
-        ScanPeriod[] expectedPeriods = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.FULL_AND_RESULT,
-                    ScanResults.create(0, 2400, 2450, 2400, 5175),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_BOTH)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(1, 2450, 2400, 2450, 2400),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.FULL_AND_RESULT,
-                    ScanResults.create(2, 2450, 2400, 2450, 5150),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_BOTH))
-        };
-
-        doSuccessfulTest(settings, expectedPeriods);
-    }
-
-    @Test
-    public void backgroundScanNoBatch() {
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(10000,
-                        WifiScanner.REPORT_EVENT_NO_BATCH,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .build();
-
-        ScanPeriod[] expectedPeriods = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.NONE,
-                    ScanResults.create(0, 2400, 2400, 2400),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.NONE,
-                    ScanResults.create(1, 2400, 2400, 2450),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.NONE,
-                    ScanResults.create(2, 2400, 2450, 2400),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ))
-        };
-
-        doSuccessfulTest(settings, expectedPeriods);
-    }
-
-    @Test
-    public void backgroundScanBatch() {
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .withMaxScansToCache(3)
-                .addBucketWithBand(10000,
-                        WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .build();
-
-        ScanResults[] periodResults = new ScanResults[] {
-            ScanResults.create(0, 2400, 2400, 2400),
-            ScanResults.create(1, 2400, 2400, 2400, 2400),
-            ScanResults.create(2, 2450),
-            ScanResults.create(3, 2400, 2400),
-            ScanResults.create(4, 2400, 2450),
-            ScanResults.create(5, 2450)
-        };
-
-        ScanPeriod[] expectedPeriods = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.NONE,
-                    periodResults[0],
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.NONE,
-                    periodResults[1],
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    new ScanResults[] {
-                        periodResults[0],
-                        periodResults[1],
-                        periodResults[2]
-                    },
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.NONE,
-                    periodResults[3],
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.NONE,
-                    periodResults[4],
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    new ScanResults[] {
-                        periodResults[3],
-                        periodResults[4],
-                        periodResults[5]
-                    },
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ))
-        };
-
-        doSuccessfulTest(settings, expectedPeriods);
-    }
-
-    @Test
-    public void backgroundScanSuccessWithMultipleBuckets() {
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(10000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .addBucketWithBand(30000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_BOTH)
-                .addBucketWithChannels(20000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        5650)
-                .build();
-
-        ScanPeriod[] expectedPeriods = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(0, 2400, 5175),
-                    expectedBandAndChannelScanFreqs(WifiScanner.WIFI_BAND_BOTH, 5650)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(1, 2400),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(2, 2450, 5650),
-                    expectedBandAndChannelScanFreqs(WifiScanner.WIFI_BAND_24_GHZ, 5650)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(3, 2450, 5175),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_BOTH)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(4, new int[0]),
-                    expectedBandAndChannelScanFreqs(WifiScanner.WIFI_BAND_24_GHZ, 5650)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(5, 2400, 2400, 2400, 2450),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(6, 5150, 5650, 5650),
-                    expectedBandAndChannelScanFreqs(WifiScanner.WIFI_BAND_BOTH, 5650))
-        };
-
-        doSuccessfulTest(settings, expectedPeriods);
-    }
-
-    @Test
-    public void backgroundScanSuccessWithMultipleBucketsWhereAPeriodDoesNotRequireAScan() {
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(30000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_BOTH)
-                .addBucketWithChannels(20000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        5650)
-                .build();
-
-        // expected scan frequencies
-        ScanPeriod[] expectedPeriods = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(0, 2400, 5175),
-                    expectedBandAndChannelScanFreqs(WifiScanner.WIFI_BAND_BOTH, 5650)),
-            null,
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(1, 5650),
-                    createFreqSet(5650)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(2, 2450, 5175),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_BOTH)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(3, 5650, 5650, 5650),
-                    createFreqSet(5650)),
-            null,
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(4, 2400, 2400, 2400, 2450),
-                    expectedBandAndChannelScanFreqs(WifiScanner.WIFI_BAND_BOTH, 5650))
-        };
-
-        doSuccessfulTest(settings, expectedPeriods);
-    }
-
-    @Test
-    public void backgroundScanStartFailed() {
-        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(10000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .build();
-
-        InOrder order = inOrder(eventHandler, mWifiNative);
-
-        // All scans fail
-        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(false);
-
-        // Start scan
-        mScanner.startBatchedScan(settings, eventHandler);
-
-        assertBackgroundPeriodAlarmPending();
-
-        expectFailedScanStart(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ));
-
-        // Fire alarm to start next scan
-        dispatchBackgroundPeriodAlarm();
-
-        assertBackgroundPeriodAlarmPending();
-
-        expectFailedScanStart(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ));
-
-        verifyNoMoreInteractions(eventHandler);
-    }
-
-
-    @Test
-    public void backgroundScanEventFailed() {
-        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(10000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .build();
-
-        InOrder order = inOrder(eventHandler, mWifiNative);
-
-        // All scan starts succeed
-        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
-
-        // Start scan
-        mScanner.startBatchedScan(settings, eventHandler);
-
-        assertBackgroundPeriodAlarmPending();
-
-        expectFailedEventScan(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ));
-
-        // Fire alarm to start next scan
-        dispatchBackgroundPeriodAlarm();
-
-        assertBackgroundPeriodAlarmPending();
-
-        expectFailedEventScan(order, eventHandler,
-                expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ));
-
-        verifyNoMoreInteractions(eventHandler);
-    }
-
-    /**
-     * Run a scan and then pause after the first scan completes, but before the next one starts
-     * Then resume the scan
-     */
-    @Test
-    public void pauseWhileWaitingToStartNextScanAndResumeScan() {
-        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(10000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .build();
-
-        ScanPeriod[] expectedPeriods = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(0, 2400, 2450, 2450),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(1, 2400),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ))
-        };
-
-        InOrder order = inOrder(eventHandler, mWifiNative);
-
-        // All scan starts succeed
-        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
-
-        // Start scan
-        mScanner.startBatchedScan(settings, eventHandler);
-
-        assertBackgroundPeriodAlarmPending();
-
-        expectSuccessfulBackgroundScan(order, eventHandler, expectedPeriods[0], 0);
-
-        assertBackgroundPeriodAlarmPending();
-
-        mScanner.pauseBatchedScan();
-
-        // onPause callback (previous results were flushed)
-        order.verify(eventHandler).onScanPaused(new WifiScanner.ScanData[0]);
-
-        assertBackgroundPeriodAlarmNotPending();
-
-        mScanner.restartBatchedScan();
-
-        // onRestarted callback
-        order.verify(eventHandler).onScanRestarted();
-
-        expectSuccessfulBackgroundScan(order, eventHandler, expectedPeriods[1], 1);
-
-        verifyNoMoreInteractions(eventHandler);
-    }
-
-    /**
-     * Run a scan and then pause while the first scan is running
-     * Then resume the scan
-     */
-    @Test
-    public void pauseWhileScanningAndResumeScan() {
-        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(10000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .build();
-
-        ScanPeriod[] expectedPeriods = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(0, 2400, 2450, 2450),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ)),
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(1, 2400),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ))
-        };
-
-        InOrder order = inOrder(eventHandler, mWifiNative);
-
-        // All scan starts succeed
-        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
-
-        // Start scan
-        mScanner.startBatchedScan(settings, eventHandler);
-
-        assertBackgroundPeriodAlarmPending();
-
-        order.verify(mWifiNative).scan(eq(expectedPeriods[0].getScanFreqs()), any(Set.class));
-
-        mScanner.pauseBatchedScan();
-
-        // onPause callback (no pending results)
-        order.verify(eventHandler).onScanPaused(new WifiScanner.ScanData[0]);
-
-        assertBackgroundPeriodAlarmNotPending();
-
-        // Setup scan results
-        when(mWifiNative.getScanResults()).thenReturn(expectedPeriods[0]
-                .getResultsToBeDelivered()[0].getScanDetailArrayList());
-
-        // Notify scan has finished
-        mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_RESULTS_EVENT);
-        assertEquals("dispatch message after results event", 1, mLooper.dispatchAll());
-
-        // listener should not be notified
-
-        mScanner.restartBatchedScan();
-
-        // onRestarted callback
-        order.verify(eventHandler).onScanRestarted();
-
-        expectSuccessfulBackgroundScan(order, eventHandler, expectedPeriods[1], 1);
-
-        verifyNoMoreInteractions(eventHandler);
-    }
-
-
-    /**
-     * Run a scan and then pause after the first scan completes, but before the next one starts
-     * Then schedule a new scan while still paused
-     */
-    @Test
-    public void pauseWhileWaitingToStartNextScanAndStartNewScan() {
-        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
-        WifiNative.ScanSettings settings = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(10000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_24_GHZ)
-                .build();
-
-        WifiNative.ScanSettings settings2 = new NativeScanSettingsBuilder()
-                .withBasePeriod(10000)
-                .withMaxApPerScan(10)
-                .addBucketWithBand(10000, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN,
-                        WifiScanner.WIFI_BAND_5_GHZ)
-                .build();
-
-        ScanPeriod[] expectedPeriods = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(0, 2400, 2450, 2450),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_24_GHZ))
-        };
-
-        ScanPeriod[] expectedPeriods2 = new ScanPeriod[] {
-            new ScanPeriod(ScanPeriod.ReportType.RESULT,
-                    ScanResults.create(1, 5150, 5175, 5175),
-                    expectedBandScanFreqs(WifiScanner.WIFI_BAND_5_GHZ)),
-        };
-
-        InOrder order = inOrder(eventHandler, mWifiNative);
-
-        // All scan starts succeed
-        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
-
-        // Start scan
-        mScanner.startBatchedScan(settings, eventHandler);
-
-        assertBackgroundPeriodAlarmPending();
-
-        expectSuccessfulBackgroundScan(order, eventHandler, expectedPeriods[0], 0);
-
-        assertBackgroundPeriodAlarmPending();
-
-        mScanner.pauseBatchedScan();
-
-        // onPause callback (previous results were flushed)
-        order.verify(eventHandler).onScanPaused(new WifiScanner.ScanData[0]);
-
-        assertBackgroundPeriodAlarmNotPending();
-
-        // Start new scan
-        mScanner.startBatchedScan(settings2, eventHandler);
-
-        expectSuccessfulBackgroundScan(order, eventHandler, expectedPeriods2[0], 0);
-
-        verifyNoMoreInteractions(eventHandler);
-    }
 
     /**
      * Test that dump() of WificondScannerImpl dumps native scan results.
@@ -613,173 +109,4 @@
         return stringWriter.toString();
     }
 
-    /**
-     * Run a test with the given settings where all native scans succeed
-     * This will execute expectedPeriods.length scan periods by first
-     * starting the scan settings and then dispatching the scan period alarm to start the
-     * next scan.
-     */
-    private void doSuccessfulTest(WifiNative.ScanSettings settings, ScanPeriod[] expectedPeriods) {
-        WifiNative.ScanEventHandler eventHandler = mock(WifiNative.ScanEventHandler.class);
-
-        InOrder order = inOrder(eventHandler, mWifiNative);
-
-        // All scans succeed
-        when(mWifiNative.scan(any(), any(Set.class))).thenReturn(true);
-
-        // Start scan
-        mScanner.startBatchedScan(settings, eventHandler);
-
-        for (int i = 0; i < expectedPeriods.length; ++i) {
-            ScanPeriod period = expectedPeriods[i];
-            assertBackgroundPeriodAlarmPending();
-            if (period != null) { // scan should be scheduled
-                expectSuccessfulBackgroundScan(order, eventHandler, expectedPeriods[i], i);
-            }
-            if (i < expectedPeriods.length - 1) {
-                dispatchBackgroundPeriodAlarm();
-            }
-        }
-
-        verifyNoMoreInteractions(eventHandler);
-    }
-
-    /**
-     * Verify the state after a scan was started either through startBatchedScan or
-     * dispatching the period alarm.
-     */
-    private void expectSuccessfulBackgroundScan(InOrder order,
-            WifiNative.ScanEventHandler eventHandler, ScanPeriod period, int periodId) {
-        WifiScanner.ScanData[] scanDatas = null;
-        ArrayList<ScanDetail> nativeResults = null;
-        ScanResult[] fullResults = null;
-        if (period.getResultsToBeDelivered() != null) {
-            ScanResults lastPeriodResults = period.getResultsToBeDelivered()
-                    [period.getResultsToBeDelivered().length - 1];
-            nativeResults = lastPeriodResults.getScanDetailArrayList();
-            if (period.expectResults()) {
-                scanDatas =
-                        new WifiScanner.ScanData[period.getResultsToBeDelivered().length];
-                for (int j = 0; j < scanDatas.length; ++j) {
-                    scanDatas[j] = period.getResultsToBeDelivered()[j].getScanData();
-                }
-            }
-            if (period.expectFullResults()) {
-                fullResults = lastPeriodResults.getRawScanResults();
-            }
-        }
-        expectSuccessfulBackgroundScan(order, eventHandler, period.getScanFreqs(),
-                nativeResults, scanDatas, fullResults, periodId);
-    }
-
-    /**
-     * Verify the state after a scan was started either through startBatchedScan or
-     * dispatching the period alarm.
-     */
-    private void expectSuccessfulBackgroundScan(InOrder order,
-            WifiNative.ScanEventHandler eventHandler, Set<Integer> scanFreqs,
-            ArrayList<ScanDetail> nativeResults,
-            WifiScanner.ScanData[] expectedScanResults,
-            ScanResult[] fullResults, int periodId) {
-        // Verify scan started
-        order.verify(mWifiNative).scan(eq(scanFreqs), any(Set.class));
-
-        // Setup scan results
-        when(mWifiNative.getScanResults()).thenReturn(nativeResults);
-
-        // Notify scan has finished
-        mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_RESULTS_EVENT);
-        assertEquals("dispatch message after results event", 1, mLooper.dispatchAll());
-
-        if (fullResults != null) {
-            for (ScanResult result : fullResults) {
-                order.verify(eventHandler).onFullScanResult(eq(result), eq(0));
-            }
-        }
-
-        if (expectedScanResults != null) {
-            // Verify scan results delivered
-            order.verify(eventHandler).onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
-            assertScanDatasEquals("period[" + periodId + "].", expectedScanResults,
-                    mScanner.getLatestBatchedScanResults(true));
-        }
-    }
-
-    private void expectFailedScanStart(InOrder order, WifiNative.ScanEventHandler eventHandler,
-            Set<Integer> scanFreqs) {
-        // Verify scan started
-        order.verify(mWifiNative).scan(eq(scanFreqs), any(Set.class));
-    }
-
-    private void expectFailedEventScan(InOrder order, WifiNative.ScanEventHandler eventHandler,
-            Set<Integer> scanFreqs) {
-        // Verify scan started
-        order.verify(mWifiNative).scan(eq(scanFreqs), any(Set.class));
-
-        // Notify scan has failed
-        mWifiMonitor.sendMessage(mWifiNative.getInterfaceName(), WifiMonitor.SCAN_FAILED_EVENT);
-        assertEquals("dispatch message after results event", 1, mLooper.dispatchAll());
-
-        // TODO: verify failure event
-    }
-
-    private void assertBackgroundPeriodAlarmPending() {
-        assertTrue("background period alarm not pending",
-                mAlarmManager.isPending(WificondScannerImpl.BACKGROUND_PERIOD_ALARM_TAG));
-    }
-
-    private void assertBackgroundPeriodAlarmNotPending() {
-        assertFalse("background period alarm is pending",
-                mAlarmManager.isPending(WificondScannerImpl.BACKGROUND_PERIOD_ALARM_TAG));
-    }
-
-    private void dispatchBackgroundPeriodAlarm() {
-        assertTrue("dispatch background period alarm",
-                mAlarmManager.dispatch(WificondScannerImpl.BACKGROUND_PERIOD_ALARM_TAG));
-        mLooper.dispatchAll();
-    }
-
-    private static class ScanPeriod {
-        enum ReportType {
-            NONE(false, false),
-            RESULT(true, false),
-            FULL_AND_RESULT(true, true),
-            FULL(false, true);
-
-            public final boolean result;
-            public final boolean full;
-            ReportType(boolean result, boolean full) {
-                this.result = result;
-                this.full = full;
-            }
-        };
-        private final ReportType mReportType;
-        private final ScanResults[] mDeliveredResults;
-        private final Set<Integer> mRequestedFreqs;
-
-        ScanPeriod(ReportType reportType, ScanResults deliveredResult,
-                Set<Integer> requestedFreqs) {
-            this(reportType, new ScanResults[] {deliveredResult}, requestedFreqs);
-        }
-
-        ScanPeriod(ReportType reportType, ScanResults[] deliveredResults,
-                Set<Integer> requestedFreqs) {
-            mReportType = reportType;
-            mDeliveredResults = deliveredResults;
-            mRequestedFreqs = requestedFreqs;
-        }
-
-        public boolean expectResults() {
-            return mReportType.result;
-        }
-        public boolean expectFullResults() {
-            return mReportType.full;
-        }
-        public final ScanResults[] getResultsToBeDelivered() {
-            return mDeliveredResults;
-        }
-        public Set<Integer> getScanFreqs() {
-            return mRequestedFreqs;
-        }
-    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/util/ApConfigUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/ApConfigUtilTest.java
index a9d3fea..92551a8 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/ApConfigUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/ApConfigUtilTest.java
@@ -235,28 +235,8 @@
         when(mWifiNative.isHalStarted()).thenReturn(true);
         when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
                 .thenReturn(null);
-        when(mWifiNative.isGetChannelsForBandSupported()).thenReturn(true);
         assertEquals(ApConfigUtil.ERROR_NO_CHANNEL,
                 ApConfigUtil.updateApChannelConfig(
                         mWifiNative, TEST_COUNTRY_CODE, mAllowed2GChannels, config));
     }
-
-    /**
-     * Verify updateApChannelConfig will use the default band and channel when
-     * GetChannelsForBand API is not supported by HAL.
-     */
-    @Test
-    public void updateApChannelConfigWithoutChannelsForBandSupported() throws Exception {
-        WifiConfiguration config = new WifiConfiguration();
-        config.apBand = WifiConfiguration.AP_BAND_5GHZ;
-        when(mWifiNative.isHalStarted()).thenReturn(true);
-        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ))
-                .thenReturn(null);
-        when(mWifiNative.isGetChannelsForBandSupported()).thenReturn(false);
-        assertEquals(ApConfigUtil.SUCCESS,
-                ApConfigUtil.updateApChannelConfig(
-                        mWifiNative, TEST_COUNTRY_CODE, mAllowed2GChannels, config));
-        assertEquals(ApConfigUtil.DEFAULT_AP_BAND, config.apBand);
-        assertEquals(ApConfigUtil.DEFAULT_AP_CHANNEL, config.apChannel);
-    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
index b9a8c31..86b394f 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/InformationElementUtilTest.java
@@ -727,12 +727,12 @@
 
     /**
      * Verify that the expected Interworking information element is parsed and retrieved from the
-     * list of IEs.
+     * list of IEs. Uses an IE w/o the optional Venue Info.
      *
      * @throws Exception
      */
     @Test
-    public void getInterworkingElementIE() throws Exception {
+    public void getInterworkingElementNoVenueIE() throws Exception {
         InformationElement ie = new InformationElement();
         ie.id = InformationElement.EID_INTERWORKING;
         /**
@@ -753,4 +753,34 @@
         assertEquals(NetworkDetail.Ant.Private, interworking.ant);
         assertEquals(0x112233445566L, interworking.hessid);
     }
+
+    /**
+     * Verify that the expected Interworking information element is parsed and retrieved from the
+     * list of IEs. Uses an IE with the optional Venue Info.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void getInterworkingElementWithVenueIE() throws Exception {
+        InformationElement ie = new InformationElement();
+        ie.id = InformationElement.EID_INTERWORKING;
+        /**
+         * Interworking Format:
+         * | Access Network Option | Venue Info (optional) | HESSID (optional) |
+         *           1                       2                     6
+         *
+         * Access Network Option Format:
+         *
+         * B0                   B3    B4       B5    B6     B7
+         * | Access Network Type | Internet | ASRA | ESR | UESA |
+         */
+        ie.bytes = new byte[]{(byte) 0x10, (byte) 0xAA, (byte) 0xBB, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66};
+        InformationElementUtil.Interworking interworking =
+                InformationElementUtil.getInterworkingIE(new InformationElement[] {ie});
+        assertTrue(interworking.internet);
+        assertEquals(NetworkDetail.Ant.Private, interworking.ant);
+        assertEquals(0x112233445566L, interworking.hessid);
+    }
+
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/util/KalmanFilterTest.java b/tests/wifitests/src/com/android/server/wifi/util/KalmanFilterTest.java
new file mode 100644
index 0000000..5e06e70
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/util/KalmanFilterTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.util.Random;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.util.KalmanFilter}.
+ */
+public class KalmanFilterTest {
+    /**
+     * Test that constructor works
+     */
+    @Test
+    public void testConstructor() throws Exception {
+        KalmanFilter kf = new KalmanFilter();
+        assertNotNull(kf);
+    }
+
+    /**
+     * Sets up a Kalman filter to behave as as a phase-locked loop
+     * <p>
+     * Set up a 2-D model that generates sinusoidal output at a fixed frequency; the state
+     * transformation is just a rotation by a fixed angle, and the output matrix projects oen
+     * of the dimensions.
+     */
+    private KalmanFilter initializePll(double stepSizeRadians,
+                                       double modelStandardDeviation,
+                                       double measurementStandardDeviation) {
+        KalmanFilter kf = new KalmanFilter();
+        double cos = Math.cos(stepSizeRadians);
+        double sin = Math.sin(stepSizeRadians);
+        kf.mF = new Matrix(2, new double[]{
+                cos, sin,
+                -sin, cos});
+        double modelVariance = modelStandardDeviation * modelStandardDeviation;
+        kf.mQ = new Matrix(2, new double[]{
+                modelVariance, 0.0,
+                0.0, modelVariance});
+        kf.mH = new Matrix(2, new double[]{1.0, 0.0});
+        double measurementVariance = measurementStandardDeviation * measurementStandardDeviation;
+        kf.mR = new Matrix(1, new double[]{measurementVariance});
+        double initialAPosterioriVariance = 10000.0;
+        kf.mP = new Matrix(2, new double[]{
+                initialAPosterioriVariance, 0.0,
+                0.0, initialAPosterioriVariance});
+        kf.mx = new Matrix(2, 1);
+        return kf;
+    }
+
+    private double mAmplitude = 10.0;
+    private double mStepSizeRadians = Math.PI / 17.01;
+    private int mTransitionPoint = 300;
+
+    /**
+     * Generates the ideal signal at time step i.
+     * <p>
+     * Sinusoid, with an abrupt phase shift thrown in to test transient response
+     */
+    private double idealSignal(int i) {
+        double phi = mStepSizeRadians * i;
+        if (i > mTransitionPoint) {
+            phi = phi + Math.PI * .75;
+        }
+        return mAmplitude * Math.sin(phi);
+    }
+
+    private double mNoiseAmplitude = 3.0;
+
+    private int mSteps = 500;
+    private int mSeed = 271828;
+
+    /**
+     * Test that using the phase locked loop Kalman filter results in a residual that is
+     * a lot smaller than the noise in the signal.
+     */
+    @Test
+    public void testPhaseLockedLoop() throws Exception {
+        Random random = new Random(mSeed);
+        double modelStandardDeviation = 0.5;
+        double [] noise = new double[mSteps];
+        for (int i = 0; i < mSteps; i++) {
+            noise[i] = random.nextGaussian() * mNoiseAmplitude;
+        }
+        double [] filtered = new double[mSteps];
+        double [] residual = new double[mSteps];
+        KalmanFilter kf = initializePll(mStepSizeRadians, modelStandardDeviation, mNoiseAmplitude);
+        for (int i = 0; i < mSteps; i++) {
+            kf.predict();
+            kf.update(new Matrix(1, new double[] {idealSignal(i) + noise[i]}));
+            filtered[i] = kf.mx.get(0, 0);
+            residual[i] = filtered[i] - idealSignal(i);
+        }
+        double sum = 0.0;
+        double sumSquares = 0.0;
+        double n = 0.0;
+        for (int i = 0; i < mSteps; i++) {
+            if (i < 5 || (i > mTransitionPoint && i < mTransitionPoint + 20)) continue;
+            sum += residual[i];
+            sumSquares += residual[i] * residual[i];
+            n += 1.0;
+        }
+        double mean = sum / n;
+        double variance = (sumSquares - sum * sum) / (n * n);
+        assertTrue(mean < 0.1);
+        assertTrue(variance < 1.5);
+        assertNotNull(kf.toString());
+    }
+
+    /**
+     * Test that the toString method works even if the matrices have not been set.
+     */
+    @Test
+    public void testToStrinWithNullsInside() throws Exception {
+        assertNotNull(new KalmanFilter().toString());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/util/MatrixTest.java b/tests/wifitests/src/com/android/server/wifi/util/MatrixTest.java
index bfa1071..f347f3d 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/MatrixTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/MatrixTest.java
@@ -17,9 +17,13 @@
 package com.android.server.wifi.util;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
 
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Random;
 
 /**
@@ -67,7 +71,18 @@
     }
 
     /**
-     * Test multiplication.
+     * Test scalar multiplication.
+     */
+    @Test
+    public void testScalarMultiplication() throws Exception {
+        Matrix m1 = new Matrix(2, new double[]{1, 2, 3, 4});
+        double x = 1001;
+        Matrix m2 = new Matrix(2, new double[]{x * 1, x * 2, x * 3, x * 4});
+        assertEquals(m2, m1.times(x));
+    }
+
+    /**
+     * Test matrix multiplication.
      */
     @Test
     public void testMultiplication() throws Exception {
@@ -94,18 +109,75 @@
     }
 
     /**
+     * Generates a random permuation matrix
+     */
+    public Matrix generatePermutationMatrix(int size, Random random) {
+        Matrix matrix = new Matrix(size, size);
+        ArrayList<Integer> j = new ArrayList(size);
+        for (int i = 0; i < size; i++) {
+            j.add(i);
+        }
+        Collections.shuffle(j, random);
+        for (int i = 0; i < size; i++) {
+            matrix.put(i, j.get(i), 1.0);
+        }
+        return matrix;
+    }
+
+    /**
+     * Test that inverting a random permutaion matrix works
+     */
+    @Test
+    public void testInverseOfPermutation() throws Exception {
+        int size = 20;
+        Matrix m1 = generatePermutationMatrix(size, new Random(132));
+        Matrix m2 = m1.inverse();
+        Matrix m3 = m1.transpose();
+        assertEquals(m3, m2);
+    }
+
+    /**
      * Test that attempting to invert a singular matrix throws an exception.
      * @throws Exception
      */
-    @Test(expected = ArithmeticException.class)
+    @Test
     public void testSingularity() throws Exception {
         Matrix m1 = new Matrix(3, new double[]{10, 1, -1, 0, 0, 0, 0, 0, 0});
-        Matrix m2 = m1.inverse();
+        try {
+            m1.inverse();
+            fail("Expected ArithmeticException");
+        } catch (ArithmeticException e) {
+            return;
+        }
+    }
+
+    /**
+     * Test multiplication by a transpose
+     */
+    @Test
+    public void testMultiplicationByTranspose() throws Exception {
+        Matrix m1 = new Matrix(2, new double[]{1, 2, 3, 4, 5, 6});
+        Matrix m2 = new Matrix(2, new double[]{1, 2, 3, 4, 5, 6, 7, 8});
+        assertEquals(m1.dot(m2.transpose()), m1.dotTranspose(m2));
+    }
+
+    /**
+     * Test that exception is thrown for non-conformable dotTranspose
+     */
+    @Test
+    public void testMultiplicationByBadlyShapedTranspose() throws Exception {
+        Matrix m1 = new Matrix(2, new double[]{1, 2, 3, 4, 5, 6});
+        Matrix m2 = m1.transpose();
+        try {
+            m1.dotTranspose(m2);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
     }
 
     /**
      * Test that a copy is equal to the original, and that hash codes match,
-     * and that string versions match.
+     * and that string versions match; exercise equals in various ways.
      */
     @Test
     public void testCopy() throws Exception {
@@ -114,9 +186,16 @@
         for (int i = 0; i < m1.mem.length; i++) {
             m1.mem[i] = random.nextDouble();
         }
+        assertEquals(m1, m1);
         Matrix m2 = new Matrix(m1);
         assertEquals(m1, m2);
         assertEquals(m1.hashCode(), m2.hashCode());
         assertEquals(m1.toString(), m2.toString());
+        m2.put(2, 2, 2.0); // guaranteed change, because nextDouble() is between 0 and 1.
+        assertNotEquals(m1, m2);
+        assertNotEquals(m1, m1.transpose());
+        assertNotEquals(m1.toString(), m2.toString());
+        assertNotEquals(m1, null);
+        assertNotEquals(m1, "a");
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
index 7f5a66d..308e267 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
@@ -19,7 +19,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doAnswer;
@@ -28,15 +27,12 @@
 
 import android.Manifest;
 import android.app.AppOpsManager;
-import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.net.NetworkScoreManager;
-import android.net.NetworkScorerAppData;
-import android.net.wifi.WifiConfiguration;
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -45,7 +41,6 @@
 
 import com.android.server.wifi.BinderUtil;
 import com.android.server.wifi.FakeWifiLog;
-import com.android.server.wifi.FrameworkFacade;
 import com.android.server.wifi.WifiInjector;
 import com.android.server.wifi.WifiSettingsStore;
 
@@ -77,10 +72,8 @@
     @Mock private UserManager mMockUserManager;
     @Mock private WifiSettingsStore mMockWifiSettingsStore;
     @Mock private ContentResolver mMockContentResolver;
-    @Mock private NetworkScoreManager mMockNetworkScoreManager;
-    @Mock private WifiInjector mMockWifiInjector;
-    @Mock private FrameworkFacade mMockFrameworkFacade;
-    @Mock private WifiConfiguration mMockWifiConfig;
+    @Mock private NetworkScoreManager mNetworkScoreManager;
+    @Mock private WifiInjector mWifiInjector;
     @Spy private FakeWifiLog mWifiLog;
 
     private static final String TEST_PACKAGE_NAME = "com.google.somePackage";
@@ -108,10 +101,6 @@
     private boolean mActiveNwScorer;
     private Answer<Integer> mReturnPermission;
     private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();
-    private String mUseOpenWifiPackage;
-    private NetworkScorerAppData mNetworkScorerAppData;
-    private boolean mGetActiveScorerThrowsSecurityException;
-    private boolean mConfigIsOpen;
 
     /**
     * Set up Mockito tests
@@ -135,8 +124,8 @@
         mUid = MANAGED_PROFILE_UID;  // do not really care about this value
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         when(mMockPermissionsWrapper.getOverrideWifiConfigPermission(anyInt()))
                 .thenReturn(PackageManager.PERMISSION_GRANTED);
         assertTrue(codeUnderTest.checkConfigOverridePermission(mUid));
@@ -150,8 +139,8 @@
         mUid = OTHER_USER_UID;  // do not really care about this value
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         when(mMockPermissionsWrapper.getOverrideWifiConfigPermission(anyInt()))
                 .thenReturn(PackageManager.PERMISSION_DENIED);
         assertFalse(codeUnderTest.checkConfigOverridePermission(mUid));
@@ -165,8 +154,8 @@
         mUid = OTHER_USER_UID;  // do not really care about this value
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         doThrow(new RemoteException("Failed to check permissions for " + mUid))
                 .when(mMockPermissionsWrapper).getOverrideWifiConfigPermission(mUid);
         assertFalse(codeUnderTest.checkConfigOverridePermission(mUid));
@@ -190,8 +179,8 @@
         mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -218,8 +207,8 @@
         mMockUserInfo.id = mCallingUser;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -241,8 +230,8 @@
         mPermissionsList.put(mMacAddressPermission, mUid);
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -270,8 +259,8 @@
         mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -297,8 +286,8 @@
         mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -323,8 +312,8 @@
         mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -334,354 +323,6 @@
     }
 
     /**
-     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
-     *                    User is current
-     *                    The current config is for an open network.
-     * Validate result is true
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_PackageIsUseOpenWifiPackage() throws Exception {
-        final boolean output;
-        mThrowSecurityException = false;
-        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
-        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
-        ComponentName useOpenWifiComponent = new ComponentName(TEST_PACKAGE_NAME, "TestClass");
-        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
-                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
-                useOpenWifiComponent, null /*networkAvailableNotificationChannelId*/);
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
-                mUid, mTargetVersion);
-
-        assertEquals(true, output);
-    }
-
-    /**
-     * Test case setting: Package is valid because the caller has access to scan results.
-     *                    Location mode is ON
-     *                    User is current
-     *                    The current config is not for an open network.
-     * Validate result is true
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_HasAccessToScanResults() throws Exception {
-        final boolean output;
-        mThrowSecurityException = false;
-        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.GINGERBREAD;
-        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
-        mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
-        mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
-        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
-        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
-        mConfigIsOpen = false;
-
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
-                mUid, mTargetVersion);
-
-        assertEquals(true, output);
-    }
-
-    /**
-     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
-     *                    User or profile is not current but the uid has
-     *                    permission to INTERACT_ACROSS_USERS_FULL
-     *                    The current config is for an open network.
-     * Validate result is true
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_UserNotCurrentButHasInteractAcrossUsers()
-            throws Exception {
-        final boolean output;
-        mThrowSecurityException = false;
-        mUid = MANAGED_PROFILE_UID;
-        mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
-        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
-        ComponentName useOpenWifiComponent = new ComponentName(TEST_PACKAGE_NAME, "TestClass");
-        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
-                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
-                useOpenWifiComponent, null /*networkAvailableNotificationChannelId*/);
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
-                mUid, mTargetVersion);
-
-        assertEquals(true, output);
-    }
-
-    /**
-     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
-     *                    User or profile is NOT current
-     *                    INTERACT_ACROSS_USERS_FULL NOT granted
-     *                    The current config is for an open network.
-     * Validate result is false
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_UserNotCurrentNoInteractAcrossUsers()
-            throws Exception {
-        final boolean output;
-        mThrowSecurityException = false;
-        mUid = MANAGED_PROFILE_UID;
-        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
-                mUid, mTargetVersion);
-
-        assertEquals(false, output);
-    }
-
-    /**
-     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
-     *                    User is current
-     *                    The current config is NULL.
-     * Validate result is false
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_WiFiConfigIsNull() throws Exception {
-        final boolean output;
-        mThrowSecurityException = false;
-        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
-        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
-        ComponentName useOpenWifiComponent = new ComponentName(TEST_PACKAGE_NAME, "TestClass");
-        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
-                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
-                useOpenWifiComponent, null /*networkAvailableNotificationChannelId*/);
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        output = codeUnderTest.canAccessFullConnectionInfo(null /*config*/, TEST_PACKAGE_NAME,
-                mUid, mTargetVersion);
-
-        assertEquals(false, output);
-    }
-
-    /**
-     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
-     *                    User is current
-     *                    The current config is not for an open network.
-     * Validate result is false
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_WiFiConfigIsNotOpen() throws Exception {
-        final boolean output;
-        mThrowSecurityException = false;
-        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
-        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
-        ComponentName useOpenWifiComponent = new ComponentName(TEST_PACKAGE_NAME, "TestClass");
-        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
-                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
-                useOpenWifiComponent, null /*networkAvailableNotificationChannelId*/);
-        mConfigIsOpen = false;
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
-                mUid, mTargetVersion);
-
-        assertEquals(false, output);
-    }
-
-    /**
-     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
-     *                    User is current
-     *                    The current config is for an open network.
-     *                    There is no active scorer
-     * Validate result is false
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_UseOpenWifiPackageIsSetButNoActiveScorer()
-            throws Exception {
-        final boolean output;
-        mThrowSecurityException = false;
-        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
-        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
-        mNetworkScorerAppData = null; // getActiveScorer() will return null
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
-                mUid, mTargetVersion);
-
-        assertEquals(false, output);
-    }
-
-    /**
-     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
-     *                    User is current
-     *                    The current config is for an open network.
-     *                    The scorer is active but the useOpenWiFi component name doesn't match
-     *                    the provided package.
-     * Validate result is false
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_MismatchBetweenUseOpenWifiPackages()
-            throws Exception {
-        final boolean output;
-        mThrowSecurityException = false;
-        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
-        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
-        ComponentName useOpenWifiComponent =
-                new ComponentName(mUseOpenWifiPackage + ".nomatch", "TestClass");
-        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
-                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
-                useOpenWifiComponent, null /*networkAvailableNotificationChannelId*/);
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
-                mUid, mTargetVersion);
-
-        assertEquals(false, output);
-    }
-
-    /**
-     * Test case setting: Package is valid because it matches the USE_OPEN_WIFI_PACKAGE.
-     *                    User is current
-     *                    The current config is for an open network.
-     *                    The scorer is active but the useOpenWiFi component name is null.
-     * Validate result is false
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_UseOpenWifiPackageFromScorerIsNull()
-            throws Exception {
-        final boolean output;
-        mThrowSecurityException = false;
-        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
-        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
-        mNetworkScorerAppData = new NetworkScorerAppData(0 /*packageUid*/,
-                null /*recommendationServiceComp*/, null /*recommendationServiceLabel*/,
-                null /*useOpenWifiComponent*/, null /*networkAvailableNotificationChannelId*/);
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
-                mUid, mTargetVersion);
-
-        assertEquals(false, output);
-    }
-
-    /**
-     * Test case setting: Package is invalid because USE_OPEN_WIFI_PACKAGE is an empty string.
-     *                    Location mode is ON
-     *                    User is current
-     *                    The current config is for an open network.
-     * Validate result is false
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_UseOpenWifiPackageIsEmpty() throws Exception {
-        final boolean output;
-        mThrowSecurityException = false;
-        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
-        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
-        mUseOpenWifiPackage = "";
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
-                mUid, mTargetVersion);
-
-        assertEquals(false, output);
-    }
-
-    /**
-     * Test case setting: Package is invalid because it does not match the USE_OPEN_WIFI_PACKAGE.
-     *                    User is current
-     *                    The current config is for an open network.
-     * Validate result is false
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_DoesNotMatchUseOpenWifiPackage() throws Exception {
-        final boolean output;
-        mThrowSecurityException = false;
-        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
-        mUseOpenWifiPackage = TEST_PACKAGE_NAME + ".nomatch";
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        output = codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME,
-                mUid, mTargetVersion);
-
-        assertEquals(false, output);
-    }
-
-    /**
-     * Test case setting: The caller is invalid because its UID does not match the provided package.
-     *
-     * Validate a SecurityException is thrown.
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_UidPackageCheckFails() throws Exception {
-        mThrowSecurityException = true;
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        try {
-            codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME, mUid,
-                    mTargetVersion);
-            fail("SecurityException not thrown.");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Test case setting: The getActiveScorer() call fails with a SecurityException.
-     *
-     * Validate a SecurityException is thrown.
-     */
-    @Test
-    public void testCanAccessFullConnectionInfo_GetActiveScorerFails() throws Exception {
-        mThrowSecurityException = false;
-        mGetActiveScorerThrowsSecurityException = true;
-        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
-        mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
-        mUseOpenWifiPackage = TEST_PACKAGE_NAME;
-        setupTestCase();
-        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
-
-        try {
-            codeUnderTest.canAccessFullConnectionInfo(mMockWifiConfig, TEST_PACKAGE_NAME, mUid,
-                    mTargetVersion);
-            fail("SecurityException not thrown.");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
      * Test case Setting: Package is valid
      *                    Legacy App
      *                    Foreground
@@ -700,8 +341,8 @@
         mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -732,8 +373,8 @@
         mMockUserInfo.id = mCallingUser;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -759,8 +400,8 @@
         mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -778,8 +419,8 @@
         boolean output = false;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         try {
             output = codeUnderTest.canAccessScanResults(TEST_PACKAGE_NAME, mUid, mTargetVersion);
         } catch (SecurityException e) {
@@ -803,8 +444,8 @@
         mMockUserInfo.id = mCallingUser;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         codeUnderTest.enforceLocationPermission(TEST_PACKAGE_NAME, mUid);
     }
 
@@ -816,8 +457,8 @@
     public void testEnforceLocationPermissionExpectSecurityException() throws Exception {
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
-                mMockContext, mMockWifiSettingsStore, mMockUserManager, mMockNetworkScoreManager,
-                mMockWifiInjector);
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mNetworkScoreManager,
+                mWifiInjector);
         codeUnderTest.enforceLocationPermission(TEST_PACKAGE_NAME, mUid);
     }
 
@@ -859,15 +500,7 @@
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
         when(mMockContext.getSystemService(Context.USER_SERVICE))
             .thenReturn(mMockUserManager);
-        when(mMockWifiInjector.makeLog(anyString())).thenReturn(mWifiLog);
-        when(mMockWifiInjector.getFrameworkFacade()).thenReturn(mMockFrameworkFacade);
-        if (mGetActiveScorerThrowsSecurityException) {
-            when(mMockNetworkScoreManager.getActiveScorer()).thenThrow(
-                    new SecurityException("Caller is neither the system process nor a "
-                            + "score requester."));
-        } else {
-            when(mMockNetworkScoreManager.getActiveScorer()).thenReturn(mNetworkScorerAppData);
-        }
+        when(mWifiInjector.makeLog(anyString())).thenReturn(mWifiLog);
     }
 
     private void initTestVars() {
@@ -885,10 +518,6 @@
         mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
         mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
         mActiveNwScorer = false;
-        mUseOpenWifiPackage = null;
-        mNetworkScorerAppData = null;
-        mGetActiveScorerThrowsSecurityException = false;
-        mConfigIsOpen = true;
     }
 
     private void setupMockInterface() {
@@ -899,14 +528,11 @@
                         anyString(), anyInt());
         when(mMockPermissionsWrapper.getCallingUserId(mUid)).thenReturn(mCallingUser);
         when(mMockPermissionsWrapper.getCurrentUser()).thenReturn(mCurrentUser);
-        when(mMockNetworkScoreManager.isCallerActiveScorer(mUid)).thenReturn(mActiveNwScorer);
+        when(mNetworkScoreManager.isCallerActiveScorer(mUid)).thenReturn(mActiveNwScorer);
         when(mMockPermissionsWrapper.getUidPermission(mManifestStringCoarse, mUid))
             .thenReturn(mCoarseLocationPermission);
         when(mMockWifiSettingsStore.getLocationModeSetting(mMockContext))
             .thenReturn(mLocationModeSetting);
         when(mMockPermissionsWrapper.getTopPkgName()).thenReturn(mPkgNameOfTopActivity);
-        when(mMockFrameworkFacade.getStringSetting(mMockContext,
-                Settings.Global.USE_OPEN_WIFI_PACKAGE)).thenReturn(mUseOpenWifiPackage);
-        when(mMockWifiConfig.isOpenNetwork()).thenReturn(mConfigIsOpen);
     }
 }