Merge "[RTT2] Update RTT service name"
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index 2c16444..31124d5 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -79,6 +79,17 @@
                 contentObserver);
     }
 
+    /**
+     * Helper method for classes to unregister a ContentObserver
+     * {@see ContentResolver#unregisterContentObserver(ContentObserver)}.
+     *
+     * @param context
+     * @param contentObserver
+     */
+    public void unregisterContentObserver(Context context, ContentObserver contentObserver) {
+        context.getContentResolver().unregisterContentObserver(contentObserver);
+    }
+
     public IBinder getService(String serviceName) {
         return ServiceManager.getService(serviceName);
     }
diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java
index 429e3c3..2200618 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -23,21 +23,27 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
+import android.database.ContentObserver;
 import android.net.InterfaceConfiguration;
 import android.net.wifi.IApInterface;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.Handler;
 import android.os.INetworkManagementService;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.wifi.WifiNative.SoftApListener;
 import com.android.server.wifi.util.ApConfigUtil;
@@ -51,8 +57,15 @@
 public class SoftApManager implements ActiveModeManager {
     private static final String TAG = "SoftApManager";
 
-    private final Context mContext;
+    // Minimum limit to use for timeout delay if the value from overlay setting is too small.
+    private static final int MIN_SOFT_AP_TIMEOUT_DELAY_MS = 600_000;  // 10 minutes
 
+    @VisibleForTesting
+    public static final String SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG = TAG
+            + " Soft AP Send Message Timeout";
+
+    private final Context mContext;
+    private final FrameworkFacade mFrameworkFacade;
     private final WifiNative mWifiNative;
 
     private final String mCountryCode;
@@ -72,8 +85,9 @@
     private final int mMode;
     private WifiConfiguration mApConfig;
 
-    private int mNumAssociatedStations = 0;
-
+    /**
+     * Listener for soft AP events.
+     */
     private final SoftApListener mSoftApListener = new SoftApListener() {
         @Override
         public void onNumAssociatedStationsChanged(int numStations) {
@@ -82,7 +96,6 @@
         }
     };
 
-
     /**
      * Listener for soft AP state changes.
      */
@@ -97,6 +110,7 @@
 
     public SoftApManager(Context context,
                          Looper looper,
+                         FrameworkFacade framework,
                          WifiNative wifiNative,
                          String countryCode,
                          Listener listener,
@@ -106,9 +120,8 @@
                          WifiApConfigStore wifiApConfigStore,
                          @NonNull SoftApModeConfiguration apConfig,
                          WifiMetrics wifiMetrics) {
-        mStateMachine = new SoftApStateMachine(looper);
-
         mContext = context;
+        mFrameworkFacade = framework;
         mWifiNative = wifiNative;
         mCountryCode = countryCode;
         mListener = listener;
@@ -124,6 +137,7 @@
             mApConfig = config;
         }
         mWifiMetrics = wifiMetrics;
+        mStateMachine = new SoftApStateMachine(looper);
     }
 
     /**
@@ -141,29 +155,6 @@
     }
 
     /**
-     * Get number of stations associated with this soft AP
-     */
-    @VisibleForTesting
-    public int getNumAssociatedStations() {
-        return mNumAssociatedStations;
-    }
-
-    /**
-     * Set number of stations associated with this soft AP
-     * @param numStations Number of connected stations
-     */
-    private void setNumAssociatedStations(int numStations) {
-        if (mNumAssociatedStations == numStations) {
-            return;
-        }
-        mNumAssociatedStations = numStations;
-        Log.d(TAG, "Number of associated stations changed: " + mNumAssociatedStations);
-
-        // TODO:(b/63906412) send it up to settings.
-        mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(mNumAssociatedStations, mMode);
-    }
-
-    /**
      * Update AP state.
      * @param newState new AP state
      * @param currentState current AP state
@@ -253,6 +244,8 @@
         public static final int CMD_WIFICOND_BINDER_DEATH = 2;
         public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
         public static final int CMD_NUM_ASSOCIATED_STATIONS_CHANGED = 4;
+        public static final int CMD_NO_ASSOCIATED_STATIONS_TIMEOUT = 5;
+        public static final int CMD_TIMEOUT_TOGGLE_CHANGED = 6;
 
         private final State mIdleState = new IdleState();
         private final State mStartedState = new StartedState();
@@ -313,7 +306,6 @@
                         }
                         updateApState(WifiManager.WIFI_AP_STATE_ENABLING,
                                 WifiManager.WIFI_AP_STATE_DISABLED, 0);
-                        setNumAssociatedStations(0);
                         if (!mWifiNative.registerWificondDeathHandler(mWificondDeathRecipient)) {
                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
                                     WifiManager.WIFI_AP_STATE_ENABLING,
@@ -375,6 +367,92 @@
         private class StartedState extends State {
             private boolean mIfaceIsUp;
 
+            private int mNumAssociatedStations;
+
+            private boolean mTimeoutEnabled;
+            private int mTimeoutDelay;
+            private WakeupMessage mSoftApTimeoutMessage;
+            private SoftApTimeoutEnabledSettingObserver mSettingObserver;
+
+            /**
+            * Observer for timeout settings changes.
+            */
+            private class SoftApTimeoutEnabledSettingObserver extends ContentObserver {
+                SoftApTimeoutEnabledSettingObserver(Handler handler) {
+                    super(handler);
+                }
+
+                public void register() {
+                    mFrameworkFacade.registerContentObserver(mContext,
+                            Settings.Global.getUriFor(Settings.Global.SOFT_AP_TIMEOUT_ENABLED),
+                            true, this);
+                    mTimeoutEnabled = getValue();
+                }
+
+                public void unregister() {
+                    mFrameworkFacade.unregisterContentObserver(mContext, this);
+                }
+
+                @Override
+                public void onChange(boolean selfChange) {
+                    super.onChange(selfChange);
+                    mStateMachine.sendMessage(SoftApStateMachine.CMD_TIMEOUT_TOGGLE_CHANGED,
+                            getValue() ? 1 : 0);
+                }
+
+                private boolean getValue() {
+                    boolean enabled = mFrameworkFacade.getIntegerSetting(mContext,
+                            Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1) == 1;
+                    return enabled;
+                }
+            }
+
+            private int getConfigSoftApTimeoutDelay() {
+                int delay = mContext.getResources().getInteger(
+                        R.integer.config_wifi_framework_soft_ap_timeout_delay);
+                if (delay < MIN_SOFT_AP_TIMEOUT_DELAY_MS) {
+                    delay = MIN_SOFT_AP_TIMEOUT_DELAY_MS;
+                    Log.w(TAG, "Overriding timeout delay with minimum limit value");
+                }
+                Log.d(TAG, "Timeout delay: " + delay);
+                return delay;
+            }
+
+            private void scheduleTimeoutMessage() {
+                if (!mTimeoutEnabled) {
+                    return;
+                }
+                mSoftApTimeoutMessage.schedule(SystemClock.elapsedRealtime() + mTimeoutDelay);
+                Log.d(TAG, "Timeout message scheduled");
+            }
+
+            private void cancelTimeoutMessage() {
+                mSoftApTimeoutMessage.cancel();
+                Log.d(TAG, "Timeout message canceled");
+            }
+
+            /**
+             * Set number of stations associated with this soft AP
+             * @param numStations Number of connected stations
+             */
+            private void setNumAssociatedStations(int numStations) {
+                if (mNumAssociatedStations == numStations) {
+                    return;
+                }
+                mNumAssociatedStations = numStations;
+                Log.d(TAG, "Number of associated stations changed: " + mNumAssociatedStations);
+
+                // TODO:(b/63906412) send it up to settings.
+                mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(mNumAssociatedStations,
+                        mMode);
+
+                if (mNumAssociatedStations == 0) {
+                    scheduleTimeoutMessage();
+                } else {
+                    cancelTimeoutMessage();
+                }
+            }
+
             private void onUpChanged(boolean isUp) {
                 if (isUp == mIfaceIsUp) {
                     return;  // no change
@@ -388,9 +466,7 @@
                 } else {
                     // TODO: handle the case where the interface was up, but goes down
                 }
-
                 mWifiMetrics.addSoftApUpChangedEvent(isUp, mMode);
-                setNumAssociatedStations(0);
             }
 
             @Override
