Merge "Remove software pno scan support"
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index 760ee69..2c16444 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -25,7 +25,7 @@
 import android.database.ContentObserver;
 import android.net.TrafficStats;
 import android.net.Uri;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.os.BatteryStats;
 import android.os.Handler;
 import android.os.IBinder;
@@ -137,9 +137,9 @@
         return TrafficStats.getRxPackets(iface);
     }
 
-    public IpManager makeIpManager(
-            Context context, String iface, IpManager.Callback callback) {
-        return new IpManager(context, iface, callback);
+    public IpClient makeIpClient(
+            Context context, String iface, IpClient.Callback callback) {
+        return new IpClient(context, iface, callback);
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/HalDeviceManager.java b/service/java/com/android/server/wifi/HalDeviceManager.java
index 7a1e8d9..0cc735a 100644
--- a/service/java/com/android/server/wifi/HalDeviceManager.java
+++ b/service/java/com/android/server/wifi/HalDeviceManager.java
@@ -35,9 +35,9 @@
 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 +70,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 +102,13 @@
      * 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 a new Handler based on
+     *                the current looper.
      */
-    public void registerStatusListener(ManagerStatusListener listener, Looper looper) {
+    public void registerStatusListener(ManagerStatusListener listener, Handler handler) {
         synchronized (mLock) {
             if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener,
-                    looper == null ? Looper.myLooper() : looper))) {
+                    handler == null ? new Handler(Looper.myLooper()) : handler))) {
                 Log.w(TAG, "registerStatusListener: duplicate registration ignored");
             }
         }
@@ -192,37 +197,37 @@
      * @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 The Handler on which to dispatch the listener. A null implies a new Handler
+     *                based on the current looper.
      * @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);
+            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);
+            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);
+            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);
+            Handler handler) {
+        return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, handler);
     }
 
     /**
@@ -263,11 +268,11 @@
      * 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.
+     * Listener called-back on the specified handler - or on the current looper if a null is passed.
      */
     public boolean registerDestroyedListener(IWifiIface iface,
             InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
+            Handler handler) {
         String name = getName(iface);
         if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name);
 
@@ -279,8 +284,7 @@
             }
 
             return cacheEntry.destroyedListeners.add(
-                    new InterfaceDestroyedListenerProxy(destroyedListener,
-                            looper == null ? Looper.myLooper() : looper));
+                    new InterfaceDestroyedListenerProxy(destroyedListener, handler));
         }
     }
 
@@ -299,17 +303,16 @@
      * @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 The Handler on which to dispatch the listener. A null implies a new Handler
+     *                on the current looper.
      */
     public void registerInterfaceAvailableForRequestListener(int ifaceType,
-            InterfaceAvailableForRequestListener listener, Looper looper) {
+            InterfaceAvailableForRequestListener listener, 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();
@@ -474,12 +477,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();
         }
@@ -1208,8 +1213,8 @@
     private class ManagerStatusListenerProxy  extends
             ListenerProxy<ManagerStatusListener> {
         ManagerStatusListenerProxy(ManagerStatusListener statusListener,
-                Looper looper) {
-            super(statusListener, looper, "ManagerStatusListenerProxy");
+                Handler handler) {
+            super(statusListener, handler, true, "ManagerStatusListenerProxy");
         }
 
         @Override
@@ -1270,7 +1275,7 @@
     }
 
     private IWifiIface createIface(int ifaceType, InterfaceDestroyedListener destroyedListener,
-            Looper looper) {
+            Handler handler) {
         if (DBG) Log.d(TAG, "createIface: ifaceType=" + ifaceType);
 
         synchronized (mLock) {
@@ -1288,7 +1293,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
@@ -1300,7 +1305,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);
@@ -1341,9 +1346,9 @@
                     cacheEntry.type = ifaceType;
                     if (destroyedListener != null) {
                         cacheEntry.destroyedListeners.add(
-                                new InterfaceDestroyedListenerProxy(destroyedListener,
-                                        looper == null ? Looper.myLooper() : looper));
+                                new InterfaceDestroyedListenerProxy(destroyedListener, handler));
                     }
+                    cacheEntry.creationTime = mClock.getUptimeSinceBootMillis();
 
                     if (DBG) Log.d(TAG, "createIfaceIfPossible: added cacheEntry=" + cacheEntry);
                     mInterfaceInfoCache.put(cacheEntry.name, cacheEntry);
@@ -1468,7 +1473,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[ifaceType].length != 0)) {
                         if (DBG) {
                             Log.d(TAG, "Couldn't delete existing type " + type
                                     + " interfaces for requested type");
@@ -1498,17 +1504,17 @@
             }
 
             if (tooManyInterfaces > 0) { // may need to delete some
-                if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType)) {
+                if (!allowedToDeleteIfaceTypeForRequestedType(type, ifaceType,
+                        chipInfo.ifaces[ifaceType].length != 0)) {
                     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]);
             }
         }
 
@@ -1576,14 +1582,20 @@
      * interface type.
      *
      * Rules:
-     * 1. Request for AP or STA will destroy any other interface (except see #4)
+     * 1. Request for AP or STA will destroy any other interface (except see #4 and #5)
      * 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
+     * 5. No interface will be destroyed if one of the requested interfaces already exists
      */
     private boolean allowedToDeleteIfaceTypeForRequestedType(int existingIfaceType,
-            int requestedIfaceType) {
+            int requestedIfaceType, boolean requestedIfaceTypeAlreadyExists) {
+        // rule 5
+        if (requestedIfaceTypeAlreadyExists) {
+            return false;
+        }
+
         // rule 4
         if (existingIfaceType == requestedIfaceType) {
             return false;
@@ -1604,6 +1616,46 @@
     }
 
     /**
+     * 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)
@@ -1850,6 +1902,7 @@
 
         protected LISTENER mListener;
         private Handler mHandler;
+        private boolean mFrontOfQueue;
 
         // override equals & hash to make sure that the container HashSet is unique with respect to
         // the contained listener
@@ -1864,37 +1917,32 @@
         }
 
         void trigger() {
-            mHandler.sendMessage(mHandler.obtainMessage(LISTENER_TRIGGERED));
+            if (mFrontOfQueue) {
+                mHandler.postAtFrontOfQueue(() -> {
+                    action();
+                });
+            } else {
+                mHandler.post(() -> {
+                    action();
+                });
+            }
         }
 
         protected abstract void action();
 
-        ListenerProxy(LISTENER listener, Looper looper, String tag) {
+        ListenerProxy(LISTENER listener, Handler handler, boolean frontOfQueue, 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;
+            mFrontOfQueue = frontOfQueue;
         }
     }
 
     private class InterfaceDestroyedListenerProxy extends
             ListenerProxy<InterfaceDestroyedListener> {
         InterfaceDestroyedListenerProxy(InterfaceDestroyedListener destroyedListener,
-                Looper looper) {
-            super(destroyedListener, looper, "InterfaceDestroyedListenerProxy");
+                Handler handler) {
+            super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
+                    true, "InterfaceDestroyedListenerProxy");
         }
 
         @Override
@@ -1906,8 +1954,9 @@
     private class InterfaceAvailableForRequestListenerProxy extends
             ListenerProxy<InterfaceAvailableForRequestListener> {
         InterfaceAvailableForRequestListenerProxy(
-                InterfaceAvailableForRequestListener destroyedListener, Looper looper) {
-            super(destroyedListener, looper, "InterfaceAvailableForRequestListenerProxy");
+                InterfaceAvailableForRequestListener destroyedListener, Handler handler) {
+            super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
+                    false, "InterfaceAvailableForRequestListenerProxy");
         }
 
         @Override
diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java
index d4a1ea5..aca7a3a 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -20,6 +20,9 @@
 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.net.InterfaceConfiguration;
 import android.net.wifi.IApInterface;
 import android.net.wifi.WifiConfiguration;
@@ -29,6 +32,8 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.util.State;
@@ -46,6 +51,8 @@
 public class SoftApManager implements ActiveModeManager {
     private static final String TAG = "SoftApManager";
 
+    private final Context mContext;
+
     private final WifiNative mWifiNative;
 
     private final String mCountryCode;
@@ -55,12 +62,14 @@
     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;
 
     /**
@@ -75,23 +84,29 @@
         void onStateChanged(int state, int failureReason);
     }
 
-    public SoftApManager(Looper looper,
+    public SoftApManager(Context context,
+                         Looper looper,
                          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;
         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 {
@@ -116,13 +131,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 +172,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;
@@ -280,10 +311,23 @@
             public boolean processMessage(Message message) {
                 switch (message.what) {
                     case CMD_START:
-                        updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 0);
+                        // 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 (!mDeathRecipient.linkToDeath(mApInterface.asBinder())) {
                             mDeathRecipient.unlinkToDeath();
                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
+                                    WifiManager.WIFI_AP_STATE_ENABLING,
                                     WifiManager.SAP_START_FAILURE_GENERAL);
                             mWifiMetrics.incrementSoftApStartResult(
                                     false, WifiManager.SAP_START_FAILURE_GENERAL);
@@ -291,12 +335,13 @@
                         }
 
                         try {
-                            mNetworkObserver = new NetworkObserver(mApInterface.getInterfaceName());
+                            mNetworkObserver = new NetworkObserver(mApInterfaceName);
                             mNwService.registerObserver(mNetworkObserver);
                         } catch (RemoteException e) {
                             mDeathRecipient.unlinkToDeath();
                             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);
@@ -311,7 +356,9 @@
                             }
                             mDeathRecipient.unlinkToDeath();
                             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;
                         }
@@ -347,7 +394,8 @@
                 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
@@ -359,7 +407,7 @@
                 mIfaceIsUp = false;
                 InterfaceConfiguration config = null;
                 try {
-                    config = mNwService.getInterfaceConfig(mApInterface.getInterfaceName());
+                    config = mNwService.getInterfaceConfig(mApInterfaceName);
                 } catch (RemoteException e) {
                 }
                 if (config != null) {
@@ -383,13 +431,16 @@
                         break;
                     case CMD_AP_INTERFACE_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) {
                             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);
                         break;
@@ -399,6 +450,5 @@
                 return HANDLED;
             }
         }
-
     }
 }
diff --git a/service/java/com/android/server/wifi/VelocityBasedConnectedScore.java b/service/java/com/android/server/wifi/VelocityBasedConnectedScore.java
index 9d90332..69643ce 100644
--- a/service/java/com/android/server/wifi/VelocityBasedConnectedScore.java
+++ b/service/java/com/android/server/wifi/VelocityBasedConnectedScore.java
@@ -34,7 +34,8 @@
     private final int mThresholdMinimumRssi24;     // -85
 
     private int mFrequency = 5000;
-    private int mRssi = 0;
+    private double mThresholdMinimumRssi;
+    private double mThresholdAdjustment;
     private final KalmanFilter mFilter;
     private long mLastMillis;
 
@@ -44,6 +45,7 @@
                 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});
@@ -69,6 +71,7 @@
     @Override
     public void reset() {
         mLastMillis = 0;
+        mThresholdAdjustment = 0.0;
     }
 
     /**
@@ -97,6 +100,8 @@
         mFilter.predict();
         mLastMillis = millis;
         mFilter.update(new Matrix(1, new double[]{rssi}));
+        mFilteredRssi = mFilter.mx.get(0, 0);
+        mEstimatedRateOfRssiChange = mFilter.mx.get(1, 0);
     }
 
     /**
@@ -106,10 +111,63 @@
     public void updateUsingWifiInfo(WifiInfo wifiInfo, long millis) {
         int frequency = wifiInfo.getFrequency();
         if (frequency != mFrequency) {
-            reset(); // Probably roamed
+            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;
+    }
+
+    /**
+     * 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;
+        if (wifiInfo.txSuccessRate < 10) return;
+        if (wifiInfo.rxSuccessRate < 10) return;
+        double probabilityOfSuccessfulTx = (
+                wifiInfo.txSuccessRate / (wifiInfo.txSuccessRate + wifiInfo.txBadRate)
+        );
+        if (probabilityOfSuccessfulTx >= 0.2) {
+            // May want this amount to vary with how close to threshold we are
+            mThresholdAdjustment -= 0.5;
+        }
     }
 
     /**
@@ -117,7 +175,7 @@
      */
     @Override
     public int generateScore() {
-        int badRssi = mFrequency >= 5000 ? mThresholdMinimumRssi5 : mThresholdMinimumRssi24;
+        double badRssi = getAdjustedRssiThreshold();
         double horizonSeconds = 15.0;
         Matrix x = new Matrix(mFilter.mx);
         double filteredRssi = x.get(0, 0);
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index ba1695a..b610bd9 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -1010,7 +1010,7 @@
         }
 
         boolean newNetwork = (existingInternalConfig == null);
-        // This is needed to inform IpManager about any IP configuration changes.
+        // This is needed to inform IpClient about any IP configuration changes.
         boolean hasIpChanged =
                 newNetwork || WifiConfigurationUtil.hasIpChanged(
                         existingInternalConfig, newInternalConfig);
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 1cbb29e..cc55934 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;
@@ -163,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);
@@ -370,16 +370,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(),
+                                           @NonNull IApInterface apInterface,
+                                           @NonNull String ifaceName,
+                                           @NonNull SoftApModeConfiguration config) {
+        return new SoftApManager(mContext, mWifiServiceHandlerThread.getLooper(),
                                  mWifiNative, mCountryCode.getCountryCode(),
-                                 listener, apInterface, nmService,
+                                 listener, apInterface, ifaceName, nmService,
                                  mWifiApConfigStore, config, mWifiMetrics);
     }
 
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index 7254ad4..a8ad08c 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -31,6 +31,7 @@
 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;
@@ -80,6 +81,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,
@@ -1055,10 +1058,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) {
@@ -1067,6 +1074,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);
+            }
         }
     }
 