@@ -404,6 +480,30 @@
                 if (config != null) {
                     onUpChanged(config.isUp());
                 }
+
+                mTimeoutDelay = getConfigSoftApTimeoutDelay();
+                Handler handler = mStateMachine.getHandler();
+                mSoftApTimeoutMessage = new WakeupMessage(mContext, handler,
+                        SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG,
+                        SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT);
+                mSettingObserver = new SoftApTimeoutEnabledSettingObserver(handler);
+
+                if (mSettingObserver != null) {
+                    mSettingObserver.register();
+                }
+                Log.d(TAG, "Resetting num stations on start");
+                mNumAssociatedStations = 0;
+                scheduleTimeoutMessage();
+            }
+
+            @Override
+            public void exit() {
+                if (mSettingObserver != null) {
+                    mSettingObserver.unregister();
+                }
+                Log.d(TAG, "Resetting num stations on stop");
+                mNumAssociatedStations = 0;
+                cancelTimeoutMessage();
             }
 
             @Override
@@ -414,8 +514,22 @@
                             Log.e(TAG, "Invalid number of associated stations: " + message.arg1);
                             break;
                         }
+                        Log.d(TAG, "Setting num stations on CMD_NUM_ASSOCIATED_STATIONS_CHANGED");
                         setNumAssociatedStations(message.arg1);
                         break;
+                    case CMD_TIMEOUT_TOGGLE_CHANGED:
+                        boolean isEnabled = (message.arg1 == 1);
+                        if (mTimeoutEnabled == isEnabled) {
+                            break;
+                        }
+                        mTimeoutEnabled = isEnabled;
+                        if (!mTimeoutEnabled) {
+                            cancelTimeoutMessage();
+                        }
+                        if (mTimeoutEnabled && mNumAssociatedStations == 0) {
+                            scheduleTimeoutMessage();
+                        }
+                        break;
                     case CMD_INTERFACE_STATUS_CHANGED:
                         if (message.obj != mNetworkObserver) {
                             // This is from some time before the most recent configuration.
@@ -427,11 +541,21 @@
                     case CMD_START:
                         // Already started, ignore this command.
                         break;
+                    case CMD_NO_ASSOCIATED_STATIONS_TIMEOUT:
+                        if (!mTimeoutEnabled) {
+                            Log.wtf(TAG, "Timeout message received while timeout is disabled."
+                                    + " Dropping.");
+                            break;
+                        }
+                        if (mNumAssociatedStations != 0) {
+                            Log.wtf(TAG, "Timeout message received but has clients. Dropping.");
+                            break;
+                        }
+                        Log.i(TAG, "Timeout message received. Stopping soft AP.");
                     case CMD_WIFICOND_BINDER_DEATH:
                     case CMD_STOP:
                         updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
                                 WifiManager.WIFI_AP_STATE_ENABLED, 0);