@@ -1984,6 +2005,7 @@
     public void setWifiState(int wifiState) {
         synchronized (mLock) {
             mWifiState = wifiState;
+            mWifiWins = (wifiState == WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
         }
     }
 
@@ -2089,6 +2111,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);
@@ -2109,10 +2132,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();
@@ -2268,6 +2293,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;
@@ -2275,6 +2303,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));
@@ -2337,11 +2366,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
diff --git a/service/java/com/android/server/wifi/WifiScoreReport.java b/service/java/com/android/server/wifi/WifiScoreReport.java
index 324cdba..69d91c7 100644
--- a/service/java/com/android/server/wifi/WifiScoreReport.java
+++ b/service/java/com/android/server/wifi/WifiScoreReport.java
@@ -49,7 +49,7 @@
 
     ConnectedScore mConnectedScore;
     ConnectedScore mAggressiveConnectedScore;
-    ConnectedScore mFancyConnectedScore;
+    VelocityBasedConnectedScore mFancyConnectedScore;
 
     WifiScoreReport(Context context, WifiConfigManager wifiConfigManager, Clock clock) {
         mClock = clock;
@@ -171,6 +171,8 @@
     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;
@@ -180,8 +182,8 @@
         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,%d",
-                    timestamp, mSessionNumber, rssi, freq, linkSpeed,
+                    "%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, s2);
             mLinkMetricsHistory.add(s);
@@ -205,7 +207,8 @@
      * @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,s2");
+        pw.println("time,session,rssi,filtered_rssi,rssi_threshold,"
+                + "freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1,s2");
         for (String line : mLinkMetricsHistory) {
             pw.println(line);
         }
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 7e1e4ca..dacc2d4 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -61,7 +61,7 @@
 import android.net.NetworkUtils;
 import android.net.StaticIpConfiguration;
 import android.net.Uri;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.ScanResult;
 import android.net.wifi.ScanSettings;
@@ -2278,11 +2278,11 @@
             // WifiMetrics proto bytes were requested. Dump only these.
             mWifiStateMachine.updateWifiMetrics();
             mWifiMetrics.dump(fd, pw, args);
-        } else if (args != null && args.length > 0 && IpManager.DUMP_ARG.equals(args[0])) {
-            // IpManager dump was requested. Pass it along and take no further action.
-            String[] ipManagerArgs = new String[args.length - 1];
-            System.arraycopy(args, 1, ipManagerArgs, 0, ipManagerArgs.length);
-            mWifiStateMachine.dumpIpManager(fd, pw, ipManagerArgs);
+        } else if (args != null && args.length > 0 && IpClient.DUMP_ARG.equals(args[0])) {
+            // IpClient dump was requested. Pass it along and take no further action.
+            String[] ipClientArgs = new String[args.length - 1];
+            System.arraycopy(args, 1, ipClientArgs, 0, ipClientArgs.length);
+            mWifiStateMachine.dumpIpClient(fd, pw, ipClientArgs);
         } else if (args != null && args.length > 0 && WifiScoreReport.DUMP_ARG.equals(args[0])) {
             WifiScoreReport wifiScoreReport = mWifiStateMachine.getWifiScoreReport();
             if (wifiScoreReport != null) wifiScoreReport.dump(fd, pw, args);
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index b7152ac..76b78bb 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -59,7 +59,7 @@
 import android.net.StaticIpConfiguration;
 import android.net.TrafficStats;
 import android.net.dhcp.DhcpClient;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.net.wifi.IApInterface;
 import android.net.wifi.IClientInterface;
 import android.net.wifi.RssiPacketCountInfo;
@@ -447,7 +447,7 @@
         return true;
     }
 
-    private final IpManager mIpManager;
+    private final IpClient mIpClient;
 
     // Channel for sending replies.
     private AsyncChannel mReplyChannel = new AsyncChannel();
@@ -705,7 +705,7 @@
     static final int CMD_GET_ALL_MATCHING_CONFIGS                       = BASE + 168;
 
     /**
-     * Used to handle messages bounced between WifiStateMachine and IpManager.
+     * Used to handle messages bounced between WifiStateMachine and IpClient.
      */
     static final int CMD_IPV4_PROVISIONING_SUCCESS                      = BASE + 200;
     static final int CMD_IPV4_PROVISIONING_FAILURE                      = BASE + 201;
@@ -959,8 +959,8 @@
         mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
         mLastSignalLevel = -1;
 
-        mIpManager = mFacade.makeIpManager(mContext, mInterfaceName, new IpManagerCallback());
-        mIpManager.setMulticastFilter(true);
+        mIpClient = mFacade.makeIpClient(mContext, mInterfaceName, new IpClientCallback());
+        mIpClient.setMulticastFilter(true);
 
         mNoNetworksPeriodicScan = mContext.getResources().getInteger(
                 R.integer.config_wifi_no_network_periodic_scan_interval);
@@ -982,6 +982,7 @@
         mNetworkCapabilitiesFilter.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
         mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
         mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+        mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
         mNetworkCapabilitiesFilter.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
         mNetworkCapabilitiesFilter.setLinkUpstreamBandwidthKbps(1024 * 1024);
         mNetworkCapabilitiesFilter.setLinkDownstreamBandwidthKbps(1024 * 1024);
@@ -1139,7 +1140,7 @@
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
-    class IpManagerCallback extends IpManager.Callback {
+    class IpClientCallback extends IpClient.Callback {
         @Override
         public void onPreDhcpAction() {
             sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION);
@@ -1202,10 +1203,10 @@
         }
     }
 