-                        setNumAssociatedStations(0);
                         stopSoftAp();
                         if (message.what == CMD_WIFICOND_BINDER_DEATH) {
                             updateApState(WifiManager.WIFI_AP_STATE_FAILED,
diff --git a/service/java/com/android/server/wifi/WakeupConfigStoreData.java b/service/java/com/android/server/wifi/WakeupConfigStoreData.java
index f839ac8..5775117 100644
--- a/service/java/com/android/server/wifi/WakeupConfigStoreData.java
+++ b/service/java/com/android/server/wifi/WakeupConfigStoreData.java
@@ -45,8 +45,10 @@
 
     /**
      * Interface defining a data source for the store data.
+     *
+     * @param <T> Type of data source
      */
-    interface DataSource<T> {
+    public interface DataSource<T> {
         /**
          * Returns the data from the data source.
          */
diff --git a/service/java/com/android/server/wifi/WakeupController.java b/service/java/com/android/server/wifi/WakeupController.java
index a3c095a..8787bfd 100644
--- a/service/java/com/android/server/wifi/WakeupController.java
+++ b/service/java/com/android/server/wifi/WakeupController.java
@@ -24,6 +24,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
 /**
  * WakeupController is responsible managing Auto Wifi.
  *
@@ -38,16 +41,26 @@
     private final Handler mHandler;
     private final FrameworkFacade mFrameworkFacade;
     private final ContentObserver mContentObserver;
+    private final WakeupLock mWakeupLock;
+    private final WifiConfigManager mWifiConfigManager;
 
     /** Whether this feature is enabled in Settings. */
     private boolean mWifiWakeupEnabled;
 
+    /** Whether the WakeupController is currently active. */
+    private boolean mIsActive = false;
+
     public WakeupController(
             Context context,
             Looper looper,
+            WakeupLock wakeupLock,
+            WifiConfigManager wifiConfigManager,
+            WifiConfigStore wifiConfigStore,
             FrameworkFacade frameworkFacade) {
         mContext = context;
         mHandler = new Handler(looper);
+        mWakeupLock = wakeupLock;
+        mWifiConfigManager = wifiConfigManager;
         mFrameworkFacade = frameworkFacade;
         mContentObserver = new ContentObserver(mHandler) {
             @Override
@@ -59,6 +72,19 @@
         mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
                 Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver);
         mContentObserver.onChange(false /* selfChange */);
+
+        // registering the store data here has the effect of reading the persisted value of the
+        // data sources after system boot finishes
+        WakeupConfigStoreData wakeupConfigStoreData =
+                new WakeupConfigStoreData(new IsActiveDataSource(), mWakeupLock.getDataSource());
+        wifiConfigStore.registerStoreData(wakeupConfigStoreData);
+    }
+
+    private void setActive(boolean isActive) {
+        if (mIsActive != isActive) {
+            mIsActive = isActive;
+            mWifiConfigManager.saveToStore(false /* forceWrite */);
+        }
     }
 
     /**
@@ -71,4 +97,26 @@
     boolean isEnabled() {
         return mWifiWakeupEnabled;
     }
+
+    /** Dumps wakeup controller state. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Dump of WakeupController");
+        pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled);
+        pw.println("USE_PLATFORM_WIFI_WAKE: " + USE_PLATFORM_WIFI_WAKE);
+        pw.println("mIsActive: " + mIsActive);
+        mWakeupLock.dump(fd, pw, args);
+    }
+
+    private class IsActiveDataSource implements WakeupConfigStoreData.DataSource<Boolean> {
+
+        @Override
+        public Boolean getData() {
+            return mIsActive;
+        }
+
+        @Override
+        public void setData(Boolean data) {
+            mIsActive = data;
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WakeupLock.java b/service/java/com/android/server/wifi/WakeupLock.java
index 73cda91..1fcd9f8 100644
--- a/service/java/com/android/server/wifi/WakeupLock.java
+++ b/service/java/com/android/server/wifi/WakeupLock.java
@@ -17,12 +17,16 @@
 package com.android.server.wifi;
 
 import android.util.ArrayMap;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * A lock to determine whether Auto Wifi can re-enable Wifi.
@@ -31,18 +35,24 @@
  */
 public class WakeupLock {
 
+    private static final String TAG = WakeupLock.class.getSimpleName();
+
     @VisibleForTesting
     static final int CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT = 3;
 
-    private Map<ScanResultMatchInfo, Integer> mLockedNetworks = new ArrayMap<>();
 
-    // TODO(easchwar) read initial value of mLockedNetworks from file
-    public WakeupLock() {
+    private final WifiConfigManager mWifiConfigManager;
+    private final Map<ScanResultMatchInfo, Integer> mLockedNetworks = new ArrayMap<>();
+
+    public WakeupLock(WifiConfigManager wifiConfigManager) {
+        mWifiConfigManager = wifiConfigManager;
     }
 
     /**
      * Initializes the WakeupLock with the given {@link ScanResultMatchInfo} list.
      *
+     * <p>This saves the wakeup lock to the store.
+     *
      * @param scanResultList list of ScanResultMatchInfos to start the lock with
      */
     public void initialize(Collection<ScanResultMatchInfo> scanResultList) {
@@ -50,17 +60,23 @@
         for (ScanResultMatchInfo scanResultMatchInfo : scanResultList) {
             mLockedNetworks.put(scanResultMatchInfo, CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT);
         }
+
+        Log.d(TAG, "Lock initialized. Number of networks: " + mLockedNetworks.size());
+
+        mWifiConfigManager.saveToStore(false /* forceWrite */);
     }
 
     /**
      * Updates the lock with the given {@link ScanResultMatchInfo} list.
      *
      * <p>If a network in the lock is not present in the list, reduce the number of scans
-     * required to evict by one. Remove any entries in the list with 0 scans required to evict.
+     * required to evict by one. Remove any entries in the list with 0 scans required to evict. If
+     * any entries in the lock are removed, the store is updated.
      *
      * @param scanResultList list of present ScanResultMatchInfos to update the lock with
      */
     public void update(Collection<ScanResultMatchInfo> scanResultList) {
+        boolean hasChanged = false;
         Iterator<Map.Entry<ScanResultMatchInfo, Integer>> it =
                 mLockedNetworks.entrySet().iterator();
         while (it.hasNext()) {
@@ -76,9 +92,13 @@
             entry.setValue(entry.getValue() - 1);
             if (entry.getValue() <= 0) {
                 it.remove();
+                hasChanged = true;
             }
         }
-        // TODO(easchwar) write the updated list to file
+
+        if (hasChanged) {
+            mWifiConfigManager.saveToStore(false /* forceWrite */);
+        }
     }
 
     /**
@@ -87,4 +107,36 @@
     public boolean isEmpty() {
         return mLockedNetworks.isEmpty();
     }
+
+    /** Returns the data source for the WakeupLock config store data. */
+    public WakeupConfigStoreData.DataSource<Set<ScanResultMatchInfo>> getDataSource() {
+        return new WakeupLockDataSource();
+    }
+
+    /** Dumps wakeup lock contents. */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("WakeupLock: ");
+        pw.println("Locked networks: " + mLockedNetworks.size());
+        for (Map.Entry<ScanResultMatchInfo, Integer> entry : mLockedNetworks.entrySet()) {
+            pw.println(entry.getKey() + ", scans to evict: " + entry.getValue());
+        }
+    }
+
+    private class WakeupLockDataSource
+            implements WakeupConfigStoreData.DataSource<Set<ScanResultMatchInfo>> {
+
+        @Override
+        public Set<ScanResultMatchInfo> getData() {
+            return mLockedNetworks.keySet();
+        }
+
+        @Override
+        public void setData(Set<ScanResultMatchInfo> data) {
+            mLockedNetworks.clear();
+            for (ScanResultMatchInfo network : data) {
+                mLockedNetworks.put(network, CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT);
+            }
+
+        }
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index f6770a5..dc48163 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -230,7 +230,8 @@
                 new OpenNetworkRecommender(),
                 new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade));
         mWakeupController = new WakeupController(mContext,
-                mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade);
+                mWifiStateMachineHandlerThread.getLooper(), new WakeupLock(mWifiConfigManager),
+                mWifiConfigManager, mWifiConfigStore, mFrameworkFacade);
         mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
         mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
                 mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
@@ -380,10 +381,9 @@
                                            @NonNull IApInterface apInterface,
                                            @NonNull String ifaceName,
                                            @NonNull SoftApModeConfiguration config) {
-        return new SoftApManager(mContext, mWifiServiceHandlerThread.getLooper(),
-                                 mWifiNative, mCountryCode.getCountryCode(),
-                                 listener, apInterface, ifaceName, nmService,
-                                 mWifiApConfigStore, config, mWifiMetrics);
+        return new SoftApManager(mContext, mWifiStateMachineHandlerThread.getLooper(),
+                mFrameworkFacade, mWifiNative, mCountryCode.getCountryCode(), listener, apInterface,
+                ifaceName, nmService, mWifiApConfigStore, config, mWifiMetrics);
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 2f1b550..3118761 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -152,6 +152,105 @@
         }
     }
 
+    /**
+     * TODO(b/69426063): NEW API Surface for interface management. This will eventually
+     * deprecate the other interface management API's above. But, for now there will be
+     * some duplication to ease transition.
+     */
+    /**
+     * Initialize the native modules.
+     *
+     * @return true on success, false otherwise.
+     */
+    public boolean initialize() {
+        return false;
+    }
+
+    /**
+     * Callback to notify when the status of one of the native daemons
+     * (wificond, wpa_supplicant & vendor HAL) changes.
+     */
+    public interface StatusListener {
+        /**
+         * @param allReady Indicates if all the native daemons are ready for operation or not.
+         */
+        void onStatusChanged(boolean allReady);
+    }
+
+    /**
+     * Register a StatusListener to get notified about any status changes from the native daemons.
+     *
+     * It is safe to re-register the same callback object - duplicates are detected and only a
+     * single copy kept.
+     *
+     * @param listener StatusListener listener object.
+     */
+    public void registerStatusListener(@NonNull StatusListener listener) {
+    }
+
+    /**
+     * Callback to notify when the associated interface is destroyed, up or down.
+     */
+    public interface InterfaceCallback {
+        /**
+         * Interface destroyed by HalDeviceManager.
+         *
+         * @param ifaceName Name of the iface.
+         */
+        void onDestroyed(String ifaceName);
+
+        /**
+         * Interface is up.
+         *
+         * @param ifaceName Name of the iface.
+         */
+        void onUp(String ifaceName);
+
+        /**
+         * Interface is down.
+         *
+         * @param ifaceName Name of the iface.
+         */
+        void onDown(String ifaceName);
+    }
+
+    /**
+     * Setup an interface for Client mode operations.
+     *
+     * This method configures an interface in STA mode in all the native daemons
+     * (wificond, wpa_supplicant & vendor HAL).
+     *
+     * @param interfaceCallback Associated callback for notifying status changes for the iface.
+     * @return Returns the name of the allocated interface, will be null on failure.
+     */
+    public String setupInterfaceForClientMode(@NonNull InterfaceCallback interfaceCallback) {
+        return null;
+    }
+
+    /**
+     * Setup an interface for Soft AP mode operations.
+     *
+     * This method configures an interface in AP mode in all the native daemons
+     * (wificond, wpa_supplicant & vendor HAL).
+     *
+     * @param interfaceCallback Associated callback for notifying status changes for the iface.
+     * @return Returns the name of the allocated interface, will be null on failure.
+     */
+    public String setupInterfaceForSoftApMode(@NonNull InterfaceCallback interfaceCallback) {
+        return null;
+    }
+
+    /**
+     * Teardown an interface in Client/AP mode.
+     *
+     * This method tears down the associated interface from all the native daemons
+     * (wificond, wpa_supplicant & vendor HAL).
+     *
+     * @param ifaceName Name of the interface.
+     */
+    public void teardownInterface(@NonNull String ifaceName) {
+    }
+
     /********************************************************
      * Wificond operations
      ********************************************************/
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index 0adef36..1521bb9 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -2281,6 +2281,7 @@
         } else {
             pw.println("mWifiConnectivityManager is not initialized");
         }
+        mWifiInjector.getWakeupController().dump(fd, pw, args);
     }
 
     public void handleUserSwitch(int userId) {
@@ -3249,16 +3250,18 @@
                 || stateChangeResult.wifiSsid.toString().isEmpty()) && isLinkDebouncing()) {
             return state;
         }
-        // Network id is only valid when we start connecting
+        // Network id and SSID are only valid when we start connecting
         if (SupplicantState.isConnecting(state)) {
             mWifiInfo.setNetworkId(lookupFrameworkNetworkId(stateChangeResult.networkId));
+            mWifiInfo.setBSSID(stateChangeResult.BSSID);
+            mWifiInfo.setSSID(stateChangeResult.wifiSsid);
         } else {
+            // Reset parameters according to WifiInfo.reset()
             mWifiInfo.setNetworkId(WifiConfiguration.INVALID_NETWORK_ID);
+            mWifiInfo.setBSSID(null);
+            mWifiInfo.setSSID(null);
         }
 
-        mWifiInfo.setBSSID(stateChangeResult.BSSID);
-        mWifiInfo.setSSID(stateChangeResult.wifiSsid);
-
         final WifiConfiguration config = getCurrentWifiConfiguration();
         if (config != null) {
             mWifiInfo.setEphemeral(config.ephemeral);
diff --git a/service/java/com/android/server/wifi/WifiStateMachinePrime.java b/service/java/com/android/server/wifi/WifiStateMachinePrime.java
index c49b645..de970ee 100644
--- a/service/java/com/android/server/wifi/WifiStateMachinePrime.java
+++ b/service/java/com/android/server/wifi/WifiStateMachinePrime.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.net.wifi.IApInterface;
-import android.net.wifi.IWificond;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.INetworkManagementService;
@@ -26,6 +25,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
@@ -47,12 +47,13 @@
 
     private final WifiInjector mWifiInjector;
     private final Looper mLooper;
+    private final WifiNative mWifiNative;
     private final INetworkManagementService mNMService;
 
-    private IWificond mWificond;
-
     private Queue<SoftApModeConfiguration> mApConfigQueue = new ConcurrentLinkedQueue<>();
 
+    private String mInterfaceName;
+
     /* The base for wifi message types */
     static final int BASE = Protocol.BASE_WIFI;
 
@@ -67,22 +68,17 @@
 
     WifiStateMachinePrime(WifiInjector wifiInjector,
                           Looper looper,
+                          WifiNative wifiNative,
                           INetworkManagementService nmService) {
         mWifiInjector = wifiInjector;
         mLooper = looper;
+        mWifiNative = wifiNative;
         mNMService = nmService;
 
-        // Clean up existing interfaces in wificond.
-        // This ensures that the framework and wificond are in a consistent state after a framework
-        // restart.
-        try {
-            mWificond = mWifiInjector.makeWificond();
-            if (mWificond != null) {
-                mWificond.tearDownInterfaces();
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "wificond died during framework startup");
-        }
+        mInterfaceName = mWifiNative.getInterfaceName();
+
+        // make sure we do not have leftover state in the event of a restart
+        mWifiNative.tearDown();
     }
 
     /**
@@ -211,23 +207,13 @@
             return HANDLED;
         }
 
-        private void tearDownInterfaces() {
-            if (mWificond != null) {
-                try {
-                    mWificond.tearDownInterfaces();
-                } catch (RemoteException e) {
-                    // There is very little we can do here
-                    Log.e(TAG, "Failed to tear down interfaces via wificond");
-                }
-                mWificond = null;
-            }
-            return;
+        private void cleanup() {
+            mWifiNative.tearDown();
         }
 
         class ClientModeState extends State {
             @Override
             public void enter() {
-                mWificond = mWifiInjector.makeWificond();
             }
 
             @Override
@@ -240,7 +226,7 @@
 
             @Override
             public void exit() {
-                tearDownInterfaces();
+                cleanup();
             }
         }
 
@@ -280,17 +266,27 @@
                 // Continue with setup since we are changing modes
                 mApInterface = null;
 
+                Pair<Integer, IApInterface> statusAndInterface =
+                        mWifiNative.setupForSoftApMode(mInterfaceName);
+                if (statusAndInterface.first == WifiNative.SETUP_SUCCESS) {
+                    mApInterface = statusAndInterface.second;
+                } else {
+                    // TODO: incorporate metrics - or this particular one will be done in WifiNative
+                    //incrementMetricsForSetupFailure(statusAndInterface.first);
+                }
+                if (mApInterface == null) {
+                    // TODO: update battery stats that a failure occured - best to do in
+                    // initialization Failed.  Keeping line copied from WSM for a reference
+                    //setWifiApState(WIFI_AP_STATE_FAILED,
+                    //        WifiManager.SAP_START_FAILURE_GENERAL, null, mMode);
+                    initializationFailed("Could not get IApInterface instance");
+                    return;
+                }
+
                 try {
-                    mWificond = mWifiInjector.makeWificond();
-                    mApInterface = mWificond.createApInterface(
-                            mWifiInjector.getWifiNative().getInterfaceName());
                     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");
+                    initializationFailed("Could not get IApInterface name");
                     return;
                 }
                 mModeStateMachine.transitionTo(mSoftAPModeActiveState);
@@ -325,7 +321,7 @@
 
             @Override
             public void exit() {
-                tearDownInterfaces();
+                cleanup();
             }
 
             protected IApInterface getInterface() {
diff --git a/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java b/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
index 421d9ac..62cc3ec 100644
--- a/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
+++ b/service/java/com/android/server/wifi/aware/WifiAwareServiceImpl.java
@@ -329,8 +329,8 @@
             enforceNetworkStackPermission();
         }
 
-        if (message != null
-                && message.length > mStateManager.getCharacteristics().getMaxServiceNameLength()) {
+        if (message != null && message.length
+                > mStateManager.getCharacteristics().getMaxServiceSpecificInfoLength()) {
             throw new IllegalArgumentException(
                     "Message length longer than supported by device characteristics");
         }
diff --git a/tests/wifitests/Android.mk b/tests/wifitests/Android.mk
index 11e508d..b5c5844 100644
--- a/tests/wifitests/Android.mk
+++ b/tests/wifitests/Android.mk
@@ -68,7 +68,9 @@
 	android.test.runner \
 	wifi-service \
 	services \
-	android.hidl.manager-V1.0-java
+	android.hidl.manager-V1.0-java \
+	android.test.base \
+	android.test.mock
 
 # These must be explicitly included because they are not normally accessible
 # from apps.
diff --git a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
index cda9cf5..0c35904 100644
--- a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -33,17 +33,24 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.*;
 
+import android.app.test.TestAlarmManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.net.InterfaceConfiguration;
+import android.net.Uri;
 import android.net.wifi.IApInterface;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.INetworkManagementService;
 import android.os.UserHandle;
 import android.os.test.TestLooper;
+import android.provider.Settings;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.internal.R;
+import com.android.internal.util.WakeupMessage;
 import com.android.server.net.BaseNetworkObserver;
 
 import org.junit.Before;
@@ -77,11 +84,16 @@
 
     private final WifiConfiguration mDefaultApConfig = createDefaultApConfig();
 
+    private ContentObserver mContentObserver;
+    private TestLooper mLooper;
+    private TestAlarmManager mAlarmManager;
+
     @Mock Context mContext;
-    TestLooper mLooper;
+    @Mock Resources mResources;
     @Mock WifiNative mWifiNative;
     @Mock SoftApManager.Listener mListener;
     @Mock InterfaceConfiguration mInterfaceConfiguration;
+    @Mock FrameworkFacade mFrameworkFacade;
     @Mock IApInterface mApInterface;
     @Mock INetworkManagementService mNmService;
     @Mock WifiApConfigStore mWifiApConfigStore;
@@ -104,6 +116,15 @@
         when(mWifiNative.startSoftAp(any(), any())).thenReturn(true);
         when(mWifiNative.stopSoftAp()).thenReturn(true);
         when(mWifiNative.registerWificondDeathHandler(any())).thenReturn(true);
+
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(1);
+        mAlarmManager = new TestAlarmManager();
+        when(mContext.getSystemService(Context.ALARM_SERVICE))
+                .thenReturn(mAlarmManager.getAlarmManager());
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getInteger(R.integer.config_wifi_framework_soft_ap_timeout_delay))
+                .thenReturn(600000);
     }
 
     private WifiConfiguration createDefaultApConfig() {
@@ -118,6 +139,7 @@
         }
         SoftApManager newSoftApManager = new SoftApManager(mContext,
                                                            mLooper.getLooper(),
+                                                           mFrameworkFacade,
                                                            mWifiNative,
                                                            TEST_COUNTRY_CODE,
                                                            mListener,
@@ -189,6 +211,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
         SoftApManager newSoftApManager = new SoftApManager(mContext,
                                                            mLooper.getLooper(),
+                                                           mFrameworkFacade,
                                                            mWifiNative,
                                                            TEST_COUNTRY_CODE,
                                                            mListener,
@@ -232,6 +255,7 @@
 
         SoftApManager newSoftApManager = new SoftApManager(mContext,
                                                            mLooper.getLooper(),
+                                                           mFrameworkFacade,
                                                            mWifiNative,
                                                            null,
                                                            mListener,
@@ -275,6 +299,7 @@
 
         SoftApManager newSoftApManager = new SoftApManager(mContext,
                                                            mLooper.getLooper(),
+                                                           mFrameworkFacade,
                                                            mWifiNative,
                                                            TEST_COUNTRY_CODE,
                                                            mListener,
@@ -309,8 +334,10 @@
         when(mWifiNative.startSoftAp(any(), any())).thenReturn(false);
         SoftApModeConfiguration softApModeConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, mDefaultApConfig);
+
         SoftApManager newSoftApManager = new SoftApManager(mContext,
                                                            mLooper.getLooper(),
+                                                           mFrameworkFacade,
                                                            mWifiNative,
                                                            TEST_COUNTRY_CODE,
                                                            mListener,
@@ -415,30 +442,11 @@
         mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(
                 TEST_NUM_CONNECTED_CLIENTS);
         mLooper.dispatchAll();
-        assertEquals(TEST_NUM_CONNECTED_CLIENTS, mSoftApManager.getNumAssociatedStations());
         verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(TEST_NUM_CONNECTED_CLIENTS,
                 apConfig.getTargetMode());
     }
 
     @Test
-    public void handlesNumAssociatedStationsWhenNotStarted() throws Exception {
-        SoftApModeConfiguration apConfig =
-                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
-        startSoftApAndVerifyEnabled(apConfig);
-        mSoftApManager.stop();
-        mLooper.dispatchAll();
-
-        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(
-                TEST_NUM_CONNECTED_CLIENTS);
-        mLooper.dispatchAll();
-        /* Verify numAssociatedStations is not updated when soft AP is not started */
-        assertEquals(0, mSoftApManager.getNumAssociatedStations());
-        verify(mWifiMetrics).addSoftApUpChangedEvent(false, apConfig.getTargetMode());
-        verify(mWifiMetrics, never()).addSoftApNumAssociatedStationsChangedEvent(
-                TEST_NUM_CONNECTED_CLIENTS, apConfig.getTargetMode());
-    }
-
-    @Test
     public void handlesInvalidNumAssociatedStations() throws Exception {
         SoftApModeConfiguration apConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
@@ -449,41 +457,155 @@
         /* Invalid values should be ignored */
         mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(-1);
         mLooper.dispatchAll();
-        assertEquals(TEST_NUM_CONNECTED_CLIENTS, mSoftApManager.getNumAssociatedStations());
         verify(mWifiMetrics, times(1)).addSoftApNumAssociatedStationsChangedEvent(
                 TEST_NUM_CONNECTED_CLIENTS, apConfig.getTargetMode());
     }
 
     @Test