-    private void stopIpManager() {
+    private void stopIpClient() {
         /* Restore power save and suspend optimizations */
         handlePostDhcpSetup();
-        mIpManager.stop();
+        mIpClient.stop();
     }
 
     PendingIntent getPrivateBroadcast(String action, int requestCode) {
@@ -2109,14 +2110,14 @@
      * Start filtering Multicast v4 packets
      */
     public void startFilteringMulticastPackets() {
-        mIpManager.setMulticastFilter(true);
+        mIpClient.setMulticastFilter(true);
     }
 
     /**
      * Stop filtering Multicast v4 packets
      */
     public void stopFilteringMulticastPackets() {
-        mIpManager.setMulticastFilter(false);
+        mIpClient.setMulticastFilter(false);
     }
 
     /**
@@ -2232,8 +2233,8 @@
         }
     }
 
-    public void dumpIpManager(FileDescriptor fd, PrintWriter pw, String[] args) {
-        mIpManager.dump(fd, pw, args);
+    public void dumpIpClient(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mIpClient.dump(fd, pw, args);
     }
 
     @Override
@@ -2280,7 +2281,7 @@
         pw.println();
         mWifiDiagnostics.captureBugReportData(WifiDiagnostics.REPORT_REASON_USER_ACTION);
         mWifiDiagnostics.dump(fd, pw, args);
-        dumpIpManager(fd, pw, args);
+        dumpIpClient(fd, pw, args);
         if (mWifiConnectivityManager != null) {
             mWifiConnectivityManager.dump(fd, pw, args);
         } else {
@@ -2925,23 +2926,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() {
@@ -3066,7 +3050,7 @@
             log("Link configuration changed for netId: " + mLastNetworkId
                     + " old: " + mLinkProperties + " new: " + newLp);
         }
-        // We own this instance of LinkProperties because IpManager passes us a copy.
+        // We own this instance of LinkProperties because IpClient passes us a copy.
         mLinkProperties = newLp;
         if (mNetworkAgent != null) {
             mNetworkAgent.sendLinkProperties(mLinkProperties);
@@ -3096,7 +3080,7 @@
      */
     private void clearLinkProperties() {
         // Clear the link properties obtained from DHCP. The only caller of this
-        // function has already called IpManager#stop(), which clears its state.
+        // function has already called IpClient#stop(), which clears its state.
         synchronized (mDhcpResultsLock) {
             if (mDhcpResults != null) {
                 mDhcpResults.clear();
@@ -3322,7 +3306,7 @@
 
         clearTargetBssid("handleNetworkDisconnect");
 
-        stopIpManager();
+        stopIpClient();
 
         /* Reset data structures */
         mWifiScoreReport.reset();
@@ -3453,7 +3437,7 @@
                 // short in two ways:
                 // - at the time of the CMD_IP_CONFIGURATION_SUCCESSFUL event, we don't know if we
                 //   actually have ARP reachability. it might be better to wait until the wifi
-                //   network has been validated by IpManager.
+                //   network has been validated by IpClient.
                 // - in the case of a roaming event (intra-SSID), we probably trigger when L2 is
                 //   complete.
                 //
@@ -4378,7 +4362,7 @@
             // Disable legacy multicast filtering, which on some chipsets defaults to enabled.
             // Legacy IPv6 multicast filtering blocks ICMPv6 router advertisements which breaks IPv6
             // provisioning. Legacy IPv4 multicast filtering may be re-enabled later via
-            // IpManager.Callback.setFallbackMulticastFilter()
+            // IpClient.Callback.setFallbackMulticastFilter()
             mWifiNative.stopFilteringMulticastV4Packets();
             mWifiNative.stopFilteringMulticastV6Packets();
 
@@ -5043,7 +5027,7 @@
                     // DNAv4/DNAv6 -style probing for on-link neighbors of
                     // interest (e.g. routers); harmless if none are configured.
                     if (state == SupplicantState.COMPLETED) {
-                        mIpManager.confirmConfiguration();
+                        mIpClient.confirmConfiguration();
                     }
                     break;
                 case WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST:
@@ -5314,7 +5298,7 @@
                         } else {
                             if (result.hasProxyChanged()) {
                                 log("Reconfiguring proxy on connection");
-                                mIpManager.setHttpProxy(
+                                mIpClient.setHttpProxy(
                                         getCurrentWifiConfiguration().getHttpProxy());
                             }
                             if (result.hasIpChanged()) {
@@ -5783,7 +5767,7 @@
 
         @Override
         public void exit() {
-            mIpManager.stop();
+            mIpClient.stop();
 
             // This is handled by receiving a NETWORK_DISCONNECTION_EVENT in ConnectModeState
             // Bug: 15347363
@@ -5813,17 +5797,17 @@
                     handlePreDhcpSetup();
                     break;
                 case DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE:
-                    mIpManager.completedPreDhcpAction();
+                    mIpClient.completedPreDhcpAction();
                     break;
                 case DhcpClient.CMD_POST_DHCP_ACTION:
                     handlePostDhcpSetup();
-                    // We advance to mConnectedState because IpManager will also send a
+                    // We advance to mConnectedState because IpClient will also send a
                     // CMD_IPV4_PROVISIONING_SUCCESS message, which calls handleIPv4Success(),
                     // which calls updateLinkProperties, which then sends
                     // CMD_IP_CONFIGURATION_SUCCESSFUL.
                     //
                     // In the event of failure, we transition to mDisconnectingState
-                    // similarly--via messages sent back from IpManager.
+                    // similarly--via messages sent back from IpClient.
                     break;
                 case CMD_IPV4_PROVISIONING_SUCCESS: {
                     handleIPv4Success((DhcpResults) message.obj);
@@ -6065,7 +6049,7 @@
             // cause the roam to fail and the device to disconnect.
             clearTargetBssid("ObtainingIpAddress");
 
-            // Stop IpManager in case we're switching from DHCP to static
+            // Stop IpClient in case we're switching from DHCP to static
             // configuration or vice versa.
             //
             // TODO: Only ever enter this state the first time we connect to a
@@ -6075,15 +6059,15 @@
             // disconnected, because DHCP might take a long time during which
             // connectivity APIs such as getActiveNetworkInfo should not return
             // CONNECTED.
-            stopIpManager();
+            stopIpClient();
 
-            mIpManager.setHttpProxy(currentConfig.getHttpProxy());
+            mIpClient.setHttpProxy(currentConfig.getHttpProxy());
             if (!TextUtils.isEmpty(mTcpBufferSizes)) {
-                mIpManager.setTcpBufferSizes(mTcpBufferSizes);
+                mIpClient.setTcpBufferSizes(mTcpBufferSizes);
             }
-            final IpManager.ProvisioningConfiguration prov;
+            final IpClient.ProvisioningConfiguration prov;
             if (!isUsingStaticIp) {
-                prov = IpManager.buildProvisioningConfiguration()
+                prov = IpClient.buildProvisioningConfiguration()
                             .withPreDhcpAction()
                             .withApfCapabilities(mWifiNative.getApfCapabilities())
                             .withNetwork(getCurrentNetwork())
@@ -6091,14 +6075,14 @@
                             .build();
             } else {
                 StaticIpConfiguration staticIpConfig = currentConfig.getStaticIpConfiguration();
-                prov = IpManager.buildProvisioningConfiguration()
+                prov = IpClient.buildProvisioningConfiguration()
                             .withStaticConfiguration(staticIpConfig)
                             .withApfCapabilities(mWifiNative.getApfCapabilities())
                             .withNetwork(getCurrentNetwork())
                             .withDisplayName(currentConfig.SSID)
                             .build();
             }
-            mIpManager.startProvisioning(prov);
+            mIpClient.startProvisioning(prov);
             // Get Link layer stats so as we get fresh tx packet counters
             getWifiLinkLayerStats();
         }
@@ -6294,10 +6278,10 @@
                         // We used to transition to ObtainingIpState in an
                         // attempt to do DHCPv4 RENEWs on framework roams.
                         // DHCP can take too long to time out, and we now rely
-                        // upon IpManager's use of IpReachabilityMonitor to
+                        // upon IpClient's use of IpReachabilityMonitor to
                         // confirm our current network configuration.
                         //
-                        // mIpManager.confirmConfiguration() is called within
+                        // mIpClient.confirmConfiguration() is called within
                         // the handling of SupplicantState.COMPLETED.
                         transitionTo(mConnectedState);
                     } else {
@@ -6984,10 +6968,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;
             }
@@ -6995,16 +6976,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 cd1948f..c49b645 100644
--- a/service/java/com/android/server/wifi/WifiStateMachinePrime.java
+++ b/service/java/com/android/server/wifi/WifiStateMachinePrime.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.net.wifi.IApInterface;
 import android.net.wifi.IWificond;
 import android.net.wifi.WifiConfiguration;
@@ -50,7 +51,7 @@
 
     private IWificond mWificond;
 
-    private Queue<WifiConfiguration> mApConfigQueue = new ConcurrentLinkedQueue<>();
+    private Queue<SoftApModeConfiguration> mApConfigQueue = new ConcurrentLinkedQueue<>();
 
     /* The base for wifi message types */
     static final int BASE = Protocol.BASE_WIFI;
@@ -106,17 +107,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);
     }
@@ -270,6 +267,7 @@
 
         class SoftAPModeState extends State {
             IApInterface mApInterface = null;
+            String mIfaceName = null;
 
             @Override
             public void enter() {
@@ -281,23 +279,18 @@
 
                 // 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);
-                    return;
-                }
 
                 try {
+                    mWificond = mWifiInjector.makeWificond();
                     mApInterface = mWificond.createApInterface(
                             mWifiInjector.getWifiNative().getInterfaceName());
-                } 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 instance or its name from wificond");
+                    return;
+                } catch (NullPointerException e) {
+                    initializationFailed("wificond failure when initializing softap mode");
                     return;
                 }
                 mModeStateMachine.transitionTo(mSoftAPModeActiveState);
@@ -317,6 +310,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:
@@ -337,12 +332,13 @@
                 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);
             }
         }
 
@@ -410,16 +406,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 cec36a5..2d8d286 100644
--- a/service/java/com/android/server/wifi/WifiVendorHal.java
+++ b/service/java/com/android/server/wifi/WifiVendorHal.java
@@ -249,7 +249,8 @@
     public boolean initialize(WifiNative.VendorHalDeathEventHandler handler) {
         synchronized (sLock) {
             mHalDeviceManager.initialize();
-            mHalDeviceManager.registerStatusListener(mHalDeviceManagerStatusCallbacks, mLooper);
+            mHalDeviceManager.registerStatusListener(mHalDeviceManagerStatusCallbacks,
+                    mHalEventHandler);
             mDeathEventHandler = handler;
             return true;
         }
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java b/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
index e855d81..8659a77 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareNativeManager.java
@@ -20,6 +20,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 android.os.RemoteException;
 import android.util.Log;
 
@@ -41,6 +42,7 @@
 
     private WifiAwareStateManager mWifiAwareStateManager;
     private HalDeviceManager mHalDeviceManager;
+    private Handler mHandler;
     private WifiAwareNativeCallback mWifiAwareNativeCallback;
     private IWifiNanIface mWifiNanIface = null;
     private InterfaceDestroyedListener mInterfaceDestroyedListener =
@@ -56,7 +58,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,7 +77,7 @@
                             // 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();
                         }
@@ -77,7 +85,7 @@
                 }, null);
         if (mHalDeviceManager.isStarted()) {
             mHalDeviceManager.registerInterfaceAvailableForRequestListener(
-                    IfaceType.NAN, mInterfaceAvailableForRequestListener, null);
+                    IfaceType.NAN, mInterfaceAvailableForRequestListener, mHandler);
             tryToGetAware();
         }
     }