-    public void resetsNumAssociatedStationsWhenStopped() throws Exception {
+    public void schedulesTimeoutTimerOnStart() throws Exception {
         SoftApModeConfiguration apConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
         startSoftApAndVerifyEnabled(apConfig);
 
-        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(
-                TEST_NUM_CONNECTED_CLIENTS);
-        mSoftApManager.stop();
-        mLooper.dispatchAll();
-        assertEquals(0, mSoftApManager.getNumAssociatedStations());
-        verify(mWifiMetrics).addSoftApUpChangedEvent(false, apConfig.getTargetMode());
+        // Verify timer is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
     }
 
     @Test
-    public void resetsNumAssociatedStationsOnFailure() throws Exception {
+    public void cancelsTimeoutTimerOnStop() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+        mSoftApManager.stop();
+        mLooper.dispatchAll();
+
+        // Verify timer is canceled
+        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+    }
+
+    @Test
+    public void cancelsTimeoutTimerOnNewClientsConnect() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(
+                TEST_NUM_CONNECTED_CLIENTS);
+        mLooper.dispatchAll();
+
+        // Verify timer is canceled
+        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+    }
+
+    @Test
+    public void schedulesTimeoutTimerWhenAllClientsDisconnect() throws Exception {
         SoftApModeConfiguration apConfig =
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
         startSoftApAndVerifyEnabled(apConfig);
 
         mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(
                 TEST_NUM_CONNECTED_CLIENTS);
-        /* Force soft AP to fail */
-        mDeathListenerCaptor.getValue().onDeath();
         mLooper.dispatchAll();
-        verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_FAILED,
-                WifiManager.SAP_START_FAILURE_GENERAL);
+        // Verify timer is canceled at this point
+        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
 