@@ -104,7 +112,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 {
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
index 31bdff8..0efe736 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -1625,7 +1625,7 @@
                     waitForResponse = endDataPathLocal(mCurrentTransactionId, msg.arg2);
                     break;
                 case COMMAND_TYPE_DELAYED_INITIALIZATION:
-                    mWifiAwareNativeManager.start();
+                    mWifiAwareNativeManager.start(getHandler());
                     waitForResponse = false;
                     break;
                 default:
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/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index d70ce41..da3da7c 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -32,7 +32,7 @@
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
 import android.net.NetworkUtils;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.net.wifi.WpsInfo;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.net.wifi.p2p.WifiP2pConfig;
@@ -113,7 +113,7 @@
     private Context mContext;
 
     INetworkManagementService mNwService;
-    private IpManager mIpManager;
+    private IpClient mIpClient;
     private DhcpResults mDhcpResults;
 
     private P2pStateMachine mP2pStateMachine;
@@ -180,12 +180,12 @@
     //   msg.obj  = StateMachine to send to when blocked
     public static final int BLOCK_DISCOVERY                 =   BASE + 15;
 
-    // Messages for interaction with IpManager.
-    private static final int IPM_PRE_DHCP_ACTION            =   BASE + 30;
-    private static final int IPM_POST_DHCP_ACTION           =   BASE + 31;
-    private static final int IPM_DHCP_RESULTS               =   BASE + 32;
-    private static final int IPM_PROVISIONING_SUCCESS       =   BASE + 33;
-    private static final int IPM_PROVISIONING_FAILURE       =   BASE + 34;
+    // Messages for interaction with IpClient.
+    private static final int IPC_PRE_DHCP_ACTION            =   BASE + 30;
+    private static final int IPC_POST_DHCP_ACTION           =   BASE + 31;
+    private static final int IPC_DHCP_RESULTS               =   BASE + 32;
+    private static final int IPC_PROVISIONING_SUCCESS       =   BASE + 33;
+    private static final int IPC_PROVISIONING_FAILURE       =   BASE + 34;
 
     public static final int ENABLED                         = 1;
     public static final int DISABLED                        = 0;
@@ -442,50 +442,50 @@
         }
     }
 