-        assertEquals(0, mSoftApManager.getNumAssociatedStations());
-        verify(mWifiMetrics).addSoftApUpChangedEvent(false, apConfig.getTargetMode());
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(0);
+        mLooper.dispatchAll();
+        // Verify timer is scheduled again
+        verify(mAlarmManager.getAlarmManager(), times(2)).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+    }
+
+    @Test
+    public void stopsSoftApOnTimeoutMessage() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        mAlarmManager.dispatch(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG);
+        mLooper.dispatchAll();
+
+        verify(mWifiNative).stopSoftAp();
+    }
+
+    @Test
+    public void cancelsTimeoutTimerOnTimeoutToggleChangeWhenNoClients() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(0);
+        mContentObserver.onChange(false);
+        mLooper.dispatchAll();
+
+        // Verify timer is canceled
+        verify(mAlarmManager.getAlarmManager()).cancel(any(WakeupMessage.class));
+    }
+
+    @Test
+    public void schedulesTimeoutTimerOnTimeoutToggleChangeWhenNoClients() throws Exception {
+        // start with timeout toggle disabled
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(0);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(1);
+        mContentObserver.onChange(false);
+        mLooper.dispatchAll();
+
+        // Verify timer is scheduled
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+    }
+
+    @Test
+    public void doesNotScheduleTimeoutTimerOnStartWhenTimeoutIsDisabled() throws Exception {
+        // start with timeout toggle disabled
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(0);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+
+        // Verify timer is not scheduled
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+    }
+
+    @Test
+    public void doesNotScheduleTimeoutTimerWhenAllClientsDisconnectButTimeoutIsDisabled()
+            throws Exception {
+        // start with timeout toggle disabled
+        when(mFrameworkFacade.getIntegerSetting(
+                mContext, Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1)).thenReturn(0);
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+        // add some clients
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(
+                TEST_NUM_CONNECTED_CLIENTS);
+        mLooper.dispatchAll();
+        // remove all clients
+        mSoftApListenerCaptor.getValue().onNumAssociatedStationsChanged(0);
+        mLooper.dispatchAll();
+        // Verify timer is not scheduled
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+    }
+
+    @Test
+    public void unregistersSettingsObserverOnStop() throws Exception {
+        SoftApModeConfiguration apConfig =
+                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null);
+        startSoftApAndVerifyEnabled(apConfig);
+        mSoftApManager.stop();
+        mLooper.dispatchAll();
+
+        verify(mFrameworkFacade).unregisterContentObserver(eq(mContext), eq(mContentObserver));
     }
 
     /** Starts soft AP and verifies that it is enabled successfully. */
@@ -506,6 +628,8 @@
             expectedConfig = new WifiConfiguration(config);
         }
 
+        ArgumentCaptor<ContentObserver> observerCaptor = ArgumentCaptor.forClass(
+                ContentObserver.class);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
 
         mSoftApManager.start();
@@ -530,8 +654,10 @@
         checkApStateChangedBroadcast(capturedIntents.get(1), WIFI_AP_STATE_ENABLED,
                 WIFI_AP_STATE_ENABLING, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
                 softApConfig.getTargetMode());
-        assertEquals(0, mSoftApManager.getNumAssociatedStations());
         verify(mWifiMetrics).addSoftApUpChangedEvent(true, softApConfig.mTargetMode);
+        verify(mFrameworkFacade).registerContentObserver(eq(mContext), any(Uri.class), eq(true),
+                observerCaptor.capture());
+        mContentObserver = observerCaptor.getValue();
     }
 
     /** Verifies that soft AP was not disabled. */
diff --git a/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
index 742c520..5e45570 100644
--- a/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WakeupControllerTest.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -29,12 +31,18 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+
 /**
  * Unit tests for {@link WakeupController}.
  */
 public class WakeupControllerTest {
 
     @Mock private Context mContext;
+    @Mock private WakeupLock mWakeupLock;
+    @Mock private WifiConfigManager mWifiConfigManager;
+    @Mock private WifiConfigStore mWifiConfigStore;
     @Mock private FrameworkFacade mFrameworkFacade;
 
     private TestLooper mLooper;
@@ -47,6 +55,11 @@
         mLooper = new TestLooper();
     }
 
+    private WakeupController newWakeupController() {
+        return new WakeupController(mContext, mLooper.getLooper(), mWakeupLock, mWifiConfigManager,
+                mWifiConfigStore, mFrameworkFacade);
+    }
+
     /**
      * Verify WakeupController is enabled when the settings toggle is true.
      */
@@ -54,8 +67,7 @@
     public void verifyEnabledWhenToggledOn() {
         when(mFrameworkFacade.getIntegerSetting(mContext,
                 Settings.Global.WIFI_WAKEUP_ENABLED, 0)).thenReturn(1);
-        mWakeupController = new WakeupController(mContext, mLooper.getLooper(),
-                mFrameworkFacade);
+        mWakeupController = newWakeupController();
 
         assertTrue(mWakeupController.isEnabled());
     }
@@ -67,9 +79,30 @@
     public void verifyDisabledWhenToggledOff() {
         when(mFrameworkFacade.getIntegerSetting(mContext,
                 Settings.Global.WIFI_WAKEUP_ENABLED, 0)).thenReturn(0);
-        mWakeupController = new WakeupController(mContext, mLooper.getLooper(),
-                mFrameworkFacade);
+        mWakeupController = newWakeupController();
 
         assertFalse(mWakeupController.isEnabled());
     }
+
+    /**
+     * Verify WakeupController registers its store data with the WifiConfigStore on construction.
+     */
+    @Test
+    public void registersWakeupConfigStoreData() {
+        mWakeupController = newWakeupController();
+        verify(mWifiConfigStore).registerStoreData(any(WakeupConfigStoreData.class));
+    }
+
+    /**
+     * Verify that dump calls also dump the state of the WakeupLock.
+     */
+    @Test
+    public void dumpIncludesWakeupLock() {
+        mWakeupController = newWakeupController();
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        PrintWriter writer = new PrintWriter(stream);
+        mWakeupController.dump(null, writer, null);
+
+        verify(mWakeupLock).dump(null, writer, null);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WakeupLockTest.java b/tests/wifitests/src/com/android/server/wifi/WakeupLockTest.java
index bad9e3a..7144ecf 100644
--- a/tests/wifitests/src/com/android/server/wifi/WakeupLockTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WakeupLockTest.java
@@ -18,9 +18,13 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -35,6 +39,8 @@
     private static final String SSID_1 = "ssid1";
     private static final String SSID_2 = "ssid2";
 
+    @Mock private WifiConfigManager mWifiConfigManager;
+
     private ScanResultMatchInfo mNetwork1;
     private ScanResultMatchInfo mNetwork2;
     private WakeupLock mWakeupLock;
@@ -44,6 +50,8 @@
      */
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
         mNetwork1 = new ScanResultMatchInfo();
         mNetwork1.networkSsid = SSID_1;
         mNetwork1.networkType = ScanResultMatchInfo.NETWORK_TYPE_OPEN;
@@ -52,7 +60,7 @@
         mNetwork2.networkSsid = SSID_2;
         mNetwork2.networkType = ScanResultMatchInfo.NETWORK_TYPE_EAP;
 
-        mWakeupLock = new WakeupLock();
+        mWakeupLock = new WakeupLock(mWifiConfigManager);
     }
 
     /**
@@ -69,6 +77,18 @@
     }
 
     /**
+     * Updates the lock enough times to evict any networks not passed in.
+     *
+     * <p>It calls update {@link WakeupLock#CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT} times with
+     * the given network list. It does not make any assertions about the state of the lock.
+     */
+    private void updateEnoughTimesToEvictWithoutAsserts(Collection<ScanResultMatchInfo> networks) {
+        for (int i = 0; i < WakeupLock.CONSECUTIVE_MISSED_SCANS_REQUIRED_TO_EVICT; i++) {
+            mWakeupLock.update(networks);
+        }
+    }
+
+    /**
      * Verify that the WakeupLock is not empty immediately after being initialized with networks.
      */
     @Test
@@ -161,4 +181,34 @@
         updateEnoughTimesToEvictWithAsserts(Collections.singletonList(mNetwork2));
         assertTrue(mWakeupLock.isEmpty());
     }
+
+    /**
+     * Verify that initializing the lock persists the SSID list to the config store.
+     */
+    @Test
+    public void initializeShouldSaveSsidsToStore() {
+        mWakeupLock.initialize(Collections.singletonList(mNetwork1));
+        verify(mWifiConfigManager).saveToStore(eq(false));
+    }
+
+    /**
+     * Verify that update saves to store if the lock changes.
+     */
+    @Test
+    public void updateShouldOnlySaveIfLockChanges() {
+        mWakeupLock.initialize(Collections.singletonList(mNetwork1));
+        updateEnoughTimesToEvictWithoutAsserts(Collections.emptyList());
+
+        // need exactly 2 invocations: 1 for initialize, 1 for successful update
+        verify(mWifiConfigManager, times(2)).saveToStore(eq(false));
+    }
+
+    /**
+     * Verify that update does not save to store if the lock does not change.
+     */
+    @Test
+    public void updateShouldNotSaveIfLockDoesNotChange() {
+        mWakeupLock.update(Collections.singletonList(mNetwork1));
+        verify(mWifiConfigManager, never()).saveToStore(anyBoolean());
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java
index 2e26769..f2d6cc6 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachinePrimeTest.java
@@ -20,13 +20,13 @@
 import static org.mockito.Mockito.*;
 
 import android.net.wifi.IApInterface;
-import android.net.wifi.IWificond;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.INetworkManagementService;
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
+import android.util.Pair;
 
 import org.junit.After;
 import org.junit.Before;
@@ -56,7 +56,6 @@
     @Mock WifiNative mWifiNative;
     @Mock WifiApConfigStore mWifiApConfigStore;
     TestLooper mLooper;
-    @Mock IWificond mWificond;
     @Mock IApInterface mApInterface;
     @Mock INetworkManagementService mNMService;
     @Mock SoftApManager mSoftApManager;
@@ -76,11 +75,15 @@
         when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
         when(mWifiNative.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         mWifiStateMachinePrime = createWifiStateMachinePrime();
+
+        // creating a new WSMP cleans up any existing interfaces, check and reset expectations
+        verifyCleanupCalled();
+        reset(mWifiNative);
     }
 
     private WifiStateMachinePrime createWifiStateMachinePrime() {
-        when(mWifiInjector.makeWificond()).thenReturn(null);
-        return new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(), mNMService);
+        return new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(),
+                mWifiNative, mNMService);
     }
 
     /**
@@ -103,8 +106,8 @@
      */
     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(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_SUCCESS, mApInterface));
         when(mApInterface.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         doAnswer(
                 new Answer<Object>() {
@@ -126,24 +129,30 @@
         mLooper.dispatchAll();
         Log.e("WifiStateMachinePrimeTest", "check fromState: " + fromState);
         if (!fromState.equals(WIFI_DISABLED_STATE_STRING)) {
-            verify(mWificond).tearDownInterfaces();
+            verifyCleanupCalled();
         }
         assertEquals(SOFT_AP_MODE_ACTIVE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
         verify(mSoftApManager).start();
     }
 
+    private void verifyCleanupCalled() {
+        // for now, this is a single call, but make a helper to avoid adding any additional cleanup
+        // checks
+        verify(mWifiNative).tearDown();
+    }
+
     /**
-     * Test that when a new instance of WifiStateMachinePrime is created, any existing interfaces in
-     * the retrieved Wificond instance are cleaned up.
+     * Test that when a new instance of WifiStateMachinePrime is created, any existing
+     * resources in WifiNative are cleaned up.
      * Expectations:  When the new WifiStateMachinePrime instance is created a call to
-     * Wificond.tearDownInterfaces() is made.
+     * WifiNative.tearDown() is made.
      */
     @Test
-    public void testWificondExistsOnStartup() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
+    public void testCleanupOnStart() throws Exception {
         WifiStateMachinePrime testWifiStateMachinePrime =
-                new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(), mNMService);
-        verify(mWificond).tearDownInterfaces();
+                new WifiStateMachinePrime(mWifiInjector, mLooper.getLooper(),
+                                          mWifiNative, mNMService);
+        verifyCleanupCalled();
     }
 
     /**
@@ -157,12 +166,10 @@
 
     /**
      * Test that WifiStateMachinePrime properly enters the SoftApModeActiveState from another state.
-     * Expectations: When going from one state to another, any interfaces that are still up are torn
-     * down.
+     * Expectations: When going from one state to another, cleanup will be called
      */
     @Test
     public void testEnterSoftApModeFromDifferentState() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
         mWifiStateMachinePrime.enterClientMode();
         mLooper.dispatchAll();
         assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
@@ -179,7 +186,7 @@
         mWifiStateMachinePrime.disableWifi();
         mLooper.dispatchAll();
         verify(mSoftApManager).stop();
-        verify(mWificond).tearDownInterfaces();
+        verifyCleanupCalled();
         assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
     }
 
@@ -188,8 +195,10 @@
      */
     @Test
     public void testDisableWifiFromSoftApModeState() throws Exception {
-        // Use a failure getting wificond to stay in the SoftAPModeState
-        when(mWifiInjector.makeWificond()).thenReturn(null);
+        // Use a failure getting the interface to stay in SoftApMode
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                            .thenReturn(new Pair(WifiNative.SETUP_FAILURE_HAL, null));
+
         mWifiStateMachinePrime.enterSoftAPMode(
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mLooper.dispatchAll();
@@ -197,7 +206,7 @@
 
         mWifiStateMachinePrime.disableWifi();
         mLooper.dispatchAll();
-        // mWificond will be null due to this test, no call to tearDownInterfaces here.
+        verifyCleanupCalled();
         assertEquals(WIFI_DISABLED_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
     }
 
@@ -213,26 +222,11 @@
         mWifiStateMachinePrime.enterClientMode();
         mLooper.dispatchAll();
         verify(mSoftApManager).stop();
-        verify(mWificond).tearDownInterfaces();
+        verifyCleanupCalled();
         assertEquals(CLIENT_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
     }
 
     /**
-     * Test that we do not attempt to enter SoftApModeActiveState when we cannot get a reference to
-     * wificond.
-     * Expectations: After a failed attempt to get wificond from WifiInjector, we should remain in
-     * the SoftApModeState.
-     */
-    @Test
-    public void testWificondNullWhenSwitchingToApMode() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(null);
-        mWifiStateMachinePrime.enterSoftAPMode(
-                new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
-        mLooper.dispatchAll();
-        assertEquals(SOFT_AP_MODE_STATE_STRING, mWifiStateMachinePrime.getCurrentMode());
-    }
-
-    /**
      * Test that we do not attempt to enter SoftApModeActiveState when we cannot get an ApInterface
      * from wificond.
      * Expectations: After a failed attempt to get an ApInterface from WifiInjector, we should
@@ -240,8 +234,8 @@
      */
     @Test
     public void testAPInterfaceFailedWhenSwitchingToApMode() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
-        when(mWificond.createApInterface(WIFI_IFACE_NAME)).thenReturn(null);
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_FAILURE_HAL, null));
         mWifiStateMachinePrime.enterSoftAPMode(
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mLooper.dispatchAll();
@@ -255,8 +249,8 @@
      */
     @Test
     public void testEnterSoftApModeActiveWhenAlreadyInSoftApMode() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
-        when(mWificond.createApInterface(WIFI_IFACE_NAME)).thenReturn(null);
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_SUCCESS, null));
         mWifiStateMachinePrime.enterSoftAPMode(
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mLooper.dispatchAll();
@@ -330,8 +324,8 @@
      */
     @Test
     public void testNullApModeConfigFails() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
-        when(mWificond.createApInterface(WIFI_IFACE_NAME)).thenReturn(null);
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_SUCCESS, null));
         mWifiStateMachinePrime.enterSoftAPMode(
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null));
         mLooper.dispatchAll();