-    private void stopIpManager() {
-        if (mIpManager != null) {
-            mIpManager.stop();
-            mIpManager = null;
+    private void stopIpClient() {
+        if (mIpClient != null) {
+            mIpClient.stop();
+            mIpClient = null;
         }
         mDhcpResults = null;
     }
 
-    private void startIpManager(String ifname) {
-        stopIpManager();
+    private void startIpClient(String ifname) {
+        stopIpClient();
 
-        mIpManager = new IpManager(mContext, ifname,
-                new IpManager.Callback() {
+        mIpClient = new IpClient(mContext, ifname,
+                new IpClient.Callback() {
                     @Override
                     public void onPreDhcpAction() {
-                        mP2pStateMachine.sendMessage(IPM_PRE_DHCP_ACTION);
+                        mP2pStateMachine.sendMessage(IPC_PRE_DHCP_ACTION);
                     }
                     @Override
                     public void onPostDhcpAction() {
-                        mP2pStateMachine.sendMessage(IPM_POST_DHCP_ACTION);
+                        mP2pStateMachine.sendMessage(IPC_POST_DHCP_ACTION);
                     }
                     @Override
                     public void onNewDhcpResults(DhcpResults dhcpResults) {
-                        mP2pStateMachine.sendMessage(IPM_DHCP_RESULTS, dhcpResults);
+                        mP2pStateMachine.sendMessage(IPC_DHCP_RESULTS, dhcpResults);
                     }
                     @Override
                     public void onProvisioningSuccess(LinkProperties newLp) {
-                        mP2pStateMachine.sendMessage(IPM_PROVISIONING_SUCCESS);
+                        mP2pStateMachine.sendMessage(IPC_PROVISIONING_SUCCESS);
                     }
                     @Override
                     public void onProvisioningFailure(LinkProperties newLp) {
-                        mP2pStateMachine.sendMessage(IPM_PROVISIONING_FAILURE);
+                        mP2pStateMachine.sendMessage(IPC_PROVISIONING_FAILURE);
                     }
                 },
                 mNwService);
 
-        final IpManager.ProvisioningConfiguration config =
-                mIpManager.buildProvisioningConfiguration()
-                          .withoutIPv6()
-                          .withoutIpReachabilityMonitor()
-                          .withPreDhcpAction(30 * 1000)
-                          .withProvisioningTimeoutMs(36 * 1000)
-                          .build();
-        mIpManager.startProvisioning(config);
+        final IpClient.ProvisioningConfiguration config =
+                mIpClient.buildProvisioningConfiguration()
+                         .withoutIPv6()
+                         .withoutIpReachabilityMonitor()
+                         .withPreDhcpAction(30 * 1000)
+                         .withProvisioningTimeoutMs(36 * 1000)
+                         .build();
+        mIpClient.startProvisioning(config);
     }
 
     /**
@@ -529,7 +529,7 @@
                     synchronized (mLock) {
                         mIWifiP2pIface = null;
                     }
-                }, mP2pStateMachine.getHandler().getLooper());
+                }, mP2pStateMachine.getHandler());
             }
 
             return messenger;
@@ -642,10 +642,10 @@
         pw.println("mDeathDataByBinder " + mDeathDataByBinder);
         pw.println();
 
-        final IpManager ipManager = mIpManager;
-        if (ipManager != null) {
-            pw.println("mIpManager:");
-            ipManager.dump(fd, pw, args);
+        final IpClient ipClient = mIpClient;
+        if (ipClient != null) {
+            pw.println("mIpClient:");
+            ipClient.dump(fd, pw, args);
         }
     }
 
@@ -945,11 +945,11 @@
                     case DROP_WIFI_USER_REJECT:
                     case GROUP_CREATING_TIMED_OUT:
                     case DISABLE_P2P_TIMED_OUT:
-                    case IPM_PRE_DHCP_ACTION:
-                    case IPM_POST_DHCP_ACTION:
-                    case IPM_DHCP_RESULTS:
-                    case IPM_PROVISIONING_SUCCESS:
-                    case IPM_PROVISIONING_FAILURE:
+                    case IPC_PRE_DHCP_ACTION:
+                    case IPC_POST_DHCP_ACTION:
+                    case IPC_DHCP_RESULTS:
+                    case IPC_PROVISIONING_SUCCESS:
+                    case IPC_PROVISIONING_FAILURE:
                     case WifiP2pMonitor.P2P_PROV_DISC_FAILURE_EVENT:
                     case SET_MIRACAST_MODE:
                     case WifiP2pManager.START_LISTEN:
@@ -1958,7 +1958,7 @@
                             startDhcpServer(mGroup.getInterface());
                         } else {
                             mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
-                            startIpManager(mGroup.getInterface());
+                            startIpClient(mGroup.getInterface());
                             WifiP2pDevice groupOwner = mGroup.getOwner();
                             WifiP2pDevice peer = mPeers.get(groupOwner.deviceAddress);
                             if (peer != null) {
@@ -2218,17 +2218,17 @@
                             loge("Disconnect on unknown device: " + device);
                         }
                         break;
-                    case IPM_PRE_DHCP_ACTION:
+                    case IPC_PRE_DHCP_ACTION:
                         mWifiNative.setP2pPowerSave(mGroup.getInterface(), false);
-                        mIpManager.completedPreDhcpAction();
+                        mIpClient.completedPreDhcpAction();
                         break;
-                    case IPM_POST_DHCP_ACTION:
+                    case IPC_POST_DHCP_ACTION:
                         mWifiNative.setP2pPowerSave(mGroup.getInterface(), true);
                         break;
-                    case IPM_DHCP_RESULTS:
+                    case IPC_DHCP_RESULTS:
                         mDhcpResults = (DhcpResults) message.obj;
                         break;
-                    case IPM_PROVISIONING_SUCCESS:
+                    case IPC_PROVISIONING_SUCCESS:
                         if (DBG) logd("mDhcpResults: " + mDhcpResults);
                         if (mDhcpResults != null) {
                             setWifiP2pInfoOnGroupFormation(mDhcpResults.serverAddress);
@@ -2244,7 +2244,7 @@
                             loge("Failed to add iface to local network " + e);
                         }
                         break;
-                    case IPM_PROVISIONING_FAILURE:
+                    case IPC_PROVISIONING_FAILURE:
                         loge("IP provisioning failed");
                         mWifiNative.p2pGroupRemove(mGroup.getInterface());
                         break;
@@ -3061,8 +3061,8 @@
             if (mGroup.isGroupOwner()) {
                 stopDhcpServer(mGroup.getInterface());
             } else {
-                if (DBG) logd("stop IpManager");
-                stopIpManager();
+                if (DBG) logd("stop IpClient");
+                stopIpClient();
                 try {
                     mNwService.removeInterfaceFromLocalNetwork(mGroup.getInterface());
                 } catch (RemoteException e) {
diff --git a/service/java/com/android/server/wifi/rtt/RttNative.java b/service/java/com/android/server/wifi/rtt/RttNative.java
index f731c19..fe1829f 100644
--- a/service/java/com/android/server/wifi/rtt/RttNative.java
+++ b/service/java/com/android/server/wifi/rtt/RttNative.java
@@ -74,6 +74,15 @@
         }
     }
 
+    /**
+     * 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);
 
@@ -98,6 +107,12 @@
             } else {
                 mIWifiRttController = null;
             }
+
+            if (mIWifiRttController == null) {
+                mRttService.disable();
+            } else {
+                mRttService.enable();
+            }
         }
     }
 
@@ -112,7 +127,7 @@
     public boolean rangeRequest(int cmdId, RangingRequest request) {
         if (VDBG) Log.v(TAG, "rangeRequest: cmdId=" + cmdId + ", request=" + request);
         synchronized (mLock) {
-            if (mIWifiRttController == null) {
+            if (!isReady()) {
                 Log.e(TAG, "rangeRequest: RttController is null");
                 return false;
             }
@@ -149,7 +164,7 @@
     public boolean rangeCancel(int cmdId, ArrayList<byte[]> macAddresses) {
         if (VDBG) Log.v(TAG, "rangeCancel: cmdId=" + cmdId);
         synchronized (mLock) {
-            if (mIWifiRttController == null) {
+            if (!isReady()) {
                 Log.e(TAG, "rangeCancel: RttController is null");
                 return false;
             }
@@ -223,12 +238,12 @@
                     config.mustRequestLci = false;
                     config.mustRequestLcr = false;
                     config.burstDuration = 15;
-                    if (config.channel.centerFreq > 5000) {
+                    config.bw = halChannelBandwidthFromScanResult(scanResult.channelWidth);
+                    if (config.bw == RttBw.BW_80MHZ || config.bw == RttBw.BW_160MHZ) {
                         config.preamble = RttPreamble.VHT;
                     } else {
                         config.preamble = RttPreamble.HT;
                     }
-                    config.bw = halChannelBandwidthFromScanResult(scanResult.channelWidth);
                 } catch (IllegalArgumentException e) {
                     Log.e(TAG, "Invalid configuration: " + e.getMessage());
                     continue;
diff --git a/service/java/com/android/server/wifi/rtt/RttService.java b/service/java/com/android/server/wifi/rtt/RttService.java
index 35f7615..0790dee 100644
--- a/service/java/com/android/server/wifi/rtt/RttService.java
+++ b/service/java/com/android/server/wifi/rtt/RttService.java
@@ -66,7 +66,6 @@
                     Context.WIFI_AWARE_SERVICE);
 
             RttNative rttNative = new RttNative(mImpl, halDeviceManager);
-            rttNative.start();
             mImpl.start(handlerThread.getLooper(), 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
index 1921047..e6e3591 100644
--- a/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
+++ b/service/java/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -16,7 +16,10 @@
 
 package com.android.server.wifi.rtt;
 
+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;
@@ -29,12 +32,15 @@
 import android.net.wifi.rtt.RangingRequest;
 import android.net.wifi.rtt.RangingResult;
 import android.net.wifi.rtt.RangingResultCallback;
+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.SystemClock;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -63,7 +69,9 @@
 
     private final Context mContext;
     private IWifiAwareManager mAwareBinder;
+    private RttNative mRttNative;
     private WifiPermissionsUtil mWifiPermissionsUtil;
+    private PowerManager mPowerManager;
 
     private RttServiceSynchronized mRttServiceSynchronized;
 
@@ -91,8 +99,32 @@
     public void start(Looper looper, IWifiAwareManager awareBinder, RttNative rttNative,
             WifiPermissionsUtil wifiPermissionsUtil) {
         mAwareBinder = awareBinder;
+        mRttNative = rttNative;
         mWifiPermissionsUtil = wifiPermissionsUtil;
         mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
+
+        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();
+        });
     }
 
     /*
@@ -108,6 +140,40 @@
     }
 
     /**
+     * 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.
      */
@@ -130,6 +196,15 @@
         }
         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 check
@@ -186,6 +261,13 @@
                 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(
@@ -249,6 +331,27 @@
             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);
+                }
+            }
+            mRttRequestQueue.clear();
+            mRangingTimeoutMessage.cancel();
+        }
+
         private void cleanUpOnClientDeath(int uid) {
             if (VDBG) {
                 Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
@@ -351,6 +454,19 @@
                 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 "
diff --git a/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java b/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
index 7a25e17..30d2b40 100644
--- a/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/HalDeviceManagerTest.java
@@ -51,6 +51,7 @@
 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;
@@ -82,7 +83,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 +98,10 @@
     private WifiStatus mStatusFail;
 
     private class HalDeviceManagerSpy extends HalDeviceManager {
+        HalDeviceManagerSpy() {
+            super(mClock);
+        }
+
         @Override
         protected IWifi getWifiServiceMockable() {
             return mWifiMock;
@@ -111,6 +118,7 @@
         MockitoAnnotations.initMocks(this);
 
         mTestLooper = new TestLooper();
+        mHandler = new Handler(mTestLooper.getLooper());
 
         // initialize dummy status objects
         mStatusOk = getStatus(WifiStatusCode.SUCCESS);
@@ -141,6 +149,10 @@
         dumpDut("after: ");
     }
 
+    //////////////////////////////////////////////////////////////////////////////////////
+    // Chip Independent Tests
+    //////////////////////////////////////////////////////////////////////////////////////
+
     /**
      * Test basic startup flow:
      * - IServiceManager registrations
@@ -196,9 +208,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,186 +286,319 @@
     }
 
     /**
-     * Validate creation of STA interface from blank start-up. The remove interface.
+     * 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 testCreateStaInterfaceNoInitMode() throws Exception {
-        final String name = "sta0";
-
-        BaselineChip chipMock = new BaselineChip();
+    public void testCacheMismatchError() throws Exception {
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
         executeAndValidateStartupSequence();
 
-        HalDeviceManager.InterfaceDestroyedListener idl = mock(
+        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener = mock(
                 HalDeviceManager.InterfaceDestroyedListener.class);
-        HalDeviceManager.InterfaceAvailableForRequestListener iafrl = mock(
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
                 HalDeviceManager.InterfaceAvailableForRequestListener.class);
 
-        IWifiStaIface iface = (IWifiStaIface) validateInterfaceSequence(chipMock,
+        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
-                name, // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                "sta0", // ifaceName
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
-                idl, // destroyedListener
-                iafrl // availableListener
+                staDestroyedListener, // destroyedListener
+                staAvailListener // availableListener
         );
-        collector.checkThat("allocated interface", iface, IsNull.notNullValue());
 
-        // act: remove interface
-        mDut.removeIface(iface);
+        // Request NAN
+        IWifiIface 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
+        );
+
+        // fiddle with the "chip" by removing the STA
+        chipMock.interfaceNames.get(IfaceType.STA).remove("sta0");
+
+        // now try to request another NAN
+        nanIface = mDut.createNanIface(nanDestroyedListener, mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
+                mHandler);
+        collector.checkThat("NAN can't be created", nanIface, 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();
+
+        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
+                nanDestroyedListener, nanAvailListener);
+    }
+
+    /**
+     * 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 {
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.class);
+
+        // get STA interface
+        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
+                null, // destroyedListener
+                null // availableListener
+        );
+        collector.checkThat("STA created", staIface, IsNull.notNullValue());
+
+        // act: register the same listener twice
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
+                mHandler);
+        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
+                mHandler);
         mTestLooper.dispatchAll();
 
-        // verify: callback triggered
-        mInOrder.verify(chipMock.chip).removeStaIface(name);
-        verify(idl).onDestroyed();
-        verify(iafrl).onAvailableForRequest();
+        // remove STA interface -> should trigger callbacks
+        mDut.removeIface(staIface);
+        mTestLooper.dispatchAll();
 
-        verifyNoMoreInteractions(mManagerStatusListenerMock, idl, iafrl);
+        // verify: only a single trigger
+        verify(staAvailListener).onAvailableForRequest();
+
+        verifyNoMoreInteractions(staAvailListener);
+    }
+
+    /**
+     * Validate that when no chip info is found an empty list is returned.
+     */
+    @Test
+    public void testGetSupportedIfaceTypesError() throws Exception {
+        // try API
+        Set<Integer> results = mDut.getSupportedIfaceTypes();
+
+        // verify results
+        assertEquals(0, results.size());
+    }
+
+    /**
+     * 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);
+
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence(2, true);
+    }
+
+    /**
+     * 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));
+
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence(START_HAL_RETRY_TIMES + 1, false);
+    }
+
+    /**
+     * 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));
+
+        TestChipV1 chipMock = new TestChipV1();
+        chipMock.initialize();
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip);
+        executeAndValidateInitializationSequence();
+        executeAndValidateStartupSequence(1, false);
+    }
+
+    /**
+     * Validate that isSupported() returns true when IServiceManager finds the vendor HAL daemon in
+     * the VINTF.
+     */
+    @Test
+    public void testIsSupportedTrue() throws Exception {
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock);
+        executeAndValidateInitializationSequence();
+        assertTrue(mDut.isSupported());
+    }
+
+    /**
+     * Validate that isSupported() returns false when IServiceManager does not find the vendor HAL
+     * daemon in the VINTF.
+     */
+    @Test
+    public void testIsSupportedFalse() throws Exception {
+        when(mServiceManagerMock.getTransport(
+                eq(IWifi.kInterfaceName), eq(HalDeviceManager.HAL_INSTANCE_NAME)))
+                .thenReturn(IServiceManager.Transport.EMPTY);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock);
+        executeAndValidateInitializationSequence(false);
+        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 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);
+    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 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);
+    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 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);
+    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);
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////
+    // 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 testCreateApWithStaModeUp() throws Exception {
+    public void testCreateApWithStaModeUpTestChipV1() throws Exception {
         final String name = "ap0";
 
-        BaselineChip chipMock = new BaselineChip();
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
                 mManagerStatusListenerMock);
@@ -467,10 +612,10 @@
 
         IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
                 true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
                 IfaceType.AP, // ifaceTypeToCreate
                 name, // ifaceName
-                BaselineChip.AP_CHIP_MODE_ID, // finalChipMode
+                TestChipV1.AP_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 idl, // destroyedListener
                 iafrl // availableListener
@@ -493,10 +638,10 @@
      * no change in chip mode.
      */
     @Test
-    public void testCreateApWithApModeUp() throws Exception {
+    public void testCreateApWithApModeUpTestChipV1() throws Exception {
         final String name = "ap0";
 
-        BaselineChip chipMock = new BaselineChip();
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
                 mManagerStatusListenerMock);
@@ -510,10 +655,10 @@
 
         IWifiApIface iface = (IWifiApIface) validateInterfaceSequence(chipMock,
                 true, // chipModeValid
-                BaselineChip.AP_CHIP_MODE_ID, // chipModeId
+                TestChipV1.AP_CHIP_MODE_ID, // chipModeId
                 IfaceType.AP, // ifaceTypeToCreate
                 name, // ifaceName
-                BaselineChip.AP_CHIP_MODE_ID, // finalChipMode
+                TestChipV1.AP_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 idl, // destroyedListener
                 iafrl // availableListener
@@ -550,8 +695,8 @@
      *    - Can create NAN when requested
      */
     @Test
-    public void testCreateSameAndDiffPriorities() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
+    public void testCreateSameAndDiffPrioritiesTestChipV1() throws Exception {
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
                 mManagerStatusListenerMock);
@@ -584,116 +729,158 @@
         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
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 staDestroyedListener, // destroyedListener
-                staAvailListener // availableListener
+                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, mTestLooper.getLooper());
-        mDut.registerDestroyedListener(staIface, staDestroyedListener, mTestLooper.getLooper());
+        mDut.registerDestroyedListener(staIface, staDestroyedListener2, mHandler);
+        mDut.registerDestroyedListener(staIface, staDestroyedListener, mHandler);
 
         // Request P2P
         IWifiIface p2pIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
                 IfaceType.P2P, // ifaceTypeToCreate
                 "p2p0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 p2pDestroyedListener, // destroyedListener
-                p2pAvailListener // availableListener
+                null // availableListener
         );
         collector.checkThat("allocated P2P interface", p2pIface, IsNull.notNullValue());
 
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+
         // Request AP
         IWifiIface apIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
                 IfaceType.AP, // ifaceTypeToCreate
                 "ap0", // ifaceName
-                BaselineChip.AP_CHIP_MODE_ID, // finalChipMode
+                TestChipV1.AP_CHIP_MODE_ID, // finalChipMode
                 new IWifiIface[]{staIface, p2pIface}, // tearDownList
                 apDestroyedListener, // destroyedListener
-                apAvailListener, // availableListener
+                null, // availableListener
                 // destroyedInterfacesDestroyedListeners...
                 staDestroyedListener, staDestroyedListener2, 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, mTestLooper.getLooper());
+        p2pIface = mDut.createP2pIface(p2pDestroyedListener, mHandler);
         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
+                TestChipV1.AP_CHIP_MODE_ID, // chipModeId
                 IfaceType.STA, // ifaceTypeToCreate
                 "sta0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 staDestroyedListener, // destroyedListener
-                staAvailListener, // availableListener
+                null, // availableListener
                 apDestroyedListener // destroyedInterfacesDestroyedListeners...
         );
         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();
 
         // Request P2P: expect success now
         p2pIface = validateInterfaceSequence(chipMock,
                 true, // chipModeValid
-                BaselineChip.STA_CHIP_MODE_ID, // chipModeId
+                TestChipV1.STA_CHIP_MODE_ID, // chipModeId
                 IfaceType.P2P, // ifaceTypeToCreate
                 "p2p0", // ifaceName
-                BaselineChip.STA_CHIP_MODE_ID, // finalChipMode
+                TestChipV1.STA_CHIP_MODE_ID, // finalChipMode
                 null, // tearDownList
                 p2pDestroyedListener2, // destroyedListener
-                p2pAvailListener // availableListener
+                null // availableListener
         );
 
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+
         // Request NAN: should fail
-        IWifiIface nanIface = mDut.createNanIface(nanDestroyedListener, mTestLooper.getLooper());
+        IWifiIface nanIface = mDut.createNanIface(nanDestroyedListener, mHandler);
         mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
-                mTestLooper.getLooper());
+                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();
 
         // Should now be able to request and get NAN
         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
         );
         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();
+        inOrderApAvail.verify(apAvailListener).onAvailableForRequest();
+        inOrderP2pAvail.verify(p2pAvailListener).onAvailableForRequest();
 
         verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
                 staDestroyedListener2, apDestroyedListener, apAvailListener, p2pDestroyedListener,
@@ -713,171 +900,18 @@
      *   - 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.
-     */
-    @Test
-    public void testCacheMismatchError() 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);
-
-        // 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
-        );
-
-        // fiddle with the "chip" by removing the STA
-        chipMock.interfaceNames.get(IfaceType.STA).remove("sta0");
-
-        // now try to request another NAN
-        nanIface = mDut.createNanIface(nanDestroyedListener, mTestLooper.getLooper());
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.NAN, nanAvailListener,
-                mTestLooper.getLooper());
-        collector.checkThat("NAN can't be created", nanIface, 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();
-
-        verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener, staAvailListener,
-                nanDestroyedListener, nanAvailListener);
+    public void testP2pAndNanInteractionsTestChipV1() throws Exception {
+        runP2pAndNanExclusiveInteractionsTestChip(new TestChipV1(), false,
+                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 baseline chip).
+     * is permitted (by TestChipV1 chip).
      */
     @Test
-    public void testDuplicateStaRequests() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
+    public void testDuplicateStaRequestsTestChipV1() throws Exception {
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
                 mManagerStatusListenerMock);
@@ -898,7 +932,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
                 staDestroyedListener1, // destroyedListener
                 staAvailListener1 // availableListener
@@ -906,7 +940,7 @@
         collector.checkThat("STA created", staIface1, IsNull.notNullValue());
 
         // get STA interface again
-        IWifiIface staIface2 = mDut.createStaIface(staDestroyedListener2, mTestLooper.getLooper());
+        IWifiIface staIface2 = mDut.createStaIface(staDestroyedListener2, mHandler);
         collector.checkThat("STA created", staIface2, IsNull.nullValue());
 
         verifyNoMoreInteractions(mManagerStatusListenerMock, staDestroyedListener1,
@@ -914,59 +948,11 @@
     }
 
     /**
-     * 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.
-     */
-    @Test
-    public void testDuplicateAvailableRegistrations() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
-        chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
-                mManagerStatusListenerMock);
-        executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence();
-
-        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
-                HalDeviceManager.InterfaceAvailableForRequestListener.class);
-
-        // get STA interface
-        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
-                null, // destroyedListener
-                null // availableListener
-        );
-        collector.checkThat("STA created", staIface, IsNull.notNullValue());
-
-        // act: register the same listener twice
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
-                mTestLooper.getLooper());
-        mDut.registerInterfaceAvailableForRequestListener(IfaceType.STA, staAvailListener,
-                mTestLooper.getLooper());
-        mTestLooper.dispatchAll();
-
-        // remove STA interface -> should trigger callbacks
-        mDut.removeIface(staIface);
-        mTestLooper.dispatchAll();
-
-        // verify: only a single trigger
-        verify(staAvailListener).onAvailableForRequest();
-
-        verifyNoMoreInteractions(staAvailListener);
-    }
-
-    /**
      * Validate that the getSupportedIfaceTypes API works when requesting for all chips.
      */
     @Test
-    public void testGetSupportedIfaceTypesAll() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
+    public void testGetSupportedIfaceTypesAllTestChipV1() throws Exception {
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
                 mManagerStatusListenerMock);
@@ -990,8 +976,8 @@
      * Validate that the getSupportedIfaceTypes API works when requesting for a specific chip.
      */
     @Test
-    public void testGetSupportedIfaceTypesOneChip() throws Exception {
-        BaselineChip chipMock = new BaselineChip();
+    public void testGetSupportedIfaceTypesOneChipTestChipV1() throws Exception {
+        TestChipV1 chipMock = new TestChipV1();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
                 mManagerStatusListenerMock);
@@ -1011,92 +997,280 @@
         assertEquals(correctResults, results);
     }
 