@@ -352,7 +346,8 @@
      */
     @Test
     public void testValidConfigIsSavedOnFailureToStart() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(null);
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_SUCCESS, null));
         when(mWifiInjector.getWifiApConfigStore()).thenReturn(mWifiApConfigStore);
         WifiConfiguration config = new WifiConfiguration();
         config.SSID = "ThisIsAConfig";
@@ -365,14 +360,14 @@
     }
 
     /**
-     * Thest that two calls to switch to SoftAPMode in succession ends up with the correct config.
+     * Test that two calls to switch to SoftAPMode in succession ends up with the correct config.
      *
      * Expectation: we should end up in SoftAPMode state configured with the second config.
      */
     @Test
     public void testStartSoftApModeTwiceWithTwoConfigs() throws Exception {
-        when(mWifiInjector.makeWificond()).thenReturn(mWificond);
-        when(mWificond.createApInterface(WIFI_IFACE_NAME)).thenReturn(mApInterface);
+        when(mWifiNative.setupForSoftApMode(WIFI_IFACE_NAME))
+                .thenReturn(new Pair(WifiNative.SETUP_SUCCESS, mApInterface));
         when(mApInterface.getInterfaceName()).thenReturn(WIFI_IFACE_NAME);
         when(mWifiInjector.getWifiApConfigStore()).thenReturn(mWifiApConfigStore);
         WifiConfiguration config1 = new WifiConfiguration();
@@ -406,12 +401,12 @@
 
     /**
      * Test that we safely disable wifi if it is already disabled.
-     * Expectations: We should not interact with wificond since we should have already cleaned up
+     * Expectations: We should not interact with WifiNative since we should have already cleaned up
      * everything.
      */
     @Test
     public void disableWifiWhenAlreadyOff() throws Exception {
-        verifyNoMoreInteractions(mWificond);
+        verifyNoMoreInteractions(mWifiNative);
         mWifiStateMachinePrime.disableWifi();
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
index 7a7fa92..e6972ea 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
@@ -376,6 +376,7 @@
     @Mock IProvisioningCallback mProvisioningCallback;
     @Mock HandlerThread mWifiServiceHandlerThread;
     @Mock WifiPermissionsWrapper mWifiPermissionsWrapper;
+    @Mock WakeupController mWakeupController;
 
     public WifiStateMachineTest() throws Exception {
     }
@@ -422,6 +423,7 @@
         when(mWifiServiceHandlerThread.getLooper()).thenReturn(mLooper.getLooper());
         when(mWifiInjector.getWifiServiceHandlerThread()).thenReturn(mWifiServiceHandlerThread);
         when(mWifiInjector.getWifiPermissionsWrapper()).thenReturn(mWifiPermissionsWrapper);
+        when(mWifiInjector.getWakeupController()).thenReturn(mWakeupController);
 
         when(mWifiNative.setupForClientMode(WIFI_IFACE_NAME))
                 .thenReturn(Pair.create(WifiNative.SETUP_SUCCESS, mClientInterface));
@@ -2199,6 +2201,16 @@
         assertEquals(sBSSID1, wifiInfo.getBSSID());
         assertEquals(sFreq1, wifiInfo.getFrequency());
         assertEquals(SupplicantState.COMPLETED, wifiInfo.getSupplicantState());
+
+        mWsm.sendMessage(WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT, 0, 0,
+                new StateChangeResult(0, sWifiSsid, sBSSID1, SupplicantState.DISCONNECTED));
+        mLooper.dispatchAll();
+
+        wifiInfo = mWsm.getWifiInfo();
+        assertEquals(null, wifiInfo.getBSSID());
+        assertEquals(WifiSsid.NONE, wifiInfo.getSSID());
+        assertEquals(WifiConfiguration.INVALID_NETWORK_ID, wifiInfo.getNetworkId());
+        assertEquals(SupplicantState.DISCONNECTED, wifiInfo.getSupplicantState());
     }
 
     /**
@@ -2819,4 +2831,15 @@
         currentConfig.networkId = lastSelectedNetworkId - 1;
         assertFalse(mWsm.shouldEvaluateWhetherToSendExplicitlySelected(currentConfig));
     }
+
+    /**
+     * Verify that WSM dump includes WakeupController.
+     */
+    @Test
+    public void testDumpShouldDumpWakeupController() {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        PrintWriter writer = new PrintWriter(stream);
+        mWsm.dump(null, writer, null);
+        verify(mWakeupController).dump(null, writer, null);
+    }
 }