-    /**
-     * Validate that when no chip info is found an empty list is returned.
-     */
-    @Test
-    public void testGetSupportedIfaceTypesError() throws Exception {
-        // try API
-        Set<Integer> results = mDut.getSupportedIfaceTypes();
-
-        // verify results
-        assertEquals(0, results.size());
-    }
+    //////////////////////////////////////////////////////////////////////////////////////
+    // TestChipV2 Specific Tests
+    //////////////////////////////////////////////////////////////////////////////////////
 
     /**
-     * Test start HAL can retry upon failure.
+     * 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 testStartHalRetryUponNotAvailableFailure() throws Exception {
-        // Override the stubbing for mWifiMock in before().
-        when(mWifiMock.start())
-            .thenReturn(getStatus(WifiStatusCode.ERROR_NOT_AVAILABLE))
-            .thenReturn(mStatusOk);
-
-        BaselineChip chipMock = new BaselineChip();
+    public void testInterfaceCreationFlowTestChipV2() throws Exception {
+        TestChipV2 chipMock = new TestChipV2();
         chipMock.initialize();
         mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
                 mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence(2, true);
+        executeAndValidateStartupSequence();
+
+        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceDestroyedListener staDestroyedListener2 = mock(
+                HalDeviceManager.InterfaceDestroyedListener.class);
+        HalDeviceManager.InterfaceAvailableForRequestListener staAvailListener = mock(
+                HalDeviceManager.InterfaceAvailableForRequestListener.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 nanDestroyedListener = mock(
+                HalDeviceManager.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();
+
+        // 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),
+                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();
+
+        // 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);
     }
 
     /**
-     * Test start HAL fails after multiple retry failures.
+     * 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 testStartHalRetryFailUponMultipleNotAvailableFailures() throws Exception {
-        // Override the stubbing for mWifiMock in before().
-        when(mWifiMock.start()).thenReturn(getStatus(WifiStatusCode.ERROR_NOT_AVAILABLE));
+    public void testP2pAndNanInteractionsTestChipV2() throws Exception {
+        runP2pAndNanExclusiveInteractionsTestChip(new TestChipV2(), true, TestChipV2.CHIP_MODE_ID);
+    }
 
-        BaselineChip chipMock = new BaselineChip();
+    /**
+     * 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);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence(START_HAL_RETRY_TIMES + 1, false);
+        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);
     }
 
     /**
-     * Test start HAL fails after multiple retry failures.
+     * Validate that the getSupportedIfaceTypes API works when requesting for a specific chip.
      */
     @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();
+    public void testGetSupportedIfaceTypesOneChipTestChipV2() throws Exception {
+        TestChipV2 chipMock = new TestChipV2();
         chipMock.initialize();
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip);
+        mInOrder = inOrder(mServiceManagerMock, mWifiMock, chipMock.chip,
+                mManagerStatusListenerMock);
         executeAndValidateInitializationSequence();
-        executeAndValidateStartupSequence(1, false);
+        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 isSupported() returns true when IServiceManager finds the vendor HAL daemon in
-     * the VINTF.
-     */
-    @Test
-    public void testIsSupportedTrue() throws Exception {
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock);
-        executeAndValidateInitializationSequence();
-        assertTrue(mDut.isSupported());
-    }
-
-    /**
-     * Validate that isSupported() returns false when IServiceManager does not find the vendor HAL
-     * daemon in the VINTF.
-     */
-    @Test
-    public void testIsSupportedFalse() throws Exception {
-        when(mServiceManagerMock.getTransport(
-                eq(IWifi.kInterfaceName), eq(HalDeviceManager.HAL_INSTANCE_NAME)))
-                .thenReturn(IServiceManager.Transport.EMPTY);
-        mInOrder = inOrder(mServiceManagerMock, mWifiMock);
-        executeAndValidateInitializationSequence(false);
-        assertFalse(mDut.isSupported());
-    }
-
+    ///////////////////////////////////////////////////////////////////////////////////////
     // utilities
+    ///////////////////////////////////////////////////////////////////////////////////////
     private void dumpDut(String prefix) {
         StringWriter sw = new StringWriter();
         mDut.dump(null, new PrintWriter(sw), null);
@@ -1140,7 +1314,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,6 +1330,167 @@
         }
     }
 
+    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();
+
+        HalDeviceManager.InterfaceDestroyedListener idl = mock(
+                HalDeviceManager.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();
+        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,
+            boolean duplicateStas, int onlyChipMode) throws Exception {
+        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
+                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
+                nanDestroyedListener // destroyedInterfacesDestroyedListeners...
+        );
+
+        // 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();
+        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 (duplicateStas) {
+            // if there are duplicate STAs then expect an available callback for each step above
+            verify(staAvailListener, times(5)).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,
@@ -1181,7 +1516,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 +1527,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 +1538,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 +1549,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
@@ -1598,10 +1933,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 +1994,60 @@
                     .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));
+        }
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
index 892b597..91234e9 100644
--- a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -16,15 +16,24 @@
 
 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.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.content.Context;
+import android.content.Intent;
 import android.net.InterfaceConfiguration;
 import android.net.wifi.IApInterface;
 import android.net.wifi.WifiConfiguration;
@@ -32,6 +41,7 @@
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
 import android.os.INetworkManagementService;
+import android.os.UserHandle;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -47,6 +57,7 @@
 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}. */
@@ -66,6 +77,7 @@
 
     private final WifiConfiguration mDefaultApConfig = createDefaultApConfig();
 
+    @Mock Context mContext;
     TestLooper mLooper;
     @Mock WifiNative mWifiNative;
     @Mock SoftApManager.Listener mListener;
@@ -102,18 +114,20 @@
         return defaultConfig;
     }
 
-    private SoftApManager createSoftApManager(WifiConfiguration config) throws Exception {
+    private SoftApManager createSoftApManager(SoftApModeConfiguration config) throws Exception {
         when(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
         when(mApInterface.startHostapd()).thenReturn(true);
         when(mApInterface.stopHostapd()).thenReturn(true);
-        if (config == null) {
+        if (config.getWifiConfiguration() == null) {
             when(mWifiApConfigStore.getApConfiguration()).thenReturn(mDefaultApConfig);
         }
-        SoftApManager newSoftApManager = new SoftApManager(mLooper.getLooper(),
+        SoftApManager newSoftApManager = new SoftApManager(mContext,
+                                                           mLooper.getLooper(),
                                                            mWifiNative,
                                                            TEST_COUNTRY_CODE,
                                                            mListener,
                                                            mApInterface,
+                                                           TEST_INTERFACE_NAME,
                                                            mNmService,
                                                            mWifiApConfigStore,
                                                            config,
@@ -125,7 +139,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,7 +150,9 @@
         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);
     }
 
 
@@ -148,7 +166,9 @@
         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);
     }
 
     /** Tests softap startup if default config fails to load. **/
@@ -158,36 +178,155 @@
         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(),
                                                            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 the handling of stop command when soft AP is not started. */
+    /**
+     * 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(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
+        when(mApInterface.startHostapd()).thenReturn(true);
+        when(mApInterface.stopHostapd()).thenReturn(true);
+        when(mWifiNative.isHalStarted()).thenReturn(true);
+
+        SoftApManager newSoftApManager = new SoftApManager(mContext,
+                                                           mLooper.getLooper(),
+                                                           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(mApInterface.asBinder()).thenReturn(mApInterfaceBinder);
+        when(mApInterface.startHostapd()).thenReturn(true);
+        when(mApInterface.stopHostapd()).thenReturn(true);
+        when(mWifiNative.isHalStarted()).thenReturn(true);
+        when(mWifiNative.isGetChannelsForBandSupported()).thenReturn(true);
+
+        SoftApManager newSoftApManager = new SoftApManager(mContext,
+                                                           mLooper.getLooper(),
+                                                           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 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);
 
@@ -197,11 +336,30 @@
         verify(mApInterface).stopHostapd();
         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);
+
+        // reset to clear verified Intents for ap state change updates
+        reset(mContext);
 
         mDeathListenerCaptor.getValue().binderDied();
         mLooper.dispatchAll();
@@ -209,10 +367,22 @@
         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());
     }
 
     /** Starts soft AP and verifies that it is enabled successfully. */
-    protected void startSoftApAndVerifyEnabled(WifiConfiguration config) throws Exception {
+    protected void startSoftApAndVerifyEnabled(
+            SoftApModeConfiguration softApConfig) throws Exception {
         String expectedSSID;
         boolean expectedHiddenSsid;
         InOrder order = inOrder(mListener, mApInterfaceBinder, mApInterface, mNmService);
@@ -221,7 +391,8 @@
         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;
@@ -231,6 +402,8 @@
             expectedHiddenSsid = config.hiddenSSID;
         }
 
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+
         mSoftApManager.start();
         mLooper.dispatchAll();
         order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLING, 0);
@@ -243,6 +416,15 @@
         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());
     }
 
     /** Verifies that soft AP was not disabled. */
@@ -250,4 +432,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/VelocityBasedConnectedScoreTest.java b/tests/wifitests/src/com/android/server/wifi/VelocityBasedConnectedScoreTest.java
new file mode 100644
index 0000000..263cbed
--- /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 = 10.1; // proportional to pps
+        mWifiInfo.txBadRate = .5;
+        mWifiInfo.rxSuccessRate = 10.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/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index 6e0b775..c6a06e8 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -782,6 +782,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;
@@ -1060,7 +1085,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";
@@ -1108,7 +1133,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>,
@@ -1141,6 +1167,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}     /**/
     };
 
@@ -1161,6 +1189,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++) {
@@ -1180,6 +1209,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);
     }
 
     /**
@@ -1360,6 +1404,7 @@
      * 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/WifiStateMachinePrimeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java
index 060919d..2e26769 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java
@@ -92,7 +92,8 @@
     }
 
     private void enterSoftApActiveMode() throws Exception {
-        enterSoftApActiveMode(null);
+        enterSoftApActiveMode(
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
     }
 
     /**
@@ -100,10 +101,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(WIFI_IFACE_NAME)).thenReturn(mApInterface);
+        when(mApInterface.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         doAnswer(
                 new Answer<Object>() {
                     public SoftApManager answer(InvocationOnMock invocation) {
@@ -111,14 +113,16 @@
                         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)) {
@@ -186,7 +190,8 @@
     public void testDisableWifiFromSoftApModeState() throws Exception {
         // Use a failure getting wificond to stay in the SoftAPModeState
         when(mWifiInjector.makeWificond()).thenReturn(null);
-        mWifiStateMachinePrime.enterSoftAPMode(null);
+        mWifiStateMachinePrime.enterSoftAPMode(
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mLooper.dispatchAll();
         assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
 
@@ -221,7 +226,8 @@
     @Test
     public void testWificondNullWhenSwitchingToApMode() throws Exception {
         when(mWifiInjector.makeWificond()).thenReturn(null);
-        mWifiStateMachinePrime.enterSoftAPMode(null);
+        mWifiStateMachinePrime.enterSoftAPMode(
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mLooper.dispatchAll();
         assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
     }
@@ -236,13 +242,14 @@
     public void testAPInterfaceFailedWhenSwitchingToApMode() throws Exception {
         when(mWifiInjector.makeWificond()).thenReturn(mWificond);
         when(mWificond.createApInterface(WIFI_IFACE_NAME)).thenReturn(null);
-        mWifiStateMachinePrime.enterSoftAPMode(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.
      */
@@ -250,7 +257,8 @@
     public void testEnterSoftApModeActiveWhenAlreadyInSoftApMode() throws Exception {
         when(mWifiInjector.makeWificond()).thenReturn(mWificond);
         when(mWificond.createApInterface(WIFI_IFACE_NAME)).thenReturn(null);
-        mWifiStateMachinePrime.enterSoftAPMode(null);
+        mWifiStateMachinePrime.enterSoftAPMode(
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mLooper.dispatchAll();
         assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
 
@@ -297,7 +305,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);
     }
 
     /**
@@ -310,7 +320,7 @@
      */
     @Test
     public void testNullConfigIsPassedToWifiInjector() throws Exception {
-        enterSoftApActiveMode(null);
+        enterSoftApActiveMode();
     }
 
     /**
@@ -319,22 +329,25 @@
      * config - this second call should use the correct config.
      */
     @Test
-    public void testNullConfigFailsSecondCallWithConfigSuccessful() throws Exception {
+    public void testNullApModeConfigFails() throws Exception {
         when(mWifiInjector.makeWificond()).thenReturn(mWificond);
         when(mWificond.createApInterface(WIFI_IFACE_NAME)).thenReturn(null);
-        mWifiStateMachinePrime.enterSoftAPMode(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
@@ -343,10 +356,12 @@
         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));
     }
 
     /**
@@ -358,26 +373,33 @@
     public void testStartSoftApModeTwiceWithTwoConfigs() throws Exception {
         when(mWifiInjector.makeWificond()).thenReturn(mWificond);
         when(mWificond.createApInterface(WIFI_IFACE_NAME)).thenReturn(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());
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
index f7c98ea..51beee9 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
@@ -51,7 +51,7 @@
 import android.net.NetworkFactory;
 import android.net.NetworkRequest;
 import android.net.dhcp.DhcpClient;
-import android.net.ip.IpManager;
+import android.net.ip.IpClient;
 import android.net.wifi.IApInterface;
 import android.net.wifi.IClientInterface;
 import android.net.wifi.IWificond;
@@ -199,12 +199,12 @@
         IBinder batteryStatsBinder = mockService(BatteryStats.class, IBatteryStats.class);
         when(facade.getService(BatteryStats.SERVICE_NAME)).thenReturn(batteryStatsBinder);
 
-        when(facade.makeIpManager(any(Context.class), anyString(), any(IpManager.Callback.class)))
+        when(facade.makeIpClient(any(Context.class), anyString(), any(IpClient.Callback.class)))
                 .then(new AnswerWithArguments() {
-                    public IpManager answer(
-                            Context context, String ifname, IpManager.Callback callback) {
-                        mIpManagerCallback = callback;
-                        return mIpManager;
+                    public IpClient answer(
+                            Context context, String ifname, IpClient.Callback callback) {
+                        mIpClientCallback = callback;
+                        return mIpClient;
                     }
                 });
 
@@ -302,13 +302,13 @@
     }
 
     private void injectDhcpSuccess(DhcpResults dhcpResults) {
-        mIpManagerCallback.onNewDhcpResults(dhcpResults);
-        mIpManagerCallback.onProvisioningSuccess(new LinkProperties());
+        mIpClientCallback.onNewDhcpResults(dhcpResults);
+        mIpClientCallback.onProvisioningSuccess(new LinkProperties());
     }
 
     private void injectDhcpFailure() {
-        mIpManagerCallback.onNewDhcpResults(null);
-        mIpManagerCallback.onProvisioningFailure(new LinkProperties());
+        mIpClientCallback.onNewDhcpResults(null);
+        mIpClientCallback.onProvisioningFailure(new LinkProperties());
     }
 
     static final String   sSSID = "\"GoogleGuest\"";
@@ -331,7 +331,7 @@
     Context mContext;
     MockResources mResources;
     FrameworkFacade mFrameworkFacade;
-    IpManager.Callback mIpManagerCallback;
+    IpClient.Callback mIpClientCallback;
     PhoneStateListener mPhoneStateListener;
     NetworkRequest mDefaultNetworkRequest;
 
@@ -363,7 +363,7 @@
     @Mock PasspointManager mPasspointManager;
     @Mock SelfRecovery mSelfRecovery;
     @Mock WifiPermissionsUtil mWifiPermissionsUtil;
-    @Mock IpManager mIpManager;
+    @Mock IpClient mIpClient;
     @Mock TelephonyManager mTelephonyManager;
     @Mock WrongPasswordNotifier mWrongPasswordNotifier;
     @Mock Clock mClock;
@@ -401,8 +401,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);
@@ -588,30 +589,17 @@
         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);
-
-        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
-        verify(mContext, times(4))
-                .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, 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);
+        assertEquals(WIFI_AP_STATE_DISABLED, mWsm.syncGetWifiApState());
     }
 
     @Test
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
index f6524e0..362d3b1 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;
@@ -58,6 +82,7 @@
 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;
@@ -66,9 +91,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;
@@ -203,7 +225,8 @@
         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();
 
     }
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..e5a7968 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(
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 ec4c4ba..a391d3d 100644
--- a/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/aware/WifiAwareStateManagerTest.java
@@ -3060,7 +3060,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();
diff --git a/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java b/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java
index a1b22e0..51fed83 100644
--- a/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java
@@ -18,6 +18,8 @@
 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;
@@ -90,6 +92,8 @@
         mDut.start();
         verify(mockHalDeviceManager).registerStatusListener(mHdmStatusListener.capture(), any());
         verify(mockRttController).registerEventCallback(any());
+        verify(mockRttServiceImpl).enable();
+        assertTrue(mDut.isReady());
     }
 
     /**
@@ -144,11 +148,13 @@
         // (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(mockRttController);
+        verifyNoMoreInteractions(mockRttServiceImpl, 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
index 410d4d9..bfeb068 100644
--- a/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/rtt/RttServiceImplTest.java
@@ -21,14 +21,17 @@
 
 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;
@@ -37,7 +40,10 @@
 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.wifi.aware.IWifiAwareMacAddressProvider;
 import android.net.wifi.aware.IWifiAwareManager;
@@ -46,9 +52,13 @@
 import android.net.wifi.rtt.RangingRequest;
 import android.net.wifi.rtt.RangingResult;
 import android.net.wifi.rtt.RangingResultCallback;
+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.test.TestLooper;
 import android.util.Pair;
 
@@ -75,6 +85,8 @@
     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;
@@ -138,13 +150,29 @@
         mAlarmManager = new TestAlarmManager();
         when(mockContext.getSystemService(Context.ALARM_SERVICE))
                 .thenReturn(mAlarmManager.getAlarmManager());
-        mInOrder = inOrder(mAlarmManager.getAlarmManager());
+        mInOrder = inOrder(mAlarmManager.getAlarmManager(), mockContext);
 
         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);
+
         mDut.start(mMockLooper.getLooper(), 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());
     }
 
     /**
@@ -185,6 +213,7 @@
             mMockLooper.dispatchAll();
         }
 
+        verify(mockNative, atLeastOnce()).isReady();
         verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
     }
 
@@ -232,6 +261,7 @@
 
         assertTrue(compareListContentsNoOrdering(results.second, mListCaptor.getValue()));
 
+        verify(mockNative, atLeastOnce()).isReady();
         verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
     }
 
@@ -282,6 +312,7 @@
             }
         }
 
+        verify(mockNative, atLeastOnce()).isReady();
         verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
     }
 
@@ -312,6 +343,7 @@
         verify(mockCallback).onRangingFailure(eq(RangingResultCallback.STATUS_CODE_FAIL));
         verifyWakeupCancelled();
 
+        verify(mockNative, atLeastOnce()).isReady();
         verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
     }
 
@@ -379,6 +411,7 @@
             }
         }
 
+        verify(mockNative, atLeastOnce()).isReady();
         verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
     }
 
@@ -413,6 +446,7 @@
         verify(mockCallback).onRangingResults(results.second);
         verifyWakeupCancelled();
 
+        verify(mockNative, atLeastOnce()).isReady();
         verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
     }
 
@@ -447,6 +481,7 @@
         assertTrue(compareListContentsNoOrdering(results.second, mListCaptor.getValue()));
         verifyWakeupCancelled();
 
+        verify(mockNative, atLeastOnce()).isReady();
         verifyNoMoreInteractions(mockNative, mockCallback, mAlarmManager.getAlarmManager());
     }
 
@@ -492,13 +527,103 @@
         verify(mockCallback).onRangingResults(result2.second);
         verifyWakeupCancelled();
 
+        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, request1, mockCallback);
+        mDut.startRanging(mockIbinder, mPackageName, 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, 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),
@@ -510,6 +635,19 @@
                 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, byte[]> mPeerIdToMacMap;