resolve merge conflicts of b843fca to nyc-dev
am: 2709be1ecb

Change-Id: I41ad20f61dda33ba0b3a938330f12cd273f35ae3
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index c2579e0..cb03f7a 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -49,8 +49,9 @@
     }
 
     public BaseWifiLogger makeRealLogger(
-            WifiStateMachine stateMachine, WifiNative wifiNative, BuildProperties buildProperties) {
-        return new WifiLogger(stateMachine, wifiNative, buildProperties);
+            Context context, WifiStateMachine stateMachine, WifiNative wifiNative,
+            BuildProperties buildProperties) {
+        return new WifiLogger(context, stateMachine, wifiNative, buildProperties);
     }
 
     public boolean setIntegerSetting(Context context, String name, int def) {
@@ -144,7 +145,7 @@
             String countryCode, ArrayList<Integer> allowed2GChannels,
             SoftApManager.Listener listener) {
         return new SoftApManager(
-                context, looper, wifiNative, nmService, cm, countryCode,
+                looper, wifiNative, nmService, countryCode,
                 allowed2GChannels, listener);
     }
 
diff --git a/service/java/com/android/server/wifi/ScanDetail.java b/service/java/com/android/server/wifi/ScanDetail.java
index 1a5a923..dc87a5b 100644
--- a/service/java/com/android/server/wifi/ScanDetail.java
+++ b/service/java/com/android/server/wifi/ScanDetail.java
@@ -59,6 +59,9 @@
         if (networkDetail.is80211McResponderSupport()) {
             mScanResult.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
         }
+        if (networkDetail.isInterworking()) {
+            mScanResult.setFlag(ScanResult.FLAG_PASSPOINT_NETWORK);
+        }
         mMatches = null;
     }
 
diff --git a/service/java/com/android/server/wifi/ScanDetailCache.java b/service/java/com/android/server/wifi/ScanDetailCache.java
index e3f3192..cb44e2a 100644
--- a/service/java/com/android/server/wifi/ScanDetailCache.java
+++ b/service/java/com/android/server/wifi/ScanDetailCache.java
@@ -85,11 +85,6 @@
         return size() == 0;
     }
 
-    ScanDetail getFirst() {
-        Iterator<ScanDetail> it = mMap.values().iterator();
-        return it.hasNext() ? it.next() : null;
-    }
-
     Collection<String> keySet() {
         return mMap.keySet();
     }
diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java
index f271b4c..2dfb754 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -20,14 +20,8 @@
 import static com.android.server.wifi.util.ApConfigUtil.ERROR_NO_CHANNEL;
 import static com.android.server.wifi.util.ApConfigUtil.SUCCESS;
 
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.net.ConnectivityManager;
-import android.net.InterfaceConfiguration;
-import android.net.LinkAddress;
-import android.net.NetworkUtils;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.INetworkManagementService;
@@ -49,31 +43,18 @@
 public class SoftApManager {
     private static final String TAG = "SoftApManager";
 
-    private final Context mContext;
     private final INetworkManagementService mNmService;
     private final WifiNative mWifiNative;
-    private final ConnectivityManager mConnectivityManager;
     private final ArrayList<Integer> mAllowed2GChannels;
 
     private final String mCountryCode;
 
     private final String mInterfaceName;
-    private String mTetherInterfaceName;
 
     private final SoftApStateMachine mStateMachine;
 
     private final Listener mListener;
 
-    private static class TetherStateChange {
-        public ArrayList<String> available;
-        public ArrayList<String> active;
-
-        TetherStateChange(ArrayList<String> av, ArrayList<String> ac) {
-            available = av;
-            active = ac;
-        }
-    }
-
     /**
      * Listener for soft AP state changes.
      */
@@ -86,40 +67,21 @@
         void onStateChanged(int state, int failureReason);
     }
 
-    public SoftApManager(Context context,
-                         Looper looper,
+    public SoftApManager(Looper looper,
                          WifiNative wifiNative,
                          INetworkManagementService nmService,
-                         ConnectivityManager connectivityManager,
                          String countryCode,
                          ArrayList<Integer> allowed2GChannels,
                          Listener listener) {
         mStateMachine = new SoftApStateMachine(looper);
 
-        mContext = context;
         mNmService = nmService;
         mWifiNative = wifiNative;
-        mConnectivityManager = connectivityManager;
         mCountryCode = countryCode;
         mAllowed2GChannels = allowed2GChannels;
         mListener = listener;
 
         mInterfaceName = mWifiNative.getInterfaceName();
-
-        /* Register receiver for tether state changes. */
-        mContext.registerReceiver(
-                new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        ArrayList<String> available = intent.getStringArrayListExtra(
-                                ConnectivityManager.EXTRA_AVAILABLE_TETHER);
-                        ArrayList<String> active = intent.getStringArrayListExtra(
-                                ConnectivityManager.EXTRA_ACTIVE_TETHER);
-                        mStateMachine.sendMessage(
-                                SoftApStateMachine.CMD_TETHER_STATE_CHANGE,
-                                new TetherStateChange(available, active));
-                    }
-                }, new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
     }
 
     /**
@@ -208,104 +170,19 @@
         Log.d(TAG, "Soft AP is stopped");
     }
 
-    private boolean startTethering(ArrayList<String> available) {
-        String[] wifiRegexs = mConnectivityManager.getTetherableWifiRegexs();
-
-        for (String intf : available) {
-            for (String regex : wifiRegexs) {
-                if (intf.matches(regex)) {
-                    try {
-                        InterfaceConfiguration ifcg =
-                                mNmService.getInterfaceConfig(intf);
-                        if (ifcg != null) {
-                            /* IP/netmask: 192.168.43.1/255.255.255.0 */
-                            ifcg.setLinkAddress(new LinkAddress(
-                                    NetworkUtils.numericToInetAddress("192.168.43.1"), 24));
-                            ifcg.setInterfaceUp();
-
-                            mNmService.setInterfaceConfig(intf, ifcg);
-                        }
-                    } catch (Exception e) {
-                        Log.e(TAG, "Error configuring interface " + intf + ", :" + e);
-                        return false;
-                    }
-
-                    if (mConnectivityManager.tether(intf)
-                            != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
-                        Log.e(TAG, "Error tethering on " + intf);
-                        return false;
-                    }
-                    mTetherInterfaceName = intf;
-                    return true;
-                }
-            }
-        }
-        /* We found no interfaces to tether. */
-        return false;
-    }
-
-    private void stopTethering() {
-        try {
-            /* Clear the interface address. */
-            InterfaceConfiguration ifcg =
-                    mNmService.getInterfaceConfig(mTetherInterfaceName);
-            if (ifcg != null) {
-                ifcg.setLinkAddress(
-                        new LinkAddress(
-                                NetworkUtils.numericToInetAddress("0.0.0.0"), 0));
-                mNmService.setInterfaceConfig(mTetherInterfaceName, ifcg);
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Error resetting interface " + mTetherInterfaceName + ", :" + e);
-        }
-
-        if (mConnectivityManager.untether(mTetherInterfaceName)
-                != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
-            Log.e(TAG, "Untether initiate failed!");
-        }
-    }
-
-    private boolean isWifiTethered(ArrayList<String> active) {
-        String[] wifiRegexs = mConnectivityManager.getTetherableWifiRegexs();
-        for (String intf : active) {
-            for (String regex : wifiRegexs) {
-                if (intf.matches(regex)) {
-                    return true;
-                }
-            }
-        }
-        /* No tethered interface. */
-        return false;
-    }
-
     private class SoftApStateMachine extends StateMachine {
         /* Commands for the state machine. */
         public static final int CMD_START = 0;
         public static final int CMD_STOP = 1;
-        public static final int CMD_TETHER_STATE_CHANGE = 2;
-        public static final int CMD_TETHER_NOTIFICATION_TIMEOUT = 3;
-
-        private static final int TETHER_NOTIFICATION_TIME_OUT_MSECS = 5000;
-
-        /* Sequence number used to track tether notification timeout. */
-        private int mTetherToken = 0;
 
         private final State mIdleState = new IdleState();
         private final State mStartedState = new StartedState();
-        private final State mTetheringState = new TetheringState();
-        private final State mTetheredState = new TetheredState();
-        private final State mUntetheringState = new UntetheringState();
 
         SoftApStateMachine(Looper looper) {
             super(TAG, looper);
 
-            // CHECKSTYLE:OFF IndentationCheck
             addState(mIdleState);
-                addState(mStartedState, mIdleState);
-                    addState(mTetheringState, mStartedState);
-                    addState(mTetheredState, mStartedState);
-                    addState(mUntetheringState, mStartedState);
-            // CHECKSTYLE:ON IndentationCheck
+            addState(mStartedState, mIdleState);
 
             setInitialState(mIdleState);
             start();
@@ -350,12 +227,6 @@
                         updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 0);
                         transitionTo(mIdleState);
                         break;
-                    case CMD_TETHER_STATE_CHANGE:
-                        TetherStateChange stateChange = (TetherStateChange) message.obj;
-                        if (startTethering(stateChange.available)) {
-                            transitionTo(mTetheringState);
-                        }
-                        break;
                     default:
                         return NOT_HANDLED;
                 }
@@ -363,112 +234,5 @@
             }
         }
 
-        /**
-         * This is a transient state. We will transition out of this state when
-         * we receive a notification that WiFi is tethered (TetheredState) or
-         * we timed out waiting for that notification (StartedState).
-         */
-        private class TetheringState extends State {
-            @Override
-            public void enter() {
-                /* Send a delayed message to terminate if tethering fails to notify. */
-                sendMessageDelayed(
-                        obtainMessage(CMD_TETHER_NOTIFICATION_TIMEOUT, ++mTetherToken),
-                        TETHER_NOTIFICATION_TIME_OUT_MSECS);
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                switch (message.what) {
-                    case CMD_TETHER_STATE_CHANGE:
-                        TetherStateChange stateChange = (TetherStateChange) message.obj;
-                        if (isWifiTethered(stateChange.active)) {
-                            transitionTo(mTetheredState);
-                        }
-                        break;
-                    case CMD_TETHER_NOTIFICATION_TIMEOUT:
-                        if (message.arg1 == mTetherToken) {
-                            Log.e(TAG, "Failed to get tether update, "
-                                    + "shutdown soft access point");
-                            transitionTo(mStartedState);
-                            /* Needs to be first thing handled. */
-                            sendMessageAtFrontOfQueue(CMD_STOP);
-                        }
-                        break;
-                    default:
-                        return NOT_HANDLED;
-                }
-                return HANDLED;
-            }
-        }
-
-        private class TetheredState extends State {
-            @Override
-            public boolean processMessage(Message message) {
-                switch (message.what) {
-                    case CMD_TETHER_STATE_CHANGE:
-                        TetherStateChange stateChange = (TetherStateChange) message.obj;
-                        if (!isWifiTethered(stateChange.active)) {
-                            Log.e(TAG, "Tethering reports wifi as untethered!, "
-                                    + "shut down soft Ap");
-                            sendMessage(CMD_STOP);
-                        }
-                        break;
-                    case CMD_STOP:
-                        Log.d(TAG, "Untethering before stopping AP");
-                        stopTethering();
-                        transitionTo(mUntetheringState);
-                        break;
-
-                    default:
-                        return NOT_HANDLED;
-                }
-                return HANDLED;
-            }
-        }
-
-        /**
-         * This is a transient state, will transition out of this state to StartedState
-         * when we receive a notification that WiFi is untethered or we timed out waiting
-         * for that notification.
-         */
-        private class UntetheringState extends State {
-            @Override
-            public void enter() {
-                /* Send a delayed message to terminate if tethering fails to notify. */
-                sendMessageDelayed(
-                        obtainMessage(CMD_TETHER_NOTIFICATION_TIMEOUT, ++mTetherToken),
-                        TETHER_NOTIFICATION_TIME_OUT_MSECS);
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                switch (message.what) {
-                    case CMD_TETHER_STATE_CHANGE:
-                        TetherStateChange stateChange = (TetherStateChange) message.obj;
-                        /* Transition back to StartedState when WiFi is untethered. */
-                        if (!isWifiTethered(stateChange.active)) {
-                            transitionTo(mStartedState);
-                            /* Needs to be first thing handled */
-                            sendMessageAtFrontOfQueue(CMD_STOP);
-                        }
-                        break;
-                    case CMD_TETHER_NOTIFICATION_TIMEOUT:
-                        if (message.arg1 == mTetherToken) {
-                            Log.e(TAG, "Failed to get tether update, "
-                                    + "force stop access point");
-                            transitionTo(mStartedState);
-                            /* Needs to be first thing handled. */
-                            sendMessageAtFrontOfQueue(CMD_STOP);
-                        }
-                        break;
-                    default:
-                        /* Defer handling of this message until untethering is completed. */
-                        deferMessage(message);
-                        break;
-                }
-                return HANDLED;
-            }
-        }
     }
 }
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index 4c930a6..c1a334a 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -398,7 +398,7 @@
         mIpconfigStore = new IpConfigStore(mWriter);
         mWifiNetworkHistory = new WifiNetworkHistory(context, mLocalLog, mWriter);
         mWifiConfigStore =
-                new WifiConfigStore(wifiNative, mKeyStore, mLocalLog, mShowNetworks, true);
+                new WifiConfigStore(context, wifiNative, mKeyStore, mLocalLog, mShowNetworks, true);
     }
 
     public void trimANQPCache(boolean all) {
@@ -713,19 +713,13 @@
         }
 
         if (config.isPasspoint()) {
-            /* need to slap on the SSID of selected bssid to work */
-            if (getScanDetailCache(config).size() != 0) {
-                ScanDetail result = getScanDetailCache(config).getFirst();
-                if (result == null) {
-                    loge("Could not find scan result for " + config.BSSID);
-                } else {
-                    logd("Setting SSID for " + config.networkId + " to" + result.getSSID());
-                    setSSIDNative(config, result.getSSID());
-                }
-
-            } else {
-                loge("Could not find bssid for " + config);
-            }
+            // Set the SSID for the underlying WPA supplicant network entry corresponding to this
+            // Passpoint profile to the SSID of the BSS selected by QNS. |config.SSID| is set by
+            // selectQualifiedNetwork.selectQualifiedNetwork(), when the qualified network selected
+            // is a Passpoint network.
+            logd("Setting SSID for WPA supplicant network " + config.networkId + " to "
+                    + config.SSID);
+            setSSIDNative(config, config.SSID);
         }
 
         mWifiConfigStore.enableHS20(config.isPasspoint());
@@ -2981,11 +2975,7 @@
                 pw.println(s);
             }
         }
-        if (mLocalLog != null) {
-            pw.println("WifiConfigManager - Log Begin ----");
-            mLocalLog.dump(fd, pw, args);
-            pw.println("WifiConfigManager - Log End ----");
-        }
+
         if (mMOManager.isConfigured()) {
             pw.println("Begin dump of ANQP Cache");
             mAnqpCache.dump(pw);
@@ -3119,15 +3109,6 @@
     }
 
     /**
-     * Checks if the network is a sim config.
-     * @param config Config corresponding to the network.
-     * @return true if it is a sim config, false otherwise.
-     */
-    public boolean isSimConfig(WifiConfiguration config) {
-        return mWifiConfigStore.isSimConfig(config);
-    }
-
-    /**
      * Resets all sim networks from the network list.
      */
     public void resetSimNetworks() {
diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiConfigStore.java
index 25ab449..b693e23 100644
--- a/service/java/com/android/server/wifi/WifiConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiConfigStore.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.content.Context;
 import android.net.IpConfiguration.IpAssignment;
 import android.net.IpConfiguration.ProxySettings;
 import android.net.wifi.WifiConfiguration;
@@ -36,6 +37,7 @@
 import android.util.SparseArray;
 
 import com.android.server.wifi.hotspot2.Utils;
+import com.android.server.wifi.util.TelephonyUtil;
 
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -92,6 +94,7 @@
 
     private final LocalLog mLocalLog;
     private final WpaConfigFileObserver mFileObserver;
+    private final Context mContext;
     private final WifiNative mWifiNative;
     private final KeyStore mKeyStore;
     private final boolean mShowNetworks;
@@ -99,8 +102,9 @@
 
     private final BackupManagerProxy mBackupManagerProxy;
 
-    WifiConfigStore(WifiNative wifiNative, KeyStore keyStore, LocalLog localLog,
+    WifiConfigStore(Context context, WifiNative wifiNative, KeyStore keyStore, LocalLog localLog,
             boolean showNetworks, boolean verboseDebug) {
+        mContext = context;
         mWifiNative = wifiNative;
         mKeyStore = keyStore;
         mShowNetworks = showNetworks;
@@ -1079,27 +1083,6 @@
     }
 
     /**
-     * Checks if the network is a sim config.
-     *
-     * @param config Config corresponding to the network.
-     * @return true if it is a sim config, false otherwise.
-     */
-    public boolean isSimConfig(WifiConfiguration config) {
-        if (config == null) {
-            return false;
-        }
-
-        if (config.enterpriseConfig == null) {
-            return false;
-        }
-
-        int method = config.enterpriseConfig.getEapMethod();
-        return (method == WifiEnterpriseConfig.Eap.SIM
-                || method == WifiEnterpriseConfig.Eap.AKA
-                || method == WifiEnterpriseConfig.Eap.AKA_PRIME);
-    }
-
-    /**
      * Resets all sim networks from the provided network list.
      *
      * @param configs List of all the networks.
@@ -1107,10 +1090,26 @@
     public void resetSimNetworks(Collection<WifiConfiguration> configs) {
         if (VDBG) localLog("resetSimNetworks");
         for (WifiConfiguration config : configs) {
-            if (isSimConfig(config)) {
-                /* This configuration may have cached Pseudonym IDs; lets remove them */
-                mWifiNative.setNetworkVariable(config.networkId, "identity", "NULL");
-                mWifiNative.setNetworkVariable(config.networkId, "anonymous_identity", "NULL");
+            if (TelephonyUtil.isSimConfig(config)) {
+                String currentIdentity = TelephonyUtil.getSimIdentity(mContext,
+                        config.enterpriseConfig.getEapMethod());
+                String supplicantIdentity =
+                        mWifiNative.getNetworkVariable(config.networkId, "identity");
+                if(supplicantIdentity != null) {
+                    supplicantIdentity = removeDoubleQuotes(supplicantIdentity);
+                }
+                if (currentIdentity == null || !currentIdentity.equals(supplicantIdentity)) {
+                    // Identity differs so update the identity
+                    mWifiNative.setNetworkVariable(config.networkId,
+                            WifiEnterpriseConfig.IDENTITY_KEY, WifiEnterpriseConfig.EMPTY_VALUE);
+                    // This configuration may have cached Pseudonym IDs; lets remove them
+                    mWifiNative.setNetworkVariable(config.networkId,
+                            WifiEnterpriseConfig.ANON_IDENTITY_KEY,
+                            WifiEnterpriseConfig.EMPTY_VALUE);
+                }
+                // Update the loaded config
+                config.enterpriseConfig.setIdentity(currentIdentity);
+                config.enterpriseConfig.setAnonymousIdentity("");
             }
         }
     }
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index e6d285e..7f3d5d7 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -90,7 +90,8 @@
     private static final int LOW_RSSI_NETWORK_RETRY_START_DELAY_MS = 20 * 1000; // 20 seconds
     private static final int LOW_RSSI_NETWORK_RETRY_MAX_DELAY_MS = 80 * 1000; // 80 seconds
     // Maximum number of retries when starting a scan failed
-    private static final int MAX_SCAN_RESTART_ALLOWED = 5;
+    @VisibleForTesting
+    public static final int MAX_SCAN_RESTART_ALLOWED = 5;
     // Number of milli-seconds to delay before retry starting
     // a previously failed scan
     private static final int RESTART_SCAN_DELAY_MS = 2 * 1000; // 2 seconds
@@ -132,7 +133,7 @@
     private final Handler mEventHandler;
     private final Clock mClock;
     private final LocalLog mLocalLog =
-            new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 256 : 1024);
+            new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 128 : 256);
     private final LinkedList<Long> mConnectionAttemptTimeStamps;
 
     private boolean mDbg = false;
@@ -147,6 +148,8 @@
     private String mLastConnectionAttemptBssid = null;
     private int mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS;
     private long mLastPeriodicSingleScanTimeStamp = RESET_TIME_STAMP;
+    private boolean mPnoScanStarted = false;
+    private boolean mPeriodicScanTimerSet = false;
 
     // PNO settings
     private int mMin5GHzRssi;
@@ -175,17 +178,15 @@
     // A single scan will be rescheduled up to MAX_SCAN_RESTART_ALLOWED times
     // if the start scan command failed. An timer is used here to make it a deferred retry.
     private class RestartSingleScanListener implements AlarmManager.OnAlarmListener {
-        private final boolean mIsWatchdogTriggered;
         private final boolean mIsFullBandScan;
 
-        RestartSingleScanListener(boolean isWatchdogTriggered, boolean isFullBandScan) {
-            mIsWatchdogTriggered = isWatchdogTriggered;
+        RestartSingleScanListener(boolean isFullBandScan) {
             mIsFullBandScan = isFullBandScan;
         }
 
         @Override
         public void onAlarm() {
-            startSingleScan(mIsWatchdogTriggered, mIsFullBandScan);
+            startSingleScan(mIsFullBandScan);
         }
     }
 
@@ -246,9 +247,6 @@
         @Override
         public void onSuccess() {
             localLog("PeriodicScanListener onSuccess");
-
-            // reset the count
-            mScanRestartCount = 0;
         }
 
         @Override
@@ -277,6 +275,7 @@
         public void onResults(WifiScanner.ScanData[] results) {
             handleScanResults(mScanDetails, "PeriodicScanListener");
             clearScanDetails();
+            mScanRestartCount = 0;
         }
 
         @Override
@@ -293,18 +292,13 @@
 
     private final PeriodicScanListener mPeriodicScanListener = new PeriodicScanListener();
 
-    // Single scan results listener. A single scan is initiated when
-    // Disconnected/ConnectedPNO scan found a valid network and woke up
-    // the system, or by the watchdog timer.
-    private class SingleScanListener implements WifiScanner.ScanListener {
+    // All single scan results listener.
+    //
+    // Note: This is the listener for all the available single scan results,
+    //       including the ones initiated by WifiConnectivityManager and
+    //       other modules.
+    private class AllSingleScanListener implements WifiScanner.ScanListener {
         private List<ScanDetail> mScanDetails = new ArrayList<ScanDetail>();
-        private final boolean mIsWatchdogTriggered;
-        private final boolean mIsFullBandScan;
-
-        SingleScanListener(boolean isWatchdogTriggered, boolean isFullBandScan) {
-            mIsWatchdogTriggered = isWatchdogTriggered;
-            mIsFullBandScan = isFullBandScan;
-        }
 
         public void clearScanDetails() {
             mScanDetails.clear();
@@ -312,10 +306,78 @@
 
         @Override
         public void onSuccess() {
-            localLog("SingleScanListener onSuccess");
+            localLog("registerScanListener onSuccess");
+        }
 
-            // reset the count
-            mSingleScanRestartCount = 0;
+        @Override
+        public void onFailure(int reason, String description) {
+            Log.e(TAG, "registerScanListener onFailure:"
+                          + " reason: " + reason
+                          + " description: " + description);
+        }
+
+        @Override
+        public void onPeriodChanged(int periodInMs) {
+        }
+
+        @Override
+        public void onResults(WifiScanner.ScanData[] results) {
+            if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
+                clearScanDetails();
+                return;
+            }
+
+            boolean wasConnectAttempted = handleScanResults(mScanDetails, "AllSingleScanListener");
+            clearScanDetails();
+
+            // Update metrics to see if a single scan detected a valid network
+            // while PNO scan didn't.
+            // Note: We don't update the background scan metrics any more as it is
+            //       not in use.
+            if (mPnoScanStarted) {
+                if (wasConnectAttempted) {
+                    mWifiMetrics.incrementNumConnectivityWatchdogPnoBad();
+                } else {
+                    mWifiMetrics.incrementNumConnectivityWatchdogPnoGood();
+                }
+            }
+        }
+
+        @Override
+        public void onFullResult(ScanResult fullScanResult) {
+            if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
+                return;
+            }
+
+            if (mDbg) {
+                localLog("AllSingleScanListener onFullResult: "
+                            + fullScanResult.SSID + " capabilities "
+                            + fullScanResult.capabilities);
+            }
+
+            mScanDetails.add(ScanDetailUtil.toScanDetail(fullScanResult));
+        }
+    }
+
+    private final AllSingleScanListener mAllSingleScanListener = new AllSingleScanListener();
+
+    // Single scan results listener. A single scan is initiated when
+    // Disconnected/ConnectedPNO scan found a valid network and woke up
+    // the system, or by the watchdog timer, or to form the timer based
+    // periodic scan.
+    //
+    // Note: This is the listener for the single scans initiated by the
+    //        WifiConnectivityManager.
+    private class SingleScanListener implements WifiScanner.ScanListener {
+        private final boolean mIsFullBandScan;
+
+        SingleScanListener(boolean isFullBandScan) {
+            mIsFullBandScan = isFullBandScan;
+        }
+
+        @Override
+        public void onSuccess() {
+            localLog("SingleScanListener onSuccess");
         }
 
         @Override
@@ -326,7 +388,7 @@
 
             // reschedule the scan
             if (mSingleScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
-                scheduleDelayedSingleScan(mIsWatchdogTriggered, mIsFullBandScan);
+                scheduleDelayedSingleScan(mIsFullBandScan);
             } else {
                 mSingleScanRestartCount = 0;
                 Log.e(TAG, "Failed to successfully start single scan for "
@@ -342,35 +404,10 @@
 
         @Override
         public void onResults(WifiScanner.ScanData[] results) {
-            boolean wasConnectAttempted = handleScanResults(mScanDetails, "SingleScanListener");
-            clearScanDetails();
-            // update metrics if this was a watchdog triggered single scan
-            if (mIsWatchdogTriggered) {
-                if (wasConnectAttempted) {
-                    if (mScreenOn) {
-                        mWifiMetrics.incrementNumConnectivityWatchdogBackgroundBad();
-                    } else {
-                        mWifiMetrics.incrementNumConnectivityWatchdogPnoBad();
-                    }
-                } else {
-                    if (mScreenOn) {
-                        mWifiMetrics.incrementNumConnectivityWatchdogBackgroundGood();
-                    } else {
-                        mWifiMetrics.incrementNumConnectivityWatchdogPnoGood();
-                    }
-                }
-            }
         }
 
         @Override
         public void onFullResult(ScanResult fullScanResult) {
-            if (mDbg) {
-                localLog("SingleScanListener onFullResult: "
-                            + fullScanResult.SSID + " capabilities "
-                            + fullScanResult.capabilities);
-            }
-
-            mScanDetails.add(ScanDetailUtil.toScanDetail(fullScanResult));
         }
     }
 
@@ -402,9 +439,6 @@
         @Override
         public void onSuccess() {
             localLog("PnoScanListener onSuccess");
-
-            // reset the count
-            mScanRestartCount = 0;
         }
 
         @Override
@@ -451,6 +485,7 @@
             boolean wasConnectAttempted;
             wasConnectAttempted = handleScanResults(mScanDetails, "PnoScanListener");
             clearScanDetails();
+            mScanRestartCount = 0;
 
             if (!wasConnectAttempted) {
                 // The scan results were rejected by QNS due to low RSSI values
@@ -507,6 +542,9 @@
                     + " secureNetworkBonus " + mSecureBonus
                     + " initialScoreMax " + mInitialScoreMax);
 
+        // Register for all single scan results
+        mScanner.registerScanListener(mAllSingleScanListener);
+
         Log.i(TAG, "ConnectivityScanManager initialized ");
     }
 
@@ -661,7 +699,7 @@
             Log.i(TAG, "start a single scan from watchdogHandler");
 
             scheduleWatchdogTimer();
-            startSingleScan(true, true);
+            startSingleScan(true);
         }
     }
 
@@ -694,7 +732,7 @@
         }
 
         mLastPeriodicSingleScanTimeStamp = currentTimeStamp;
-        startSingleScan(false, isFullBandScan);
+        startSingleScan(isFullBandScan);
         schedulePeriodicScanTimer(mPeriodicSingleScanInterval);
 
         // Set up the next scan interval in an exponential backoff fashion.
@@ -721,7 +759,7 @@
     }
 
     // Start a single scan
-    private void startSingleScan(boolean isWatchdogTriggered, boolean isFullBandScan) {
+    private void startSingleScan(boolean isFullBandScan) {
         if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
             return;
         }
@@ -753,7 +791,7 @@
         // mSingleScanListener.clearScanDetails();
         // mScanner.startScan(settings, mSingleScanListener, WIFI_WORK_SOURCE);
         SingleScanListener singleScanListener =
-                new SingleScanListener(isWatchdogTriggered, isFullBandScan);
+                new SingleScanListener(isFullBandScan);
         mScanner.startScan(settings, singleScanListener, WIFI_WORK_SOURCE);
     }
 
@@ -761,6 +799,12 @@
     private void startPeriodicScan(boolean scanImmediately) {
         mPnoScanListener.resetLowRssiNetworkRetryDelay();
 
+        // No connectivity scan if auto roaming is disabled.
+        if (mWifiState == WIFI_STATE_CONNECTED
+                && !mConfigManager.getEnableAutoJoinWhenAssociated()) {
+            return;
+        }
+
         // Due to b/28020168, timer based single scan will be scheduled
         // to provide periodic scan in an exponential backoff fashion.
         if (!ENABLE_BACKGROUND_SCAN) {
@@ -818,6 +862,7 @@
         mPnoScanListener.clearScanDetails();
 
         mScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, mPnoScanListener);
+        mPnoScanStarted = true;
     }
 
     // Start a ConnectedPNO scan when screen is off and Wifi is connected
@@ -861,6 +906,16 @@
         mPnoScanListener.clearScanDetails();
 
         mScanner.startConnectedPnoScan(scanSettings, pnoSettings, mPnoScanListener);
+        mPnoScanStarted = true;
+    }
+
+    // Stop a PNO scan. This includes both DisconnectedPNO and ConnectedPNO scans.
+    private void stopPnoScan() {
+        if (mPnoScanStarted) {
+            mScanner.stopPnoScan(mPnoScanListener);
+        }
+
+        mPnoScanStarted = false;
     }
 
     // Set up watchdog timer
@@ -879,14 +934,23 @@
                             mClock.elapsedRealtime() + intervalMs,
                             PERIODIC_SCAN_TIMER_TAG,
                             mPeriodicScanTimerListener, mEventHandler);
+        mPeriodicScanTimerSet = true;
+    }
+
+    // Cancel periodic scan timer
+    private void cancelPeriodicScanTimer() {
+        if (mPeriodicScanTimerSet) {
+            mAlarmManager.cancel(mPeriodicScanTimerListener);
+            mPeriodicScanTimerSet = false;
+        }
     }
 
     // Set up timer to start a delayed single scan after RESTART_SCAN_DELAY_MS
-    private void scheduleDelayedSingleScan(boolean isWatchdogTriggered, boolean isFullBandScan) {
+    private void scheduleDelayedSingleScan(boolean isFullBandScan) {
         localLog("scheduleDelayedSingleScan");
 
         RestartSingleScanListener restartSingleScanListener =
-                new RestartSingleScanListener(isWatchdogTriggered, isFullBandScan);
+                new RestartSingleScanListener(isFullBandScan);
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                             mClock.elapsedRealtime() + RESTART_SCAN_DELAY_MS,
                             RESTART_SINGLE_SCAN_TIMER_TAG,
@@ -943,11 +1007,11 @@
         // Due to b/28020168, timer based single scan will be scheduled
         // to provide periodic scan in an exponential backoff fashion.
         if (!ENABLE_BACKGROUND_SCAN) {
-            mAlarmManager.cancel(mPeriodicScanTimerListener);
+            cancelPeriodicScanTimer();
         } else {
             mScanner.stopBackgroundScan(mPeriodicScanListener);
         }
-        mScanner.stopPnoScan(mPnoScanListener);
+        stopPnoScan();
         mScanRestartCount = 0;
     }
 
@@ -970,8 +1034,10 @@
 
         mWifiState = state;
 
-        // Kick off the watchdog timer if entering disconnected state
+        // Reset BSSID of last connection attempt and kick off
+        // the watchdog timer if entering disconnected state.
         if (mWifiState == WIFI_STATE_DISCONNECTED) {
+            mLastConnectionAttemptBssid = null;
             scheduleWatchdogTimer();
         }
 
@@ -1051,6 +1117,7 @@
         if (!mWifiEnabled) {
             stopConnectivityScan();
             resetLastPeriodicSingleScanTimeStamp();
+            mLastConnectionAttemptBssid = null;
         }
     }
 
@@ -1065,6 +1132,7 @@
         if (!mWifiConnectivityManagerEnabled) {
             stopConnectivityScan();
             resetLastPeriodicSingleScanTimeStamp();
+            mLastConnectionAttemptBssid = null;
         }
     }
 
diff --git a/service/java/com/android/server/wifi/WifiController.java b/service/java/com/android/server/wifi/WifiController.java
index 8fb3789..bfbf449 100644
--- a/service/java/com/android/server/wifi/WifiController.java
+++ b/service/java/com/android/server/wifi/WifiController.java
@@ -18,6 +18,7 @@
 
 import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
 import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
+import static android.net.wifi.WifiManager.WIFI_MODE_NO_LOCKS_HELD;
 import static android.net.wifi.WifiManager.WIFI_MODE_SCAN_ONLY;
 
 import android.app.AlarmManager;
@@ -42,12 +43,15 @@
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
-import com.android.server.wifi.WifiServiceImpl.LockList;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
-class WifiController extends StateMachine {
+/**
+ * WifiController is the class used to manage on/off state of WifiStateMachine for various operating
+ * modes (normal, airplane, wifi hotspot, etc.).
+ */
+public class WifiController extends StateMachine {
     private static final String TAG = "WifiController";
     private static final boolean DBG = false;
     private Context mContext;
@@ -89,9 +93,9 @@
             "com.android.server.WifiManager.action.DEVICE_IDLE";
 
     /* References to values tracked in WifiService */
-    final WifiStateMachine mWifiStateMachine;
-    final WifiSettingsStore mSettingsStore;
-    final LockList mLocks;
+    private final WifiStateMachine mWifiStateMachine;
+    private final WifiSettingsStore mSettingsStore;
+    private final WifiLockManager mWifiLockManager;
 
     /**
      * Temporary for computing UIDS that are responsible for starting WIFI.
@@ -105,22 +109,26 @@
 
     private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
 
-    static final int CMD_EMERGENCY_MODE_CHANGED       = BASE + 1;
-    static final int CMD_SCREEN_ON                    = BASE + 2;
-    static final int CMD_SCREEN_OFF                   = BASE + 3;
-    static final int CMD_BATTERY_CHANGED              = BASE + 4;
-    static final int CMD_DEVICE_IDLE                  = BASE + 5;
-    static final int CMD_LOCKS_CHANGED                = BASE + 6;
-    static final int CMD_SCAN_ALWAYS_MODE_CHANGED     = BASE + 7;
-    static final int CMD_WIFI_TOGGLED                 = BASE + 8;
-    static final int CMD_AIRPLANE_TOGGLED             = BASE + 9;
-    static final int CMD_SET_AP                       = BASE + 10;
-    static final int CMD_DEFERRED_TOGGLE              = BASE + 11;
-    static final int CMD_USER_PRESENT                 = BASE + 12;
-    static final int CMD_AP_START_FAILURE             = BASE + 13;
-    static final int CMD_EMERGENCY_CALL_STATE_CHANGED = BASE + 14;
-    static final int CMD_AP_STOPPED                   = BASE + 15;
-    static final int CMD_STA_START_FAILURE            = BASE + 16;
+    static final int CMD_EMERGENCY_MODE_CHANGED        = BASE + 1;
+    static final int CMD_SCREEN_ON                     = BASE + 2;
+    static final int CMD_SCREEN_OFF                    = BASE + 3;
+    static final int CMD_BATTERY_CHANGED               = BASE + 4;
+    static final int CMD_DEVICE_IDLE                   = BASE + 5;
+    static final int CMD_LOCKS_CHANGED                 = BASE + 6;
+    static final int CMD_SCAN_ALWAYS_MODE_CHANGED      = BASE + 7;
+    static final int CMD_WIFI_TOGGLED                  = BASE + 8;
+    static final int CMD_AIRPLANE_TOGGLED              = BASE + 9;
+    static final int CMD_SET_AP                        = BASE + 10;
+    static final int CMD_DEFERRED_TOGGLE               = BASE + 11;
+    static final int CMD_USER_PRESENT                  = BASE + 12;
+    static final int CMD_AP_START_FAILURE              = BASE + 13;
+    static final int CMD_EMERGENCY_CALL_STATE_CHANGED  = BASE + 14;
+    static final int CMD_AP_STOPPED                    = BASE + 15;
+    static final int CMD_STA_START_FAILURE             = BASE + 16;
+    // Command used to trigger a wifi stack restart when in active mode
+    static final int CMD_RESTART_WIFI                  = BASE + 17;
+    // Internal command used to complete wifi stack restart
+    private static final int CMD_RESTART_WIFI_CONTINUE = BASE + 18;
 
     private DefaultState mDefaultState = new DefaultState();
     private StaEnabledState mStaEnabledState = new StaEnabledState();
@@ -135,14 +143,14 @@
     private NoLockHeldState mNoLockHeldState = new NoLockHeldState();
     private EcmState mEcmState = new EcmState();
 
-    WifiController(Context context, WifiStateMachine wsm,
-                   WifiSettingsStore wss, LockList locks, Looper looper, FrameworkFacade f) {
+    WifiController(Context context, WifiStateMachine wsm, WifiSettingsStore wss,
+            WifiLockManager wifiLockManager, Looper looper, FrameworkFacade f) {
         super(TAG, looper);
         mFacade = f;
         mContext = context;
         mWifiStateMachine = wsm;
         mSettingsStore = wss;
-        mLocks = locks;
+        mWifiLockManager = wifiLockManager;
 
         mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
         Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
@@ -336,7 +344,7 @@
     private void updateBatteryWorkSource() {
         mTmpWorkSource.clear();
         if (mDeviceIdle) {
-            mLocks.updateWorkSource(mTmpWorkSource);
+            mTmpWorkSource.add(mWifiLockManager.createMergedWorkSource());
         }
         mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
     }
@@ -404,6 +412,8 @@
                 case CMD_AP_START_FAILURE:
                 case CMD_AP_STOPPED:
                 case CMD_STA_START_FAILURE:
+                case CMD_RESTART_WIFI:
+                case CMD_RESTART_WIFI_CONTINUE:
                     break;
                 case CMD_USER_PRESENT:
                     mFirstUserSignOnSeen = true;
@@ -479,6 +489,9 @@
                     log("DEFERRED_TOGGLE handled");
                     sendMessage((Message)(msg.obj));
                     break;
+                case CMD_RESTART_WIFI_CONTINUE:
+                    transitionTo(mDeviceActiveState);
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -690,7 +703,7 @@
                 case CMD_WIFI_TOGGLED:
                     if (mSettingsStore.isWifiToggleEnabled()) {
                         mWifiStateMachine.setHostApRunning(null, false);
-                        mPendingState = mStaEnabledState;
+                        mPendingState = mDeviceActiveState;
                     }
                     break;
                 case CMD_SET_AP:
@@ -820,6 +833,10 @@
                 }
                 mFirstUserSignOnSeen = true;
                 return HANDLED;
+            } else if (msg.what == CMD_RESTART_WIFI) {
+                deferMessage(obtainMessage(CMD_RESTART_WIFI_CONTINUE));
+                transitionTo(mApStaDisabledState);
+                return HANDLED;
             }
             return NOT_HANDLED;
         }
@@ -882,26 +899,23 @@
     }
 
     private void checkLocksAndTransitionWhenDeviceIdle() {
-        if (mLocks.hasLocks()) {
-            switch (mLocks.getStrongestLockMode()) {
-                case WIFI_MODE_FULL:
-                    transitionTo(mFullLockHeldState);
-                    break;
-                case WIFI_MODE_FULL_HIGH_PERF:
-                    transitionTo(mFullHighPerfLockHeldState);
-                    break;
-                case WIFI_MODE_SCAN_ONLY:
+        switch (mWifiLockManager.getStrongestLockMode()) {
+            case WIFI_MODE_NO_LOCKS_HELD:
+                if (mSettingsStore.isScanAlwaysAvailable()) {
                     transitionTo(mScanOnlyLockHeldState);
-                    break;
-                default:
-                    loge("Illegal lock " + mLocks.getStrongestLockMode());
-            }
-        } else {
-            if (mSettingsStore.isScanAlwaysAvailable()) {
+                } else {
+                    transitionTo(mNoLockHeldState);
+                }
+                break;
+            case WIFI_MODE_FULL:
+                transitionTo(mFullLockHeldState);
+                break;
+            case WIFI_MODE_FULL_HIGH_PERF:
+                transitionTo(mFullHighPerfLockHeldState);
+                break;
+            case WIFI_MODE_SCAN_ONLY:
                 transitionTo(mScanOnlyLockHeldState);
-            } else {
-                transitionTo(mNoLockHeldState);
-            }
+                break;
         }
     }
 
diff --git a/service/java/com/android/server/wifi/WifiCountryCode.java b/service/java/com/android/server/wifi/WifiCountryCode.java
index bd9b14b..1339656 100644
--- a/service/java/com/android/server/wifi/WifiCountryCode.java
+++ b/service/java/com/android/server/wifi/WifiCountryCode.java
@@ -21,6 +21,9 @@
 
 /**
  * Provide functions for making changes to WiFi country code.
+ * This Country Code is from MCC or phone default setting. This class sends Country Code
+ * to driver through wpa_supplicant when WifiStateMachine marks current state as ready
+ * using setReadyForChange(true).
  */
 public class WifiCountryCode {
     private static final String TAG = "WifiCountryCode";
@@ -149,12 +152,29 @@
     }
 
     /**
-     * @return Get the current country code, returns null if no country code is set.
+     * Method to get the Country Code that was sent to wpa_supplicant.
+     *
+     * @return Returns the local copy of the Country Code that was sent to the driver upon
+     * setReadyForChange(true).
+     * If wpa_supplicant was never started, this may be null even if a SIM reported a valid
+     * country code.
+     * Returns null if no Country Code was sent to driver.
      */
-    public synchronized String getCurrentCountryCode() {
+    public synchronized String getCountryCodeSentToDriver() {
         return mCurrentCountryCode;
     }
 
+    /**
+     * Method to return the currently reported Country Code from the SIM or phone default setting.
+     *
+     * @return The currently reported Country Code from the SIM. If there is no Country Code
+     * reported from SIM, a phone default Country Code will be returned.
+     * Returns null when there is no Country Code available.
+     */
+    public synchronized String getCountryCode() {
+        return pickCountryCode();
+    }
+
     private void updateCountryCode() {
         if (DBG) Log.d(TAG, "Update country code");
         String country = pickCountryCode();
@@ -163,7 +183,7 @@
         // 1. Wpa supplicant may silently modify the country code.
         // 2. If Wifi restarted therefoere wpa_supplicant also restarted,
         // the country code counld be reset to '00' by wpa_supplicant.
-        if (country.length() != 0) {
+        if (country != null) {
             setCountryCodeNative(country);
         }
         // We do not set country code if there is no candidate. This is reasonable
@@ -178,8 +198,8 @@
         if (mDefaultCountryCode != null) {
             return mDefaultCountryCode;
         }
-        // If there is no candidate country code we will return an empty string.
-        return "";
+        // If there is no candidate country code we will return null.
+        return null;
     }
 
     private boolean setCountryCodeNative(String country) {
diff --git a/service/java/com/android/server/wifi/WifiLastResortWatchdog.java b/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
index 896c1c8..558b50e 100644
--- a/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
+++ b/service/java/com/android/server/wifi/WifiLastResortWatchdog.java
@@ -80,6 +80,8 @@
 
     private WifiMetrics mWifiMetrics;
 
+    private WifiController mWifiController = null;
+
     WifiLastResortWatchdog(WifiMetrics wifiMetrics) {
         mWifiMetrics = wifiMetrics;
     }
@@ -324,13 +326,21 @@
     }
 
     /**
-     * Restart Supplicant, Driver & return WifiStateMachine to InitialState
+     * Trigger a restart of the wifi stack.
      */
     private void restartWifiStack() {
         if (VDBG) Log.v(TAG, "restartWifiStack.");
-        Log.i(TAG, "Triggered.");
+
+        // First verify that we can send the trigger message.
+        if (mWifiController == null) {
+            Log.e(TAG, "WifiLastResortWatchdog unable to trigger: WifiController is null");
+            return;
+        }
+
         if (DBG) Log.d(TAG, toString());
-        // <TODO>
+
+        mWifiController.sendMessage(WifiController.CMD_RESTART_WIFI);
+        Log.i(TAG, "Triggered WiFi stack restart.");
     }
 
     /**
@@ -537,4 +547,14 @@
                     + ", Age: " + age;
         }
     }
+
+    /**
+     * Method used to set the WifiController for the this watchdog.
+     *
+     * The WifiController is used to send the restart wifi command to carry out the wifi restart.
+     * @param wifiController WifiController instance that will be sent the CMD_RESTART_WIFI message.
+     */
+    public void setWifiController(WifiController wifiController) {
+        mWifiController = wifiController;
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiLockManager.java b/service/java/com/android/server/wifi/WifiLockManager.java
new file mode 100644
index 0000000..fe193f0
--- /dev/null
+++ b/service/java/com/android/server/wifi/WifiLockManager.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.WorkSource;
+import android.util.Slog;
+
+import com.android.internal.app.IBatteryStats;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * WifiLockManager maintains the list of wake locks held by different applications.
+ */
+public class WifiLockManager {
+    private static final String TAG = "WifiLockManager";
+    private boolean mVerboseLoggingEnabled = false;
+
+    private final Context mContext;
+    private final IBatteryStats mBatteryStats;
+
+    private final List<WifiLock> mWifiLocks = new ArrayList<>();
+    // some wifi lock statistics
+    private int mFullHighPerfLocksAcquired;
+    private int mFullHighPerfLocksReleased;
+    private int mFullLocksAcquired;
+    private int mFullLocksReleased;
+    private int mScanLocksAcquired;
+    private int mScanLocksReleased;
+
+    WifiLockManager(Context context, IBatteryStats batteryStats) {
+        mContext = context;
+        mBatteryStats = batteryStats;
+    }
+
+    /**
+     * Method allowing a calling app to acquire a Wifi WakeLock in the supplied mode.
+     *
+     * This method verifies that the caller has permission to make the call and that the lock mode
+     * is a valid WifiLock mode.
+     * @param lockMode int representation of the Wifi WakeLock type.
+     * @param tag String passed to WifiManager.WifiLock
+     * @param binder IBinder for the calling app
+     * @param ws WorkSource of the calling app
+     *
+     * @return true if the lock was successfully acquired, false if the lockMode was invalid.
+     */
+    public boolean acquireWifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+        if (!isValidLockMode(lockMode)) {
+            throw new IllegalArgumentException("lockMode =" + lockMode);
+        }
+        if (ws == null || ws.size() == 0) {
+            ws = new WorkSource(Binder.getCallingUid());
+        } else {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.UPDATE_DEVICE_STATS, null);
+        }
+        return addLock(new WifiLock(lockMode, tag, binder, ws));
+    }
+
+    /**
+     * Method used by applications to release a WiFi Wake lock.  This method checks permissions for
+     * the caller and if allowed, releases the underlying WifiLock(s).
+     *
+     * @param binder IBinder for the calling app.
+     * @return true if the lock was released, false if the caller did not hold any locks
+     */
+    public boolean releaseWifiLock(IBinder binder) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+        return releaseLock(binder);
+    }
+
+    /**
+     * Method used to get the strongest lock type currently held by the WifiLockManager.
+     *
+     * If no locks are held, WifiManager.WIFI_MODE_NO_LOCKS_HELD is returned.
+     *
+     * @return int representing the currently held (highest power consumption) lock.
+     */
+    public synchronized int getStrongestLockMode() {
+        if (mWifiLocks.isEmpty()) {
+            return WifiManager.WIFI_MODE_NO_LOCKS_HELD;
+        }
+
+        if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
+            return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
+        }
+
+        if (mFullLocksAcquired > mFullLocksReleased) {
+            return WifiManager.WIFI_MODE_FULL;
+        }
+
+        return WifiManager.WIFI_MODE_SCAN_ONLY;
+    }
+
+    /**
+     * Method to create a WorkSource containing all active WifiLock WorkSources.
+     */
+    public synchronized WorkSource createMergedWorkSource() {
+        WorkSource mergedWS = new WorkSource();
+        for (WifiLock lock : mWifiLocks) {
+            mergedWS.add(lock.getWorkSource());
+        }
+        return mergedWS;
+    }
+
+    /**
+     * Method used to update WifiLocks with a new WorkSouce.
+     *
+     * @param binder IBinder for the calling application.
+     * @param ws WorkSource to add to the existing WifiLock(s).
+     */
+    public synchronized void updateWifiLockWorkSource(IBinder binder, WorkSource ws) {
+        // Does the caller have permission to make this call?
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.UPDATE_DEVICE_STATS, null);
+
+        // Now check if there is an active lock
+        WifiLock wl = findLockByBinder(binder);
+        if (wl == null) {
+            throw new IllegalArgumentException("Wifi lock not active");
+        }
+
+        WorkSource newWorkSource;
+        if (ws == null || ws.size() == 0) {
+            newWorkSource = new WorkSource(Binder.getCallingUid());
+        } else {
+            // Make a copy of the WorkSource before adding it to the WakeLock
+            newWorkSource = new WorkSource(ws);
+        }
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mBatteryStats.noteFullWifiLockReleasedFromSource(wl.mWorkSource);
+            wl.mWorkSource = newWorkSource;
+            mBatteryStats.noteFullWifiLockAcquiredFromSource(wl.mWorkSource);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private static boolean isValidLockMode(int lockMode) {
+        if (lockMode != WifiManager.WIFI_MODE_FULL
+                && lockMode != WifiManager.WIFI_MODE_SCAN_ONLY
+                && lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
+            return false;
+        }
+        return true;
+    }
+
+    private synchronized boolean addLock(WifiLock lock) {
+        if (mVerboseLoggingEnabled) {
+            Slog.d(TAG, "addLock: " + lock);
+        }
+
+        if (findLockByBinder(lock.getBinder()) != null) {
+            if (mVerboseLoggingEnabled) {
+                Slog.d(TAG, "attempted to add a lock when already holding one");
+            }
+            return false;
+        }
+
+        mWifiLocks.add(lock);
+
+        boolean lockAdded = false;
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mBatteryStats.noteFullWifiLockAcquiredFromSource(lock.mWorkSource);
+            switch(lock.mMode) {
+                case WifiManager.WIFI_MODE_FULL:
+                    ++mFullLocksAcquired;
+                    break;
+                case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+                    ++mFullHighPerfLocksAcquired;
+                    break;
+                case WifiManager.WIFI_MODE_SCAN_ONLY:
+                    ++mScanLocksAcquired;
+                    break;
+            }
+            lockAdded = true;
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return lockAdded;
+    }
+
+    private synchronized WifiLock removeLock(IBinder binder) {
+        WifiLock lock = findLockByBinder(binder);
+        if (lock != null) {
+            mWifiLocks.remove(lock);
+            lock.unlinkDeathRecipient();
+        }
+        return lock;
+    }
+
+    private synchronized boolean releaseLock(IBinder binder) {
+        WifiLock wifiLock = removeLock(binder);
+        if (wifiLock == null) {
+            // attempting to release a lock that is not active.
+            return false;
+        }
+
+        if (mVerboseLoggingEnabled) {
+            Slog.d(TAG, "releaseLock: " + wifiLock);
+        }
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
+            switch(wifiLock.mMode) {
+                case WifiManager.WIFI_MODE_FULL:
+                    ++mFullLocksReleased;
+                    break;
+                case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+                    ++mFullHighPerfLocksReleased;
+                    break;
+                case WifiManager.WIFI_MODE_SCAN_ONLY:
+                    ++mScanLocksReleased;
+                    break;
+            }
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return true;
+    }
+
+
+    private synchronized WifiLock findLockByBinder(IBinder binder) {
+        for (WifiLock lock : mWifiLocks) {
+            if (lock.getBinder() == binder) {
+                return lock;
+            }
+        }
+        return null;
+    }
+
+    protected void dump(PrintWriter pw) {
+        pw.println("Locks acquired: " + mFullLocksAcquired + " full, "
+                + mFullHighPerfLocksAcquired + " full high perf, "
+                + mScanLocksAcquired + " scan");
+        pw.println("Locks released: " + mFullLocksReleased + " full, "
+                + mFullHighPerfLocksReleased + " full high perf, "
+                + mScanLocksReleased + " scan");
+        pw.println();
+        pw.println("Locks held:");
+        for (WifiLock lock : mWifiLocks) {
+            pw.print("    ");
+            pw.println(lock);
+        }
+    }
+
+    protected void enableVerboseLogging(int verbose) {
+        if (verbose > 0) {
+            mVerboseLoggingEnabled = true;
+        } else {
+            mVerboseLoggingEnabled = false;
+        }
+    }
+
+    private class WifiLock implements IBinder.DeathRecipient {
+        String mTag;
+        int mUid;
+        IBinder mBinder;
+        int mMode;
+        WorkSource mWorkSource;
+
+        WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
+            mTag = tag;
+            mBinder = binder;
+            mUid = Binder.getCallingUid();
+            mMode = lockMode;
+            mWorkSource = ws;
+            try {
+                mBinder.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                binderDied();
+            }
+        }
+
+        protected WorkSource getWorkSource() {
+            return mWorkSource;
+        }
+
+        protected int getUid() {
+            return mUid;
+        }
+
+        protected IBinder getBinder() {
+            return mBinder;
+        }
+
+        public void binderDied() {
+            releaseLock(mBinder);
+        }
+
+        public void unlinkDeathRecipient() {
+            mBinder.unlinkToDeath(this, 0);
+        }
+
+        public String toString() {
+            return "WifiLock{" + this.mTag + " type=" + this.mMode + " uid=" + mUid + "}";
+        }
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiLogger.java b/service/java/com/android/server/wifi/WifiLogger.java
index 4251df4..c15e2a8 100644
--- a/service/java/com/android/server/wifi/WifiLogger.java
+++ b/service/java/com/android/server/wifi/WifiLogger.java
@@ -16,10 +16,12 @@
 
 package com.android.server.wifi;
 
+import android.content.Context;
 import android.util.Base64;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.R;
 import com.android.server.wifi.util.ByteArrayRingBuffer;
 import com.android.server.wifi.util.StringUtil;
 
@@ -89,13 +91,13 @@
     /** minimum buffer size for each of the log levels */
     private static final int MinBufferSizes[] = new int[] { 0, 16384, 16384, 65536 };
 
-    @VisibleForTesting public static final int RING_BUFFER_BYTE_LIMIT_SMALL = 32 * 1024;
-    @VisibleForTesting public static final int RING_BUFFER_BYTE_LIMIT_LARGE = 1024 * 1024;
     @VisibleForTesting public static final String FIRMWARE_DUMP_SECTION_HEADER =
             "FW Memory dump";
     @VisibleForTesting public static final String DRIVER_DUMP_SECTION_HEADER =
             "Driver state dump";
 
+    private final int RING_BUFFER_BYTE_LIMIT_SMALL;
+    private final int RING_BUFFER_BYTE_LIMIT_LARGE;
     private int mLogLevel = VERBOSE_NO_LOG;
     private boolean mIsLoggingEventHandlerRegistered;
     private WifiNative.RingBufferStatus[] mRingBuffers;
@@ -103,15 +105,20 @@
     private WifiStateMachine mWifiStateMachine;
     private final WifiNative mWifiNative;
     private final BuildProperties mBuildProperties;
-    private int mMaxRingBufferSizeBytes = RING_BUFFER_BYTE_LIMIT_SMALL;
+    private int mMaxRingBufferSizeBytes;
 
-    public WifiLogger(
-            WifiStateMachine wifiStateMachine, WifiNative wifiNative,
-            BuildProperties buildProperties) {
+    public WifiLogger(Context context, WifiStateMachine wifiStateMachine, WifiNative wifiNative,
+                      BuildProperties buildProperties) {
+        RING_BUFFER_BYTE_LIMIT_SMALL = context.getResources().getInteger(
+                R.integer.config_wifi_logger_ring_buffer_default_size_limit_kb) * 1024;
+        RING_BUFFER_BYTE_LIMIT_LARGE = context.getResources().getInteger(
+                R.integer.config_wifi_logger_ring_buffer_verbose_size_limit_kb) * 1024;
+
         mWifiStateMachine = wifiStateMachine;
         mWifiNative = wifiNative;
         mBuildProperties = buildProperties;
         mIsLoggingEventHandlerRegistered = false;
+        mMaxRingBufferSizeBytes = RING_BUFFER_BYTE_LIMIT_SMALL;
     }
 
     @Override
@@ -224,8 +231,11 @@
         }
 
         dumpPacketFates(pw);
-
         pw.println("--------------------------------------------------------------------");
+
+        pw.println("WifiNative - Log Begin ----");
+        mWifiNative.getLocalLog().dump(fd, pw, args);
+        pw.println("WifiNative - Log End ----");
     }
 
     /* private methods and data */
@@ -536,7 +546,7 @@
         String result;
         //compress
         Deflater compressor = new Deflater();
-        compressor.setLevel(Deflater.BEST_COMPRESSION);
+        compressor.setLevel(Deflater.BEST_SPEED);
         compressor.setInput(input);
         compressor.finish();
         ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length);
diff --git a/service/java/com/android/server/wifi/WifiLoggerHal.java b/service/java/com/android/server/wifi/WifiLoggerHal.java
index ce36401..8a02764 100644
--- a/service/java/com/android/server/wifi/WifiLoggerHal.java
+++ b/service/java/com/android/server/wifi/WifiLoggerHal.java
@@ -32,8 +32,8 @@
     public static final byte TX_PKT_FATE_FW_DROP_OTHER = 5;
     public static final byte TX_PKT_FATE_DRV_QUEUED = 6;
     public static final byte TX_PKT_FATE_DRV_DROP_INVALID = 7;
-    public static final byte TX_PKT_FATE_DRV_DROP_NOBUFS = 9;
-    public static final byte TX_PKT_FATE_DRV_DROP_OTHER = 10;
+    public static final byte TX_PKT_FATE_DRV_DROP_NOBUFS = 8;
+    public static final byte TX_PKT_FATE_DRV_DROP_OTHER = 9;
 
     public static final byte RX_PKT_FATE_SUCCESS = 0;
     public static final byte RX_PKT_FATE_FW_QUEUED = 1;
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index cc6fe00..0dc5ccf 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -42,6 +42,11 @@
 public class WifiMetrics {
     private static final String TAG = "WifiMetrics";
     private static final boolean DBG = false;
+    /**
+     * Clamp the RSSI poll counts to values between [MIN,MAX]_RSSI_POLL
+     */
+    private static final int MAX_RSSI_POLL = 0;
+    private static final int MIN_RSSI_POLL = -127;
     private final Object mLock = new Object();
     private static final int MAX_CONNECTION_EVENTS = 256;
     private Clock mClock;
@@ -53,11 +58,11 @@
      * runtime in member lists of this WifiMetrics class, with the final WifiLog proto being pieced
      * together at dump-time
      */
-    private final WifiMetricsProto.WifiLog mWifiLogProto;
+    private final WifiMetricsProto.WifiLog mWifiLogProto = new WifiMetricsProto.WifiLog();
     /**
      * Session information that gets logged for every Wifi connection attempt.
      */
-    private final List<ConnectionEvent> mConnectionEventList;
+    private final List<ConnectionEvent> mConnectionEventList = new ArrayList<>();
     /**
      * The latest started (but un-ended) connection attempt
      */
@@ -65,16 +70,17 @@
     /**
      * Count of number of times each scan return code, indexed by WifiLog.ScanReturnCode
      */
-    private SparseIntArray mScanReturnEntries;
+    private final SparseIntArray mScanReturnEntries = new SparseIntArray();
     /**
      * Mapping of system state to the counts of scans requested in that wifi state * screenOn
      * combination. Indexed by WifiLog.WifiState * (1 + screenOn)
      */
-    private SparseIntArray mWifiSystemStateEntries;
+    private final SparseIntArray mWifiSystemStateEntries = new SparseIntArray();
     /**
      * Records the elapsedRealtime (in seconds) that represents the beginning of data
      * capture for for this WifiMetricsProto
      */
+    private final SparseIntArray mRssiPollCounts = new SparseIntArray();
     private long mRecordStartTimeSec;
 
     class RouterFingerPrint {
@@ -304,10 +310,6 @@
 
     public WifiMetrics(Clock clock) {
         mClock = clock;
-        mWifiLogProto = new WifiMetricsProto.WifiLog();
-        mConnectionEventList = new ArrayList<>();
-        mScanReturnEntries = new SparseIntArray();
-        mWifiSystemStateEntries = new SparseIntArray();
         mCurrentConnectionEvent = null;
         mScreenOn = true;
         mWifiState = WifiMetricsProto.WifiLog.WIFI_DISABLED;
@@ -790,6 +792,20 @@
         }
     }
 
+    /**
+     * Increment occurence count of RSSI level from RSSI poll.
+     * Ignores rssi values outside the bounds of [MIN_RSSI_POLL, MAX_RSSI_POLL]
+     */
+    public void incrementRssiPollRssiCount(int rssi) {
+        if (!(rssi >= MIN_RSSI_POLL && rssi <= MAX_RSSI_POLL)) {
+            return;
+        }
+        synchronized (mLock) {
+            int count = mRssiPollCounts.get(rssi);
+            mRssiPollCounts.put(rssi, count + 1);
+        }
+    }
+
     public static final String PROTO_DUMP_ARG = "wifiMetricsProto";
     /**
      * Dump all WifiMetrics. Collects some metrics from ConfigStore, Settings and WifiManager
@@ -907,6 +923,13 @@
                         + mWifiLogProto.numLastResortWatchdogTriggersWithBadOther);
                 pw.println("mWifiLogProto.recordDurationSec="
                         + ((mClock.elapsedRealtime() / 1000) - mRecordStartTimeSec));
+                pw.println("mWifiLogProto.rssiPollRssiCount: Printing counts for [" + MIN_RSSI_POLL
+                        + ", " + MAX_RSSI_POLL + "]");
+                StringBuilder sb = new StringBuilder();
+                for (int i = MIN_RSSI_POLL; i <= MAX_RSSI_POLL; i++) {
+                    sb.append(mRssiPollCounts.get(i) + " ");
+                }
+                pw.println("  " + sb.toString());
             }
         }
     }
@@ -919,6 +942,7 @@
      */
     private void consolidateProto(boolean incremental) {
         List<WifiMetricsProto.ConnectionEvent> events = new ArrayList<>();
+        List<WifiMetricsProto.RssiPollCount> rssis = new ArrayList<>();
         synchronized (mLock) {
             for (ConnectionEvent event : mConnectionEventList) {
                 // If this is not incremental, dump full ConnectionEvent list
@@ -964,6 +988,18 @@
             }
             mWifiLogProto.recordDurationSec = (int) ((mClock.elapsedRealtime() / 1000)
                     - mRecordStartTimeSec);
+
+            /**
+             * Convert the SparseIntArray of RSSI poll rssi's and counts to the proto's repeated
+             * IntKeyVal array.
+             */
+            for (int i = 0; i < mRssiPollCounts.size(); i++) {
+                WifiMetricsProto.RssiPollCount keyVal = new WifiMetricsProto.RssiPollCount();
+                keyVal.rssi = mRssiPollCounts.keyAt(i);
+                keyVal.count = mRssiPollCounts.valueAt(i);
+                rssis.add(keyVal);
+            }
+            mWifiLogProto.rssiPollRssiCount = rssis.toArray(mWifiLogProto.rssiPollRssiCount);
         }
     }
 
@@ -979,6 +1015,7 @@
             mScanReturnEntries.clear();
             mWifiSystemStateEntries.clear();
             mRecordStartTimeSec = mClock.elapsedRealtime() / 1000;
+            mRssiPollCounts.clear();
             mWifiLogProto.clear();
         }
     }
diff --git a/service/java/com/android/server/wifi/WifiMonitor.java b/service/java/com/android/server/wifi/WifiMonitor.java
index 2c1bde1..1f2b397 100644
--- a/service/java/com/android/server/wifi/WifiMonitor.java
+++ b/service/java/com/android/server/wifi/WifiMonitor.java
@@ -594,7 +594,7 @@
         while (true) {
             if (mWifiNative.connectToSupplicant()) {
                 mConnected = true;
-                new MonitorThread().start();
+                new MonitorThread(mWifiNative.getLocalLog()).start();
                 return true;
             }
             if (connectTries++ < 5) {
@@ -723,10 +723,11 @@
     }
 
     private class MonitorThread extends Thread {
-        private final LocalLog mLocalLog = mWifiNative.getLocalLog();
+        private final LocalLog mLocalLog;
 
-        public MonitorThread() {
+        public MonitorThread(LocalLog localLog) {
             super("WifiMonitor");
+            mLocalLog = localLog;
         }
 
         public void run() {
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index f268f62..73765ee 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -104,7 +105,7 @@
 
     private static final LocalLog sLocalLog = new LocalLog(8192);
 
-    public static LocalLog getLocalLog() {
+    public @NonNull LocalLog getLocalLog() {
         return sLocalLog;
     }
 
diff --git a/service/java/com/android/server/wifi/WifiNetworkHistory.java b/service/java/com/android/server/wifi/WifiNetworkHistory.java
index 380b768..edbc516 100644
--- a/service/java/com/android/server/wifi/WifiNetworkHistory.java
+++ b/service/java/com/android/server/wifi/WifiNetworkHistory.java
@@ -37,6 +37,7 @@
 import java.io.DataOutputStream;
 import java.io.EOFException;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.text.DateFormat;
 import java.util.BitSet;
@@ -543,12 +544,14 @@
                     }
                 }
             }
-        } catch (NumberFormatException e) {
-            Log.e(TAG, "readNetworkHistory: failed to read, revert to default, " + e, e);
         } catch (EOFException e) {
             // do nothing
+        } catch (FileNotFoundException e) {
+            Log.i(TAG, "readNetworkHistory: no config file, " + e);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "readNetworkHistory: failed to parse, " + e, e);
         } catch (IOException e) {
-            Log.e(TAG, "readNetworkHistory: No config file, revert to default, " + e, e);
+            Log.e(TAG, "readNetworkHistory: failed to read, " + e, e);
         }
     }
 
diff --git a/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java b/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java
index 241ddb8..ee7397a 100644
--- a/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java
@@ -98,8 +98,7 @@
     private static final int INVALID_TIME_STAMP = -1;
     private long mLastQualifiedNetworkSelectionTimeStamp = INVALID_TIME_STAMP;
 
-    // Temporarily, for dog food
-    private final LocalLog mLocalLog = new LocalLog(1024);
+    private final LocalLog mLocalLog = new LocalLog(512);
     private int mRssiScoreSlope = RSSI_SCORE_SLOPE;
     private int mRssiScoreOffset = RSSI_SCORE_OFFSET;
     private int mSameBssidAward = SAME_BSSID_AWARD;
@@ -782,7 +781,9 @@
                     potentialCandidate = network;
                 }
                 //update the cached candidate
-                if (score > status.getCandidateScore()) {
+                if (score > status.getCandidateScore() || (score == status.getCandidateScore()
+                      && status.getCandidate() != null
+                      && scanResult.level > status.getCandidate().level)) {
                     status.setCandidate(scanResult);
                     status.setCandidateScore(score);
                 }
@@ -796,6 +797,7 @@
                 currentHighestScore = highestScore;
                 scanResultCandidate = scanResult;
                 networkCandidate = configurationCandidateForThisScan;
+                networkCandidate.getNetworkSelectionStatus().setCandidate(scanResultCandidate);
             }
         }
 
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 7193580..740ef6e 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -130,15 +130,6 @@
     private final Context mContext;
     private final FrameworkFacade mFacade;
 
-    final LockList mLocks = new LockList();
-    // some wifi lock statistics
-    private int mFullHighPerfLocksAcquired;
-    private int mFullHighPerfLocksReleased;
-    private int mFullLocksAcquired;
-    private int mFullLocksReleased;
-    private int mScanLocksAcquired;
-    private int mScanLocksReleased;
-
     private final List<Multicaster> mMulticasters =
             new ArrayList<Multicaster>();
     private int mMulticastEnabled;
@@ -317,6 +308,7 @@
     WifiStateMachineHandler mWifiStateMachineHandler;
 
     private WifiController mWifiController;
+    private final WifiLockManager mWifiLockManager;
 
     public WifiServiceImpl(Context context) {
         mContext = context;
@@ -349,10 +341,13 @@
         mNotificationController = new WifiNotificationController(mContext,
                 wifiThread.getLooper(), mWifiStateMachine, mFacade, null);
 
+        mWifiLockManager = new WifiLockManager(mContext, mBatteryStats);
         mClientHandler = new ClientHandler(wifiThread.getLooper());
         mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
         mWifiController = new WifiController(mContext, mWifiStateMachine,
-                mSettingsStore, mLocks, wifiThread.getLooper(), mFacade);
+                mSettingsStore, mWifiLockManager, wifiThread.getLooper(), mFacade);
+        // Set the WifiController for WifiLastResortWatchdog
+        mWifiInjector.getWifiLastResortWatchdog().setWifiController(mWifiController);
     }
 
 
@@ -391,9 +386,12 @@
                         String state = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
                         if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(state)) {
                             Log.d(TAG, "resetting networks because SIM was removed");
-                            mWifiStateMachine.resetSimAuthNetworks();
+                            mWifiStateMachine.resetSimAuthNetworks(false);
                             Log.d(TAG, "resetting country code because SIM is removed");
                             mCountryCode.simCardRemoved();
+                        } else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(state)) {
+                            Log.d(TAG, "resetting networks because SIM was loaded");
+                            mWifiStateMachine.resetSimAuthNetworks(true);
                         }
                     }
                 },
@@ -1129,11 +1127,13 @@
 
      /**
      * Get the country code
-     * @return ISO 3166 country code.
+     * @return Get the best choice country code for wifi, regardless of if it was set or
+     * not.
+     * Returns null when there is no country code available.
      */
     public String getCountryCode() {
         enforceConnectivityInternalPermission();
-        String country = mCountryCode.getCurrentCountryCode();
+        String country = mCountryCode.getCountryCode();
         return country;
     }
     /**
@@ -1510,16 +1510,9 @@
                 }
             }
             pw.println();
-            pw.println("Locks acquired: " + mFullLocksAcquired + " full, " +
-                    mFullHighPerfLocksAcquired + " full high perf, " +
-                    mScanLocksAcquired + " scan");
-            pw.println("Locks released: " + mFullLocksReleased + " full, " +
-                    mFullHighPerfLocksReleased + " full high perf, " +
-                    mScanLocksReleased + " scan");
-            pw.println();
             pw.println("Locks held:");
-            mLocks.dump(pw);
-
+            mWifiLockManager.dump(pw);
+            pw.println();
             pw.println("Multicast Locks held:");
             for (Multicaster l : mMulticasters) {
                 pw.print("    ");
@@ -1532,251 +1525,35 @@
         }
     }
 
-    private class WifiLock extends DeathRecipient {
-        int mMode;
-        WorkSource mWorkSource;
-
-        WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
-            super(tag, binder);
-            mMode = lockMode;
-            mWorkSource = ws;
-        }
-
-        public void binderDied() {
-            synchronized (mLocks) {
-                releaseWifiLockLocked(mBinder);
-            }
-        }
-
-        public String toString() {
-            return "WifiLock{" + mTag + " type=" + mMode + " uid=" + mUid + "}";
-        }
-    }
-
-    public class LockList {
-        private List<WifiLock> mList;
-
-        private LockList() {
-            mList = new ArrayList<WifiLock>();
-        }
-
-        synchronized boolean hasLocks() {
-            return !mList.isEmpty();
-        }
-
-        synchronized int getStrongestLockMode() {
-            if (mList.isEmpty()) {
-                return WifiManager.WIFI_MODE_FULL;
-            }
-
-            if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
-                return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
-            }
-
-            if (mFullLocksAcquired > mFullLocksReleased) {
-                return WifiManager.WIFI_MODE_FULL;
-            }
-
-            return WifiManager.WIFI_MODE_SCAN_ONLY;
-        }
-
-        synchronized void updateWorkSource(WorkSource ws) {
-            for (int i = 0; i < mLocks.mList.size(); i++) {
-                ws.add(mLocks.mList.get(i).mWorkSource);
-            }
-        }
-
-        private void addLock(WifiLock lock) {
-            if (findLockByBinder(lock.mBinder) < 0) {
-                mList.add(lock);
-            }
-        }
-
-        private WifiLock removeLock(IBinder binder) {
-            int index = findLockByBinder(binder);
-            if (index >= 0) {
-                WifiLock ret = mList.remove(index);
-                ret.unlinkDeathRecipient();
-                return ret;
-            } else {
-                return null;
-            }
-        }
-
-        private int findLockByBinder(IBinder binder) {
-            int size = mList.size();
-            for (int i = size - 1; i >= 0; i--) {
-                if (mList.get(i).mBinder == binder)
-                    return i;
-            }
-            return -1;
-        }
-
-        private void dump(PrintWriter pw) {
-            for (WifiLock l : mList) {
-                pw.print("    ");
-                pw.println(l);
-            }
-        }
-    }
-
-    void enforceWakeSourcePermission(int uid, int pid) {
-        if (uid == android.os.Process.myUid()) {
-            return;
-        }
-        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
-                pid, uid, null);
-    }
-
+    @Override
     public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
-        if (lockMode != WifiManager.WIFI_MODE_FULL &&
-                lockMode != WifiManager.WIFI_MODE_SCAN_ONLY &&
-                lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
-            Slog.e(TAG, "Illegal argument, lockMode= " + lockMode);
-            if (DBG) throw new IllegalArgumentException("lockMode=" + lockMode);
-            return false;
-        }
-        if (ws != null && ws.size() == 0) {
-            ws = null;
-        }
-        if (ws != null) {
-            enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid());
-        }
-        if (ws == null) {
-            ws = new WorkSource(Binder.getCallingUid());
-        }
-        WifiLock wifiLock = new WifiLock(lockMode, tag, binder, ws);
-        synchronized (mLocks) {
-            return acquireWifiLockLocked(wifiLock);
-        }
-    }
-
-    private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException {
-        switch(wifiLock.mMode) {
-            case WifiManager.WIFI_MODE_FULL:
-            case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
-            case WifiManager.WIFI_MODE_SCAN_ONLY:
-                mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
-                break;
-        }
-    }
-
-    private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException {
-        switch(wifiLock.mMode) {
-            case WifiManager.WIFI_MODE_FULL:
-            case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
-            case WifiManager.WIFI_MODE_SCAN_ONLY:
-                mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
-                break;
-        }
-    }
-
-    private boolean acquireWifiLockLocked(WifiLock wifiLock) {
-        if (DBG) Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock);
-
-        mLocks.addLock(wifiLock);
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            noteAcquireWifiLock(wifiLock);
-            switch(wifiLock.mMode) {
-            case WifiManager.WIFI_MODE_FULL:
-                ++mFullLocksAcquired;
-                break;
-            case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
-                ++mFullHighPerfLocksAcquired;
-                break;
-
-            case WifiManager.WIFI_MODE_SCAN_ONLY:
-                ++mScanLocksAcquired;
-                break;
-            }
+        if (mWifiLockManager.acquireWifiLock(lockMode, tag, binder, ws)) {
             mWifiController.sendMessage(CMD_LOCKS_CHANGED);
             return true;
-        } catch (RemoteException e) {
-            return false;
-        } finally {
-            Binder.restoreCallingIdentity(ident);
         }
+        return false;
     }
 
-    public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) {
-        int uid = Binder.getCallingUid();
-        int pid = Binder.getCallingPid();
-        if (ws != null && ws.size() == 0) {
-            ws = null;
-        }
-        if (ws != null) {
-            enforceWakeSourcePermission(uid, pid);
-        }
-        long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mLocks) {
-                int index = mLocks.findLockByBinder(lock);
-                if (index < 0) {
-                    throw new IllegalArgumentException("Wifi lock not active");
-                }
-                WifiLock wl = mLocks.mList.get(index);
-                noteReleaseWifiLock(wl);
-                wl.mWorkSource = ws != null ? new WorkSource(ws) : new WorkSource(uid);
-                noteAcquireWifiLock(wl);
-            }
-        } catch (RemoteException e) {
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
+    @Override
+    public void updateWifiLockWorkSource(IBinder binder, WorkSource ws) {
+        mWifiLockManager.updateWifiLockWorkSource(binder, ws);
     }
 
-    public boolean releaseWifiLock(IBinder lock) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
-        synchronized (mLocks) {
-            return releaseWifiLockLocked(lock);
+    @Override
+    public boolean releaseWifiLock(IBinder binder) {
+        if (mWifiLockManager.releaseWifiLock(binder)) {
+            mWifiController.sendMessage(CMD_LOCKS_CHANGED);
+            return true;
         }
+        return false;
     }
 
-    private boolean releaseWifiLockLocked(IBinder lock) {
-        boolean hadLock;
-
-        WifiLock wifiLock = mLocks.removeLock(lock);
-
-        if (DBG) Slog.d(TAG, "releaseWifiLockLocked: " + wifiLock);
-
-        hadLock = (wifiLock != null);
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            if (hadLock) {
-                noteReleaseWifiLock(wifiLock);
-                switch(wifiLock.mMode) {
-                    case WifiManager.WIFI_MODE_FULL:
-                        ++mFullLocksReleased;
-                        break;
-                    case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
-                        ++mFullHighPerfLocksReleased;
-                        break;
-                    case WifiManager.WIFI_MODE_SCAN_ONLY:
-                        ++mScanLocksReleased;
-                        break;
-                }
-                mWifiController.sendMessage(CMD_LOCKS_CHANGED);
-            }
-        } catch (RemoteException e) {
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-
-        return hadLock;
-    }
-
-    private abstract class DeathRecipient
-            implements IBinder.DeathRecipient {
+    private class Multicaster implements IBinder.DeathRecipient {
         String mTag;
         int mUid;
         IBinder mBinder;
 
-        DeathRecipient(String tag, IBinder binder) {
-            super();
+        Multicaster(String tag, IBinder binder) {
             mTag = tag;
             mUid = Binder.getCallingUid();
             mBinder = binder;
@@ -1787,20 +1564,7 @@
             }
         }
 
-        void unlinkDeathRecipient() {
-            mBinder.unlinkToDeath(this, 0);
-        }
-
-        public int getUid() {
-            return mUid;
-        }
-    }
-
-    private class Multicaster extends DeathRecipient {
-        Multicaster(String tag, IBinder binder) {
-            super(tag, binder);
-        }
-
+        @Override
         public void binderDied() {
             Slog.e(TAG, "Multicaster binderDied");
             synchronized (mMulticasters) {
@@ -1811,6 +1575,14 @@
             }
         }
 
+        void unlinkDeathRecipient() {
+            mBinder.unlinkToDeath(this, 0);
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
         public String toString() {
             return "Multicaster{" + mTag + " uid=" + mUid  + "}";
         }
@@ -1899,6 +1671,7 @@
     public void enableVerboseLogging(int verbose) {
         enforceAccessPermission();
         mWifiStateMachine.enableVerboseLogging(verbose);
+        mWifiLockManager.enableVerboseLogging(verbose);
     }
 
     public int getVerboseLoggingLevel() {
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index 402cbe5..d89594f 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -108,6 +108,7 @@
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.hotspot2.Utils;
 import com.android.server.wifi.p2p.WifiP2pServiceImpl;
+import com.android.server.wifi.util.TelephonyUtil;
 
 import java.io.BufferedReader;
 import java.io.FileDescriptor;
@@ -1030,7 +1031,7 @@
                 R.bool.config_wifi_enable_wifi_firmware_debugging);
 
         if (enableFirmwareLogs) {
-            mWifiLogger = facade.makeRealLogger(this, mWifiNative, mBuildProperties);
+            mWifiLogger = facade.makeRealLogger(mContext, this, mWifiNative, mBuildProperties);
         } else {
             mWifiLogger = facade.makeBaseLogger();
         }
@@ -1293,25 +1294,39 @@
     }
 
     void enableVerboseLogging(int verbose) {
+        if (mVerboseLoggingLevel == verbose) {
+            // We are already at the desired verbosity, avoid resetting StateMachine log records by
+            // returning here until underlying bug is fixed (b/28027593)
+            return;
+        }
         mVerboseLoggingLevel = verbose;
         mFacade.setIntegerSetting(
                 mContext, Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, verbose);
         updateLoggingLevel();
     }
 
+    /**
+     * Set wpa_supplicant log level using |mVerboseLoggingLevel| flag.
+     */
+    void setSupplicantLogLevel() {
+        if (mVerboseLoggingLevel > 0) {
+            mWifiNative.setSupplicantLogLevel("DEBUG");
+        } else {
+            mWifiNative.setSupplicantLogLevel("INFO");
+        }
+    }
+
     void updateLoggingLevel() {
         if (mVerboseLoggingLevel > 0) {
             DBG = true;
-            mWifiNative.setSupplicantLogLevel("DEBUG");
             setLogRecSize(ActivityManager.isLowRamDeviceStatic()
                     ? NUM_LOG_RECS_VERBOSE_LOW_MEMORY : NUM_LOG_RECS_VERBOSE);
-            configureVerboseHalLogging(true);
         } else {
             DBG = false;
-            mWifiNative.setSupplicantLogLevel("INFO");
             setLogRecSize(NUM_LOG_RECS_NORMAL);
-            configureVerboseHalLogging(false);
         }
+        configureVerboseHalLogging(mVerboseLoggingLevel > 0);
+        setSupplicantLogLevel();
         mCountryCode.enableVerboseLogging(mVerboseLoggingLevel);
         mWifiLogger.startLogging(DBG);
         mWifiMonitor.enableVerboseLogging(mVerboseLoggingLevel);
@@ -1869,6 +1884,14 @@
     }
 
     /**
+     * Allow tests to confirm the operational mode for WSM.
+     */
+    @VisibleForTesting
+    protected int getOperationalModeForTest() {
+        return mOperationalMode;
+    }
+
+    /**
      * TODO: doc
      */
     public List<ScanResult> syncGetScanResultsList() {
@@ -2143,8 +2166,8 @@
     /**
      * reset cached SIM credential data
      */
-    public synchronized void resetSimAuthNetworks() {
-        sendMessage(CMD_RESET_SIM_NETWORKS);
+    public synchronized void resetSimAuthNetworks(boolean simPresent) {
+        sendMessage(CMD_RESET_SIM_NETWORKS, simPresent ? 1 : 0);
     }
 
     /**
@@ -2290,10 +2313,15 @@
         pw.println("mUserWantsSuspendOpt " + mUserWantsSuspendOpt);
         pw.println("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
         pw.println("Supplicant status " + mWifiNative.status(true));
-        if (mCountryCode.getCurrentCountryCode() != null) {
-            pw.println("CurrentCountryCode " + mCountryCode.getCurrentCountryCode());
+        if (mCountryCode.getCountryCodeSentToDriver() != null) {
+            pw.println("CountryCode sent to driver " + mCountryCode.getCountryCodeSentToDriver());
         } else {
-            pw.println("CurrentCountryCode is not initialized");
+            if (mCountryCode.getCountryCode() != null) {
+                pw.println("CountryCode: " +
+                        mCountryCode.getCountryCode() + " was not sent to driver");
+            } else {
+                pw.println("CountryCode was not initialized");
+            }
         }
         pw.println("mConnectedModeGScanOffloadStarted " + mConnectedModeGScanOffloadStarted);
         pw.println("mGScanPeriodMilli " + mGScanPeriodMilli);
@@ -2888,12 +2916,16 @@
         }
         enableRssiPolling(screenOn);
         if (mUserWantsSuspendOpt.get()) {
+            int shouldReleaseWakeLock = 0;
             if (screenOn) {
-                sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, 0);
+                sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, shouldReleaseWakeLock);
             } else {
-                // Allow 2s for suspend optimizations to be set
-                mSuspendWakeLock.acquire(2000);
-                sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, 0);
+                if (isConnected()) {
+                    // Allow 2s for suspend optimizations to be set
+                    mSuspendWakeLock.acquire(2000);
+                    shouldReleaseWakeLock = 1;
+                }
+                sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, shouldReleaseWakeLock);
             }
         }
         mScreenBroadcastReceived.set(true);
@@ -3139,6 +3171,10 @@
             if (newRssi > 0) newRssi -= 256;
             mWifiInfo.setRssi(newRssi);
             /*
+             * Log the rssi poll value in metrics
+             */
+            mWifiMetrics.incrementRssiPollRssiCount(newRssi);
+            /*
              * Rather then sending the raw RSSI out every time it
              * changes, we precalculate the signal level that would
              * be displayed in the status bar, and only send the
@@ -4079,7 +4115,9 @@
                     break;
                 case CMD_SET_SUSPEND_OPT_ENABLED:
                     if (message.arg1 == 1) {
-                        mSuspendWakeLock.release();
+                        if (message.arg2 == 1) {
+                            mSuspendWakeLock.release();
+                        }
                         setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, true);
                     } else {
                         setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, false);
@@ -4277,6 +4315,7 @@
                         }
 
                         if (mWifiNative.startSupplicant(mP2pSupported)) {
+                            setSupplicantLogLevel();
                             setWifiState(WIFI_STATE_ENABLING);
                             if (DBG) log("Supplicant start successful");
                             mWifiMonitor.startMonitoring(mInterfaceName);
@@ -4303,6 +4342,9 @@
                         transitionTo(mInitialState);
                     }
                     break;
+                case CMD_SET_OPERATIONAL_MODE:
+                    mOperationalMode = message.arg1;
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -4497,7 +4539,7 @@
                     replyToMessage(message, message.what, stats);
                     break;
                 case CMD_RESET_SIM_NETWORKS:
-                    log("resetting EAP-SIM/AKA/AKA' networks since SIM was removed");
+                    log("resetting EAP-SIM/AKA/AKA' networks since SIM was changed");
                     mWifiConfigManager.resetSimNetworks();
                     break;
                 default:
@@ -4794,7 +4836,9 @@
                 case CMD_SET_SUSPEND_OPT_ENABLED:
                     if (message.arg1 == 1) {
                         setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, true);
-                        mSuspendWakeLock.release();
+                        if (message.arg2 == 1) {
+                            mSuspendWakeLock.release();
+                        }
                     } else {
                         setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, false);
                     }
@@ -5598,24 +5642,11 @@
                             && targetWificonfiguration.networkId == networkId
                             && targetWificonfiguration.allowedKeyManagement
                                     .get(WifiConfiguration.KeyMgmt.IEEE8021X)
-                            &&  (eapMethod == WifiEnterpriseConfig.Eap.SIM
-                            || eapMethod == WifiEnterpriseConfig.Eap.AKA
-                            || eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME)) {
-                        TelephonyManager tm = (TelephonyManager)
-                                mContext.getSystemService(Context.TELEPHONY_SERVICE);
-                        if (tm != null) {
-                            String imsi = tm.getSubscriberId();
-                            String mccMnc = "";
-
-                            if (tm.getSimState() == TelephonyManager.SIM_STATE_READY)
-                                 mccMnc = tm.getSimOperator();
-
-                            String identity = buildIdentity(eapMethod, imsi, mccMnc);
-
-                            if (!identity.isEmpty()) {
-                                mWifiNative.simIdentityResponse(networkId, identity);
-                                identitySent = true;
-                            }
+                            && TelephonyUtil.isSimEapMethod(eapMethod)) {
+                        String identity = TelephonyUtil.getSimIdentity(mContext, eapMethod);
+                        if (identity != null) {
+                            mWifiNative.simIdentityResponse(networkId, identity);
+                            identitySent = true;
                         }
                     }
                     if (!identitySent) {
@@ -5657,12 +5688,6 @@
                     replyToMessage(message, message.what,
                             mWifiConfigManager.getMatchingConfig((ScanResult)message.obj));
                     break;
-                /* Do a redundant disconnect without transition */
-                case CMD_DISCONNECT:
-                    mWifiConfigManager.setAndEnableLastSelectedConfiguration
-                            (WifiConfiguration.INVALID_NETWORK_ID);
-                    mWifiNative.disconnect();
-                    break;
                 case CMD_RECONNECT:
                     if (mWifiConnectivityManager != null) {
                         mWifiConnectivityManager.forceConnectivityScan();
@@ -6674,10 +6699,11 @@
                     stopRssiMonitoringOffload();
                     break;
                 case CMD_RESET_SIM_NETWORKS:
-                    if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
+                    if (message.arg1 == 0 // sim was removed
+                            && mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
                         WifiConfiguration config =
                                 mWifiConfigManager.getWifiConfiguration(mLastNetworkId);
-                        if (mWifiConfigManager.isSimConfig(config)) {
+                        if (TelephonyUtil.isSimConfig(config)) {
                             mWifiNative.disconnect();
                             transitionTo(mDisconnectingState);
                         }
@@ -7343,6 +7369,9 @@
                 case CMD_START_SCAN:
                     deferMessage(message);
                     return HANDLED;
+                case CMD_DISCONNECT:
+                    if (DBG) log("Ignore CMD_DISCONNECT when already disconnecting.");
+                    break;
                 case CMD_DISCONNECTING_WATCHDOG_TIMER:
                     if (disconnectingWatchdogCount == message.arg1) {
                         if (DBG) log("disconnecting watchdog! -> disconnect");
@@ -7443,7 +7472,19 @@
                             setAndEnableLastSelectedConfiguration(
                                     WifiConfiguration.INVALID_NETWORK_ID);
                     break;
-                    /* Ignore network disconnect */
+                case CMD_DISCONNECT:
+                    if (SupplicantState.isConnecting(mWifiInfo.getSupplicantState())) {
+                        if (DBG) {
+                            log("CMD_DISCONNECT when supplicant is connecting - do not ignore");
+                        }
+                        mWifiConfigManager.setAndEnableLastSelectedConfiguration(
+                                WifiConfiguration.INVALID_NETWORK_ID);
+                        mWifiNative.disconnect();
+                        break;
+                    }
+                    if (DBG) log("Ignore CMD_DISCONNECT when already disconnected.");
+                    break;
+                /* Ignore network disconnect */
                 case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
                     // Interpret this as an L2 connection failure
                     break;
@@ -7658,7 +7699,7 @@
                 checkAndSetConnectivityInstance();
                 mSoftApManager = mFacade.makeSoftApManager(
                         mContext, getHandler().getLooper(), mWifiNative, mNwService,
-                        mCm, mCountryCode.getCurrentCountryCode(),
+                        mCm, mCountryCode.getCountryCode(),
                         mWifiApConfigStore.getAllowed2GChannel(),
                         new SoftApListener());
                 mSoftApManager.start(config);
@@ -7869,6 +7910,7 @@
         return result;
     }
 
+    // TODO move to TelephonyUtil, same with utilities above
     String getGsmSimAuthResponse(String[] requestData, TelephonyManager tm) {
         StringBuilder sb = new StringBuilder();
         for (String challenge : requestData) {
@@ -7930,6 +7972,7 @@
         return sb.toString();
     }
 
+    // TODO move to TelephonyUtil
     void handleGsmAuthRequest(SimAuthRequestData requestData) {
         if (targetWificonfiguration == null
                 || targetWificonfiguration.networkId == requestData.networkId) {
@@ -7957,6 +8000,7 @@
         }
     }
 
+    // TODO move to TelephonyUtil
     void handle3GAuthRequest(SimAuthRequestData requestData) {
         StringBuilder sb = new StringBuilder();
         byte[] rand = null;
diff --git a/service/java/com/android/server/wifi/configparse/ConfigBuilder.java b/service/java/com/android/server/wifi/configparse/ConfigBuilder.java
index 070c69b..9bcfddb 100644
--- a/service/java/com/android/server/wifi/configparse/ConfigBuilder.java
+++ b/service/java/com/android/server/wifi/configparse/ConfigBuilder.java
@@ -173,26 +173,25 @@
                                                  List<X509Certificate> clientChain, PrivateKey key)
             throws IOException, GeneralSecurityException {
 
-        Credential credential = homeSP.getCredential();
-
         WifiConfiguration config;
 
-        EAP.EAPMethodID eapMethodID = credential.getEAPMethod().getEAPMethodID();
+        EAP.EAPMethodID eapMethodID = homeSP.getCredential().getEAPMethod().getEAPMethodID();
         switch (eapMethodID) {
             case EAP_TTLS:
                 if (key != null || clientChain != null) {
-                    Log.w(TAG, "Client cert and/or key included with EAP-TTLS profile");
+                    Log.w(TAG, "Client cert and/or key unnecessarily included with EAP-TTLS "+
+                            "profile");
                 }
-                config = buildTTLSConfig(homeSP);
+                config = buildTTLSConfig(homeSP, caCert);
                 break;
             case EAP_TLS:
-                config = buildTLSConfig(homeSP, clientChain, key);
+                config = buildTLSConfig(homeSP, clientChain, key, caCert);
                 break;
             case EAP_AKA:
             case EAP_AKAPrim:
             case EAP_SIM:
                 if (key != null || clientChain != null || caCert != null) {
-                    Log.i(TAG, "Client/CA cert and/or key included with " +
+                    Log.i(TAG, "Client/CA cert and/or key unnecessarily included with " +
                             eapMethodID + " profile");
                 }
                 config = buildSIMConfig(homeSP);
@@ -201,11 +200,6 @@
                 throw new IOException("Unsupported EAP Method: " + eapMethodID);
         }
 
-        WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
-
-        enterpriseConfig.setCaCertificate(caCert);
-        enterpriseConfig.setAnonymousIdentity("anonymous@" + credential.getRealm());
-
         return config;
     }
 
@@ -233,7 +227,28 @@
     }
     */
 
-    private static WifiConfiguration buildTTLSConfig(HomeSP homeSP)
+    private static void setAnonymousIdentityToNaiRealm(
+            WifiConfiguration config, Credential credential) {
+        /**
+         * Set WPA supplicant's anonymous identity field to a string containing the NAI realm, so
+         * that this value will be sent to the EAP server as part of the EAP-Response/ Identity
+         * packet. WPA supplicant will reset this field after using it for the EAP-Response/Identity
+         * packet, and revert to using the (real) identity field for subsequent transactions that
+         * request an identity (e.g. in EAP-TTLS).
+         *
+         * This NAI realm value (the portion of the identity after the '@') is used to tell the
+         * AAA server which AAA/H to forward packets to. The hardcoded username, "anonymous", is a
+         * placeholder that is not used--it is set to this value by convention. See Section 5.1 of
+         * RFC3748 for more details.
+         *
+         * NOTE: we do not set this value for EAP-SIM/AKA/AKA', since the EAP server expects the
+         * EAP-Response/Identity packet to contain an actual, IMSI-based identity, in order to
+         * identify the device.
+         */
+        config.enterpriseConfig.setAnonymousIdentity("anonymous@" + credential.getRealm());
+    }
+
+    private static WifiConfiguration buildTTLSConfig(HomeSP homeSP, X509Certificate caCert)
             throws IOException {
         Credential credential = homeSP.getCredential();
 
@@ -255,13 +270,17 @@
         enterpriseConfig.setPhase2Method(remapInnerMethod(ttlsParam.getType()));
         enterpriseConfig.setIdentity(credential.getUserName());
         enterpriseConfig.setPassword(credential.getPassword());
+        enterpriseConfig.setCaCertificate(caCert);
+
+        setAnonymousIdentityToNaiRealm(config, credential);
 
         return config;
     }
 
     private static WifiConfiguration buildTLSConfig(HomeSP homeSP,
                                                     List<X509Certificate> clientChain,
-                                                    PrivateKey clientKey)
+                                                    PrivateKey clientKey,
+                                                    X509Certificate caCert)
             throws IOException, GeneralSecurityException {
 
         Credential credential = homeSP.getCredential();
@@ -296,6 +315,9 @@
         WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
         enterpriseConfig.setClientCertificateAlias(alias);
         enterpriseConfig.setClientKeyEntry(clientKey, clientCertificate);
+        enterpriseConfig.setCaCertificate(caCert);
+
+        setAnonymousIdentityToNaiRealm(config, credential);
 
         return config;
     }
diff --git a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index ae75a37..c1d9445 100644
--- a/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/service/java/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -1359,8 +1359,20 @@
                     WifiP2pDevice owner = group.getOwner();
 
                     if (owner == null) {
-                        loge("Ignored invitation from null owner");
-                        break;
+                        int id = group.getNetworkId();
+                        if (id < 0) {
+                            loge("Ignored invitation from null owner");
+                            break;
+                        }
+
+                        String addr = mGroups.getOwnerAddr(id);
+                        if (addr != null) {
+                            group.setOwner(new WifiP2pDevice(addr));
+                            owner = group.getOwner();
+                        } else {
+                            loge("Ignored invitation from null owner");
+                            break;
+                        }
                     }
 
                     config = new WifiP2pConfig();
diff --git a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index d279482..6ae2237 100644
--- a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -76,7 +76,7 @@
     private static final int MIN_PERIOD_PER_CHANNEL_MS = 200;               // DFS needs 120 ms
     private static final int UNKNOWN_PID = -1;
 
-    private final LocalLog mLocalLog = new LocalLog(1024);
+    private final LocalLog mLocalLog = new LocalLog(512);
 
     private void localLog(String message) {
         mLocalLog.log(message);
@@ -136,7 +136,8 @@
                 case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
                     ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo);
                     if (client != null) {
-                        logw("duplicate client connection: " + msg.sendingUid);
+                        logw("duplicate client connection: " + msg.sendingUid + ", messenger="
+                                + msg.replyTo);
                         client.mChannel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
                                 AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
                         return;
@@ -151,7 +152,7 @@
                     ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
                             AsyncChannel.STATUS_SUCCESSFUL);
 
-                    if (DBG) Log.d(TAG, "client connected: " + client);
+                    localLog("client connected: " + client);
                     return;
                 }
                 case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
@@ -164,9 +165,7 @@
                 case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
                     ExternalClientInfo client = (ExternalClientInfo) mClients.get(msg.replyTo);
                     if (client != null) {
-                        if (DBG) {
-                            Log.d(TAG, "client disconnected: " + client + ", reason: " + msg.arg1);
-                        }
+                        localLog("client disconnected: " + client + ", reason: " + msg.arg1);
                         client.cleanup();
                     }
                     return;
@@ -215,6 +214,15 @@
                 case WifiScanner.CMD_STOP_TRACKING_CHANGE:
                     mWifiChangeStateMachine.sendMessage(Message.obtain(msg));
                     break;
+                case WifiScanner.CMD_REGISTER_SCAN_LISTENER:
+                    logScanRequest("registerScanListener", ci, msg.arg2, null, null, null);
+                    mSingleScanListeners.addRequest(ci, msg.arg2, null, null);
+                    replySucceeded(msg);
+                    break;
+                case WifiScanner.CMD_DEREGISTER_SCAN_LISTENER:
+                    logScanRequest("deregisterScanListener", ci, msg.arg2, null, null, null);
+                    mSingleScanListeners.removeRequest(ci, msg.arg2);
+                    break;
                 default:
                     replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "Invalid request");
                     break;
@@ -243,6 +251,8 @@
     private final WifiScannerImpl.WifiScannerImplFactory mScannerImplFactory;
     private final ArrayMap<Messenger, ClientInfo> mClients;
 
+    private final RequestList<Void> mSingleScanListeners = new RequestList<>();
+
     private ChannelHelper mChannelHelper;
     private BackgroundScanScheduler mBackgroundScheduler;
     private WifiNative.ScanSettings mPreviousSchedule;
@@ -419,6 +429,7 @@
         private final IdleState  mIdleState  = new IdleState();
         private final ScanningState  mScanningState  = new ScanningState();
 
+        private WifiNative.ScanSettings mActiveScanSettings = null;
         private RequestList<ScanSettings> mActiveScans = new RequestList<>();
         private RequestList<ScanSettings> mPendingScans = new RequestList<>();
 
@@ -547,12 +558,24 @@
                                 scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY);
                         WorkSource workSource =
                                 scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY);
-                        if (validateAndAddToScanQueue(ci, handler, scanSettings, workSource)) {
+                        if (validateScanRequest(ci, handler, scanSettings, workSource)) {
+                            logScanRequest("addSingleScanRequest", ci, handler, workSource,
+                                    scanSettings, null);
                             replySucceeded(msg);
+
+                            // If there is an active scan that will fulfill the scan request then
+                            // mark this request as an active scan, otherwise mark it pending.
                             // If were not currently scanning then try to start a scan. Otherwise
                             // this scan will be scheduled when transitioning back to IdleState
                             // after finishing the current scan.
-                            if (getCurrentState() != mScanningState) {
+                            if (getCurrentState() == mScanningState) {
+                                if (activeScanSatisfies(scanSettings)) {
+                                    mActiveScans.addRequest(ci, handler, workSource, scanSettings);
+                                } else {
+                                    mPendingScans.addRequest(ci, handler, workSource, scanSettings);
+                                }
+                            } else {
+                                mPendingScans.addRequest(ci, handler, workSource, scanSettings);
                                 tryToStartNewScan();
                             }
                         } else {
@@ -598,6 +621,7 @@
 
             @Override
             public void exit() {
+                mActiveScanSettings = null;
                 try {
                     mBatteryStats.noteWifiScanStoppedFromSource(mScanWorkSource);
                 } catch (RemoteException e) {
@@ -639,7 +663,7 @@
             }
         }
 
-        boolean validateAndAddToScanQueue(ClientInfo ci, int handler, ScanSettings settings,
+        boolean validateScanRequest(ClientInfo ci, int handler, ScanSettings settings,
                 WorkSource workSource) {
             if (ci == null) {
                 Log.d(TAG, "Failing single scan request ClientInfo not found " + handler);
@@ -651,8 +675,46 @@
                     return false;
                 }
             }
-            logScanRequest("addSingleScanRequest", ci, handler, workSource, settings, null);
-            mPendingScans.addRequest(ci, handler, workSource, settings);
+            return true;
+        }
+
+        boolean activeScanSatisfies(ScanSettings settings) {
+            if (mActiveScanSettings == null) {
+                return false;
+            }
+
+            // there is always one bucket for a single scan
+            WifiNative.BucketSettings activeBucket = mActiveScanSettings.buckets[0];
+
+            // validate that all requested channels are being scanned
+            ChannelCollection activeChannels = mChannelHelper.createChannelCollection();
+            activeChannels.addChannels(activeBucket);
+            if (!activeChannels.containsSettings(settings)) {
+                return false;
+            }
+
+            // if the request is for a full scan, but there is no ongoing full scan
+            if ((settings.reportEvents & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0
+                    && (activeBucket.report_events & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT)
+                    == 0) {
+                return false;
+            }
+
+            if (settings.hiddenNetworkIds != null) {
+                if (mActiveScanSettings.hiddenNetworkIds == null) {
+                    return false;
+                }
+                Set<Integer> activeHiddenNetworkIds = new HashSet<>();
+                for (int id : mActiveScanSettings.hiddenNetworkIds) {
+                    activeHiddenNetworkIds.add(id);
+                }
+                for (int id : settings.hiddenNetworkIds) {
+                    if (!activeHiddenNetworkIds.contains(id)) {
+                        return false;
+                    }
+                }
+            }
+
             return true;
         }
 
@@ -711,6 +773,8 @@
 
             settings.buckets = new WifiNative.BucketSettings[] {bucketSettings};
             if (mScannerImpl.startSingleScan(settings, this)) {
+                // store the active scan settings
+                mActiveScanSettings = settings;
                 // swap pending and active scan requests
                 RequestList<ScanSettings> tmp = mActiveScans;
                 mActiveScans = mPendingScans;
@@ -745,6 +809,10 @@
                     entry.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, result);
                 }
             }
+
+            for (RequestInfo<Void> entry : mSingleScanListeners) {
+                entry.reportEvent(WifiScanner.CMD_FULL_SCAN_RESULT, 0, result);
+            }
         }
 
         void reportScanResults(ScanData results) {
@@ -755,18 +823,26 @@
                     mWifiMetrics.incrementEmptyScanResultCount();
                 }
             }
+            ScanData[] allResults = new ScanData[] {results};
             for (RequestInfo<ScanSettings> entry : mActiveScans) {
-                ScanData[] resultsArray = new ScanData[] {results};
                 ScanData[] resultsToDeliver = ScanScheduleUtil.filterResultsForSettings(
-                        mChannelHelper, resultsArray, entry.settings, -1);
-                WifiScanner.ParcelableScanData parcelableScanData =
+                        mChannelHelper, allResults, entry.settings, -1);
+                WifiScanner.ParcelableScanData parcelableResultsToDeliver =
                         new WifiScanner.ParcelableScanData(resultsToDeliver);
                 logCallback("singleScanResults",  entry.clientInfo, entry.handlerId,
                         describeForLog(resultsToDeliver));
-                entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableScanData);
+                entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableResultsToDeliver);
                 // make sure the handler is removed
                 entry.reportEvent(WifiScanner.CMD_SINGLE_SCAN_COMPLETED, 0, null);
             }
+
+            WifiScanner.ParcelableScanData parcelableAllResults =
+                    new WifiScanner.ParcelableScanData(allResults);
+            for (RequestInfo<Void> entry : mSingleScanListeners) {
+                logCallback("singleScanResults",  entry.clientInfo, entry.handlerId,
+                        describeForLog(allResults));
+                entry.reportEvent(WifiScanner.CMD_SCAN_RESULT, 0, parcelableAllResults);
+            }
         }
     }
 
@@ -976,8 +1052,11 @@
                         replySucceeded(msg);
                         break;
                     case WifiScanner.CMD_SET_HOTLIST:
-                        addHotlist(ci, msg.arg2, (WifiScanner.HotlistSettings) msg.obj);
-                        replySucceeded(msg);
+                        if (addHotlist(ci, msg.arg2, (WifiScanner.HotlistSettings) msg.obj)) {
+                            replySucceeded(msg);
+                        } else {
+                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
+                        }
                         break;
                     case WifiScanner.CMD_RESET_HOTLIST:
                         removeHotlist(ci, msg.arg2);
@@ -1117,13 +1196,13 @@
                 if (DBG) Log.d(TAG, "scan stopped");
                 return true;
             } else {
-                Log.d(TAG, "starting scan: "
+                localLog("starting scan: "
                         + "base period=" + schedule.base_period_ms
                         + ", max ap per scan=" + schedule.max_ap_per_scan
                         + ", batched scans=" + schedule.report_threshold_num_scans);
                 for (int b = 0; b < schedule.num_buckets; b++) {
                     WifiNative.BucketSettings bucket = schedule.buckets[b];
-                    Log.d(TAG, "bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)"
+                    localLog("bucket " + bucket.bucket + " (" + bucket.period_ms + "ms)"
                             + "[" + bucket.report_events + "]: "
                             + ChannelHelper.toString(bucket));
                 }
@@ -1214,14 +1293,22 @@
             mActiveBackgroundScans.clear();
         }
 
-        private void addHotlist(ClientInfo ci, int handler, WifiScanner.HotlistSettings settings) {
+        private boolean addHotlist(ClientInfo ci, int handler,
+                WifiScanner.HotlistSettings settings) {
+            if (ci == null) {
+                Log.d(TAG, "Failing hotlist request ClientInfo not found " + handler);
+                return false;
+            }
             mActiveHotlistSettings.addRequest(ci, handler, null, settings);
             resetHotlist();
+            return true;
         }
 
         private void removeHotlist(ClientInfo ci, int handler) {
-            mActiveHotlistSettings.removeRequest(ci, handler);
-            resetHotlist();
+            if (ci != null) {
+                mActiveHotlistSettings.removeRequest(ci, handler);
+                resetHotlist();
+            }
         }
 
         private void resetHotlist() {
@@ -1820,6 +1907,7 @@
         }
 
         public void cleanup() {
+            mSingleScanListeners.removeAllForClient(this);
             mSingleScanStateMachine.removeSingleScanRequests(this);
             mBackgroundScanStateMachine.removeBackgroundScanSettings(this);
             mBackgroundScanStateMachine.removeHotlistSettings(this);
@@ -1891,7 +1979,7 @@
 
         @Override
         public String toString() {
-            return "ClientInfo[uid=" + mUid + "]";
+            return "ClientInfo[uid=" + mUid + "," + mMessenger + "]";
         }
     }
 
@@ -1988,6 +2076,11 @@
         public void sendRequestToClientHandler(int what) {
             sendRequestToClientHandler(what, null, null);
         }
+
+        @Override
+        public String toString() {
+            return "InternalClientInfo[]";
+        }
     }
 
     void replySucceeded(Message msg) {
@@ -2080,9 +2173,12 @@
                 ClientInfo ci = mClients.get(msg.replyTo);
                 switch (msg.what) {
                     case WifiScanner.CMD_START_TRACKING_CHANGE:
-                        addWifiChangeHandler(ci, msg.arg2);
-                        replySucceeded(msg);
-                        transitionTo(mMovingState);
+                        if (addWifiChangeHandler(ci, msg.arg2)) {
+                            replySucceeded(msg);
+                            transitionTo(mMovingState);
+                        } else {
+                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
+                        }
                         break;
                     case WifiScanner.CMD_STOP_TRACKING_CHANGE:
                         // nothing to do
@@ -2114,8 +2210,11 @@
                 ClientInfo ci = mClients.get(msg.replyTo);
                 switch (msg.what) {
                     case WifiScanner.CMD_START_TRACKING_CHANGE:
-                        addWifiChangeHandler(ci, msg.arg2);
-                        replySucceeded(msg);
+                        if (addWifiChangeHandler(ci, msg.arg2)) {
+                            replySucceeded(msg);
+                        } else {
+                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
+                        }
                         break;
                     case WifiScanner.CMD_STOP_TRACKING_CHANGE:
                         removeWifiChangeHandler(ci, msg.arg2);
@@ -2167,8 +2266,11 @@
                 ClientInfo ci = mClients.get(msg.replyTo);
                 switch (msg.what) {
                     case WifiScanner.CMD_START_TRACKING_CHANGE:
-                        addWifiChangeHandler(ci, msg.arg2);
-                        replySucceeded(msg);
+                        if (addWifiChangeHandler(ci, msg.arg2)) {
+                            replySucceeded(msg);
+                        } else {
+                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
+                        }
                         break;
                     case WifiScanner.CMD_STOP_TRACKING_CHANGE:
                         removeWifiChangeHandler(ci, msg.arg2);
@@ -2394,7 +2496,11 @@
             }
         }
 
-        private void addWifiChangeHandler(ClientInfo ci, int handler) {
+        private boolean addWifiChangeHandler(ClientInfo ci, int handler) {
+            if (ci == null) {
+                Log.d(TAG, "Failing wifi change request ClientInfo not found " + handler);
+                return false;
+            }
             mActiveWifiChangeHandlers.add(Pair.create(ci, handler));
             // Add an internal client to make background scan requests.
             if (mInternalClientInfo == null) {
@@ -2402,11 +2508,14 @@
                         new InternalClientInfo(ci.getUid(), new Messenger(this.getHandler()));
                 mInternalClientInfo.register();
             }
+            return true;
         }
 
         private void removeWifiChangeHandler(ClientInfo ci, int handler) {
-            mActiveWifiChangeHandlers.remove(Pair.create(ci, handler));
-            untrackSignificantWifiChangeOnEmpty();
+            if (ci != null) {
+                mActiveWifiChangeHandlers.remove(Pair.create(ci, handler));
+                untrackSignificantWifiChangeOnEmpty();
+            }
         }
 
         private void untrackSignificantWifiChangeOnEmpty() {
@@ -2520,7 +2629,7 @@
         StringBuilder sb = new StringBuilder();
         sb.append(request)
                 .append(": ")
-                .append(ci.toString())
+                .append((ci == null) ? "ClientInfo[unknown]" : ci.toString())
                 .append(",Id=")
                 .append(id);
         if (workSource != null) {
@@ -2541,7 +2650,7 @@
         StringBuilder sb = new StringBuilder();
         sb.append(callback)
                 .append(": ")
-                .append(ci.toString())
+                .append((ci == null) ? "ClientInfo[unknown]" : ci.toString())
                 .append(",Id=")
                 .append(id);
         if (extra != null) {
diff --git a/service/java/com/android/server/wifi/util/TelephonyUtil.java b/service/java/com/android/server/wifi/util/TelephonyUtil.java
new file mode 100644
index 0000000..3d7ce45
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/TelephonyUtil.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiEnterpriseConfig;
+import android.telephony.TelephonyManager;
+
+/**
+ * Utilities for the Wifi Service to interact with telephony.
+ */
+public class TelephonyUtil {
+
+    /**
+     * Get the identity for the current SIM or null if the sim is not available
+     */
+    public static String getSimIdentity(Context context, int eapMethod) {
+        TelephonyManager tm = TelephonyManager.from(context);
+        if (tm != null) {
+            String imsi = tm.getSubscriberId();
+            String mccMnc = "";
+
+            if (tm.getSimState() == TelephonyManager.SIM_STATE_READY) {
+                mccMnc = tm.getSimOperator();
+            }
+
+            return buildIdentity(eapMethod, imsi, mccMnc);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * create Permanent Identity base on IMSI,
+     *
+     * rfc4186 & rfc4187:
+     * identity = usernam@realm
+     * with username = prefix | IMSI
+     * and realm is derived MMC/MNC tuple according 3GGP spec(TS23.003)
+     */
+    private static String buildIdentity(int eapMethod, String imsi, String mccMnc) {
+        if (imsi == null || imsi.isEmpty()) {
+            return null;
+        }
+
+        String prefix;
+        if (eapMethod == WifiEnterpriseConfig.Eap.SIM) {
+            prefix = "1";
+        } else if (eapMethod == WifiEnterpriseConfig.Eap.AKA) {
+            prefix = "0";
+        } else if (eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME) {
+            prefix = "6";
+        } else {  // not a valide EapMethod
+            return null;
+        }
+
+        /* extract mcc & mnc from mccMnc */
+        String mcc;
+        String mnc;
+        if (mccMnc != null && !mccMnc.isEmpty()) {
+            mcc = mccMnc.substring(0, 3);
+            mnc = mccMnc.substring(3);
+            if (mnc.length() == 2) {
+                mnc = "0" + mnc;
+            }
+        } else {
+            // extract mcc & mnc from IMSI, assume mnc size is 3
+            mcc = imsi.substring(0, 3);
+            mnc = imsi.substring(3, 6);
+        }
+
+        return prefix + imsi + "@wlan.mnc" + mnc + ".mcc" + mcc + ".3gppnetwork.org";
+    }
+
+    /**
+     * Checks if the network is a sim config.
+     *
+     * @param config Config corresponding to the network.
+     * @return true if it is a sim config, false otherwise.
+     */
+    public static boolean isSimConfig(WifiConfiguration config) {
+        if (config == null || config.enterpriseConfig == null) {
+            return false;
+        }
+
+        return isSimEapMethod(config.enterpriseConfig.getEapMethod());
+    }
+
+    /**
+     * Checks if the network is a sim config.
+     *
+     * @param method
+     * @return true if it is a sim config, false otherwise.
+     */
+    public static boolean isSimEapMethod(int eapMethod) {
+        return eapMethod == WifiEnterpriseConfig.Eap.SIM
+                || eapMethod == WifiEnterpriseConfig.Eap.AKA
+                || eapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME;
+    }
+}
diff --git a/service/proto/wifi.proto b/service/proto/wifi.proto
index 43a4166..3b0e854 100644
--- a/service/proto/wifi.proto
+++ b/service/proto/wifi.proto
@@ -186,6 +186,9 @@
 
   // The time duration represented by this wifi log, from start to end of capture
   optional int32 record_duration_sec = 34;
+
+  // Counts the occurrences of each individual RSSI poll level
+  repeated RssiPollCount rssi_poll_rssi_count = 35;
 }
 
 // Information that gets logged for every WiFi connection.
@@ -337,3 +340,12 @@
   // Has bug report been taken.
   optional bool automatic_bug_report_taken = 9;
 }
+
+// Number of occurrences of a specific RSSI poll rssi value
+message RssiPollCount {
+  // RSSI
+  optional int32 rssi = 1;
+
+  // Number of RSSI polls with 'rssi'
+  optional int32 count = 2;
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
index c091517..b91df5a 100644
--- a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -20,7 +20,6 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -30,7 +29,6 @@
 import android.content.IntentFilter;
 import android.net.ConnectivityManager;
 import android.net.InterfaceConfiguration;
-import android.net.LinkAddress;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.INetworkManagementService;
@@ -69,12 +67,6 @@
     @Mock SoftApManager.Listener mListener;
     @Mock InterfaceConfiguration mInterfaceConfiguration;
 
-    /**
-     * Internal BroadcastReceiver that SoftApManager uses to listen for tethering
-     * events from ConnectivityManager.
-     */
-    BroadcastReceiver mBroadcastReceiver;
-
     SoftApManager mSoftApManager;
 
     /** Sets up test. */
@@ -89,19 +81,12 @@
         when(mConnectivityManager.getTetherableWifiRegexs())
                 .thenReturn(AVAILABLE_DEVICES);
 
-        mSoftApManager = new SoftApManager(mContext,
-                                           mLooper.getLooper(),
+        mSoftApManager = new SoftApManager(mLooper.getLooper(),
                                            mWifiNative,
                                            mNmService,
-                                           mConnectivityManager,
                                            TEST_COUNTRY_CODE,
                                            mAllowed2GChannels,
                                            mListener);
-        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mContext).registerReceiver(
-                broadcastReceiverCaptor.capture(), any(IntentFilter.class));
-        mBroadcastReceiver = broadcastReceiverCaptor.getValue();
 
         mLooper.dispatchAll();
     }
@@ -119,35 +104,6 @@
                 WifiManager.WIFI_AP_STATE_FAILED, WifiManager.SAP_START_FAILURE_GENERAL);
     }
 
-    /** Tests the handling of timeout after tethering is started. */
-    @Test
-    public void tetheringTimedOut() throws Exception {
-        startSoftApAndVerifyEnabled();
-        announceAvailableForTethering();
-        verifyTetheringRequested();
-
-        InOrder order = inOrder(mListener);
-
-        /* Move the time forward to simulate notification timeout. */
-        mLooper.moveTimeForward(5000);
-        mLooper.dispatchAll();
-
-        /* Verify soft ap is disabled. */
-        verify(mNmService).stopAccessPoint(eq(TEST_INTERFACE_NAME));
-        order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLING, 0);
-        order.verify(mListener).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
-    }
-
-    /** Tests the handling of tethered notification after tethering is started. */
-    @Test
-    public void tetherCompleted() throws Exception {
-        startSoftApAndVerifyEnabled();
-        announceAvailableForTethering();
-        verifyTetheringRequested();
-        announceTethered();
-        verifySoftApNotDisabled();
-    }
-
     /** Tests the handling of stop command when soft AP is not started. */
     @Test
     public void stopWhenNotStarted() throws Exception {
@@ -198,31 +154,4 @@
         verify(mListener, never()).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLING, 0);
         verify(mListener, never()).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0);
     }
-
-    /** Sends a broadcast intent indicating that the interface is available for tethering. */
-    protected void announceAvailableForTethering() throws Exception {
-        when(mConnectivityManager.tether(TEST_INTERFACE_NAME))
-                .thenReturn(ConnectivityManager.TETHER_ERROR_NO_ERROR);
-        ArrayList<String> availableList =
-                new ArrayList<String>(Arrays.asList(AVAILABLE_DEVICES));
-        TestUtil.sendTetherStateChanged(
-                mBroadcastReceiver, mContext, availableList, new ArrayList<String>());
-        mLooper.dispatchAll();
-    }
-
-    /** Verifies that tethering was requested. */
-    protected void verifyTetheringRequested() throws Exception {
-        verify(mInterfaceConfiguration).setLinkAddress(any(LinkAddress.class));
-        verify(mInterfaceConfiguration).setInterfaceUp();
-        verify(mNmService).setInterfaceConfig(eq(TEST_INTERFACE_NAME), eq(mInterfaceConfiguration));
-    }
-
-    /** Sends a broadcast intent indicating that the interface is tethered. */
-    protected void announceTethered() throws Exception {
-        ArrayList<String> deviceList =
-                new ArrayList<String>(Arrays.asList(AVAILABLE_DEVICES));
-        TestUtil.sendTetherStateChanged(
-                mBroadcastReceiver, mContext, deviceList, deviceList);
-        mLooper.dispatchAll();
-    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
index 145c840..3993fe5 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+import android.content.Context;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.Before;
@@ -155,6 +156,7 @@
             + "}\n";
 
     @Mock private WifiNative mWifiNative;
+    @Mock private Context mContext;
     private MockKeyStore mMockKeyStore;
     private WifiConfigStore mWifiConfigStore;
 
@@ -163,8 +165,8 @@
         MockitoAnnotations.initMocks(this);
 
         mMockKeyStore = new MockKeyStore();
-        mWifiConfigStore = new WifiConfigStore(mWifiNative, mMockKeyStore.createMock(), null,
-                false, true);
+        mWifiConfigStore = new WifiConfigStore(mContext, mWifiNative, mMockKeyStore.createMock(),
+                null, false, true);
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index 2434c2d..022997d 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.wifi;
 
 import static com.android.server.wifi.WifiConfigurationTestUtil.generateWifiConfig;
+import static com.android.server.wifi.WifiStateMachine.WIFI_WORK_SOURCE;
 
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
@@ -45,6 +46,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
@@ -128,6 +130,10 @@
 
     WifiScanner mockWifiScanner() {
         WifiScanner scanner = mock(WifiScanner.class);
+        ArgumentCaptor<ScanListener> allSingleScanListenerCaptor =
+                ArgumentCaptor.forClass(ScanListener.class);
+
+        doNothing().when(scanner).registerScanListener(allSingleScanListenerCaptor.capture());
 
         // dummy scan results. QNS PeriodicScanListener bulids scanDetails from
         // the fullScanResult and doesn't really use results
@@ -144,6 +150,7 @@
                 public void answer(ScanSettings settings, ScanListener listener,
                         WorkSource workSource) throws Exception {
                     listener.onResults(scanDatas);
+                    allSingleScanListenerCaptor.getValue().onResults(scanDatas);
                 }}).when(scanner).startScan(anyObject(), anyObject(), anyObject());
 
         // This unfortunately needs to be a somewhat valid scan result, otherwise
@@ -215,6 +222,7 @@
         WifiConfigManager wifiConfigManager = mock(WifiConfigManager.class);
 
         when(wifiConfigManager.getWifiConfiguration(anyInt())).thenReturn(null);
+        when(wifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(true);
         wifiConfigManager.mThresholdSaturatedRssi24 = new AtomicInteger(
                 WifiQualifiedNetworkSelector.RSSI_SATURATION_2G_BAND);
         wifiConfigManager.mCurrentNetworkBoost = new AtomicInteger(
@@ -321,6 +329,30 @@
     }
 
     /**
+     *  Screen turned on while WiFi in connected state but
+     *  auto roaming is disabled.
+     *
+     * Expected behavior: WifiConnectivityManager doesn't invoke
+     * WifiStateMachine.autoConnectToNetwork() because roaming
+     * is turned off.
+     */
+    @Test
+    public void turnScreenOnWhenWifiInConnectedStateRoamingDisabled() {
+        // Set WiFi to connected state
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_CONNECTED);
+
+        // Turn off auto roaming
+        when(mWifiConfigManager.getEnableAutoJoinWhenAssociated()).thenReturn(false);
+
+        // Set screen to on
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        verify(mWifiStateMachine, times(0)).autoConnectToNetwork(
+                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+    }
+
+    /**
      * Multiple back to back connection attempts within the rate interval should be rate limited.
      *
      * Expected behavior: WifiConnectivityManager calls WifiStateMachine.autoConnectToNetwork()
@@ -854,4 +886,62 @@
 
         verify(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
     }
+
+    /**
+     *  Verify that we retry connectivity scan up to MAX_SCAN_RESTART_ALLOWED times
+     *  when Wifi somehow gets into a bad state and fails to scan.
+     *
+     * Expected behavior: WifiConnectivityManager schedules connectivity scan
+     * MAX_SCAN_RESTART_ALLOWED times.
+     */
+    @Test
+    public void checkMaximumScanRetry() {
+        // Set screen to ON
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        doAnswer(new AnswerWithArguments() {
+            public void answer(ScanSettings settings, ScanListener listener,
+                    WorkSource workSource) throws Exception {
+                listener.onFailure(-1, "ScanFailure");
+            }}).when(mWifiScanner).startScan(anyObject(), anyObject(), anyObject());
+
+        // Set WiFi to disconnected state to trigger the single scan based periodic scan
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
+
+        // Fire the alarm timer 2x timers
+        for (int i = 0; i < (WifiConnectivityManager.MAX_SCAN_RESTART_ALLOWED * 2); i++) {
+            mAlarmManager.dispatch(WifiConnectivityManager.RESTART_SINGLE_SCAN_TIMER_TAG);
+            mLooper.dispatchAll();
+        }
+
+        // Verify that the connectivity scan has been retried for MAX_SCAN_RESTART_ALLOWED
+        // times. Note, WifiScanner.startScan() is invoked MAX_SCAN_RESTART_ALLOWED + 1 times.
+        // The very first scan is the initial one, and the other MAX_SCAN_RESTART_ALLOWED
+        // are the retrial ones.
+        verify(mWifiScanner, times(WifiConnectivityManager.MAX_SCAN_RESTART_ALLOWED + 1)).startScan(
+                anyObject(), anyObject(), anyObject());
+    }
+
+    /**
+     * Listen to scan results not requested by WifiConnectivityManager and
+     * act on them.
+     *
+     * Expected behavior: WifiConnectivityManager calls
+     * WifiStateMachine.autoConnectToNetwork() with the
+     * expected candidate network ID and BSSID.
+     */
+    @Test
+    public void listenToAllSingleScanResults() {
+        ScanSettings settings = new ScanSettings();
+        ScanListener scanListener = mock(ScanListener.class);
+
+        // Request a single scan outside of WifiConnectivityManager.
+        mWifiScanner.startScan(settings, scanListener, WIFI_WORK_SOURCE);
+
+        // Verify that WCM receives the scan results and initiates a connection
+        // to the network.
+        verify(mWifiStateMachine).autoConnectToNetwork(
+                CANDIDATE_NETWORK_ID, CANDIDATE_BSSID);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
index 44a710a..c187faf 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
@@ -16,9 +16,13 @@
 
 package com.android.server.wifi;
 
+import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
+
 import static com.android.server.wifi.WifiController.CMD_AP_STOPPED;
+import static com.android.server.wifi.WifiController.CMD_DEVICE_IDLE;
 import static com.android.server.wifi.WifiController.CMD_EMERGENCY_CALL_STATE_CHANGED;
 import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_RESTART_WIFI;
 import static com.android.server.wifi.WifiController.CMD_SET_AP;
 import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
 
@@ -28,6 +32,7 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.os.WorkSource;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
@@ -79,7 +84,7 @@
     @Mock FrameworkFacade mFacade;
     @Mock WifiSettingsStore mSettingsStore;
     @Mock WifiStateMachine mWifiStateMachine;
-    @Mock WifiServiceImpl.LockList mLockList;
+    @Mock WifiLockManager mWifiLockManager;
 
     WifiController mWifiController;
 
@@ -94,7 +99,7 @@
         when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
 
         mWifiController = new WifiController(mContext, mWifiStateMachine,
-                mSettingsStore, mLockList, mLooper.getLooper(), mFacade);
+                mSettingsStore, mWifiLockManager, mLooper.getLooper(), mFacade);
 
         mWifiController.start();
         mLooper.dispatchAll();
@@ -265,4 +270,184 @@
         inOrder.verify(mWifiStateMachine).setDriverStart(true);
         assertEquals("DeviceActiveState", getCurrentState().getName());
     }
+
+    /**
+     * When AP mode is enabled and wifi is toggled on, we should transition to
+     * DeviceActiveState after the AP is disabled.
+     * Enter DeviceActiveState, activate AP mode, toggle WiFi.
+     * <p>
+     * Expected: AP should successfully start and exit, then return to DeviceActiveState.
+     */
+    @Test
+    public void testReturnToDeviceActiveStateAfterWifiEnabledShutdown() throws Exception {
+        enableWifi();
+        assertEquals("DeviceActiveState", getCurrentState().getName());
+
+        mWifiController.obtainMessage(CMD_SET_AP, 1, 0).sendToTarget();
+        mLooper.dispatchAll();
+        assertEquals("ApEnabledState", getCurrentState().getName());
+
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(true);
+        mWifiController.obtainMessage(CMD_WIFI_TOGGLED).sendToTarget();
+        mWifiController.obtainMessage(CMD_AP_STOPPED).sendToTarget();
+        mLooper.dispatchAll();
+
+        InOrder inOrder = inOrder(mWifiStateMachine);
+        inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
+        inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        inOrder.verify(mWifiStateMachine).setDriverStart(true);
+        assertEquals("DeviceActiveState", getCurrentState().getName());
+    }
+
+    /**
+     * When the wifi device is idle, AP mode is enabled and disabled
+     * we should return to the appropriate Idle state.
+     * Enter DeviceActiveState, indicate idle device, activate AP mode, disable AP mode.
+     * <p>
+     * Expected: AP should successfully start and exit, then return to a device idle state.
+     */
+    @Test
+    public void testReturnToDeviceIdleStateAfterAPModeShutdown() throws Exception {
+        enableWifi();
+        assertEquals("DeviceActiveState", getCurrentState().getName());
+
+        // make sure mDeviceIdle is set to true
+        when(mWifiLockManager.getStrongestLockMode()).thenReturn(WIFI_MODE_FULL);
+        when(mWifiLockManager.createMergedWorkSource()).thenReturn(new WorkSource());
+        mWifiController.sendMessage(CMD_DEVICE_IDLE);
+        mLooper.dispatchAll();
+        assertEquals("FullLockHeldState", getCurrentState().getName());
+
+        mWifiController.obtainMessage(CMD_SET_AP, 1, 0).sendToTarget();
+        mLooper.dispatchAll();
+        assertEquals("ApEnabledState", getCurrentState().getName());
+
+        when(mSettingsStore.getWifiSavedState()).thenReturn(1);
+        mWifiController.obtainMessage(CMD_AP_STOPPED).sendToTarget();
+        mLooper.dispatchAll();
+
+        InOrder inOrder = inOrder(mWifiStateMachine);
+        inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
+        inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        inOrder.verify(mWifiStateMachine).setDriverStart(true);
+        assertEquals("FullLockHeldState", getCurrentState().getName());
+    }
+
+    /**
+     * The command to trigger a WiFi reset should not trigger any action by WifiController if we
+     * are not in STA mode.
+     * WiFi is not in connect mode, so any calls to reset the wifi stack due to connection failures
+     * should be ignored.
+     * Create and start WifiController in ApStaDisabledState, send command to restart WiFi
+     * <p>
+     * Expected: WiFiController should not call WifiStateMachine.setSupplicantRunning(false)
+     */
+    @Test
+    public void testRestartWifiStackInApStaDisabledState() throws Exception {
+        // Start a new WifiController with wifi disabled
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(false);
+
+        when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
+
+        mWifiController = new WifiController(mContext, mWifiStateMachine,
+                mSettingsStore, mWifiLockManager, mLooper.getLooper(), mFacade);
+
+        mWifiController.start();
+        mLooper.dispatchAll();
+
+        reset(mWifiStateMachine);
+        assertEquals("ApStaDisabledState", getCurrentState().getName());
+        mWifiController.sendMessage(CMD_RESTART_WIFI);
+        mLooper.dispatchAll();
+        verifyZeroInteractions(mWifiStateMachine);
+    }
+
+    /**
+     * The command to trigger a WiFi reset should not trigger any action by WifiController if we
+     * are not in STA mode, even if scans are allowed.
+     * WiFi is not in connect mode, so any calls to reset the wifi stack due to connection failures
+     * should be ignored.
+     * Create and start WifiController in StaDisablediWithScanState, send command to restart WiFi
+     * <p>
+     * Expected: WiFiController should not call WifiStateMachine.setSupplicantRunning(false)
+     */
+    @Test
+    public void testRestartWifiStackInStaDisabledWithScanState() throws Exception {
+        reset(mWifiStateMachine);
+        assertEquals("StaDisabledWithScanState", getCurrentState().getName());
+        mWifiController.sendMessage(CMD_RESTART_WIFI);
+        mLooper.dispatchAll();
+        verifyZeroInteractions(mWifiStateMachine);
+    }
+
+    /**
+     * The command to trigger a WiFi reset should trigger a wifi reset in WifiStateMachine through
+     * the WifiStateMachine.setSupplicantRunning(false) call when in STA mode.
+     * WiFi is in connect mode, calls to reset the wifi stack due to connection failures
+     * should trigger a supplicant stop, and subsequently, a driver reload.
+     * Create and start WifiController in DeviceActiveState, send command to restart WiFi
+     * <p>
+     * Expected: WiFiController should call WifiStateMachine.setSupplicantRunning(false),
+     * WifiStateMachine should enter CONNECT_MODE and the wifi driver should be started.
+     */
+    @Test
+    public void testRestartWifiStackInStaEnabledState() throws Exception {
+        enableWifi();
+
+        reset(mWifiStateMachine);
+        assertEquals("DeviceActiveState", getCurrentState().getName());
+        mWifiController.sendMessage(CMD_RESTART_WIFI);
+        mLooper.dispatchAll();
+        InOrder inOrder = inOrder(mWifiStateMachine);
+        inOrder.verify(mWifiStateMachine).setSupplicantRunning(false);
+        inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
+        inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        inOrder.verify(mWifiStateMachine).setDriverStart(true);
+        assertEquals("DeviceActiveState", getCurrentState().getName());
+    }
+
+    /**
+     * The command to trigger a WiFi reset should not trigger a reset when in ECM mode.
+     * Enable wifi and enter ECM state, send command to restart wifi.
+     * <p>
+     * Expected: The command to trigger a wifi reset should be ignored and we should remain in ECM
+     * mode.
+     */
+    @Test
+    public void testRestartWifiStackDoesNotExitECMMode() throws Exception {
+        enableWifi();
+        assertEquals("DeviceActiveState", getCurrentState().getName());
+        when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
+
+        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 1);
+        mLooper.dispatchAll();
+        assertInEcm(true);
+
+        reset(mWifiStateMachine);
+        mWifiController.sendMessage(CMD_RESTART_WIFI);
+        mLooper.dispatchAll();
+        assertInEcm(true);
+        verifyZeroInteractions(mWifiStateMachine);
+    }
+
+    /**
+     * The command to trigger a WiFi reset should not trigger a reset when in AP mode.
+     * Enter AP mode, send command to restart wifi.
+     * <p>
+     * Expected: The command to trigger a wifi reset should be ignored and we should remain in AP
+     * mode.
+     */
+    @Test
+    public void testRestartWifiStackDoesNotExitAPMode() throws Exception {
+        mWifiController.obtainMessage(CMD_SET_AP, 1).sendToTarget();
+        mLooper.dispatchAll();
+        assertEquals("ApEnabledState", getCurrentState().getName());
+
+        reset(mWifiStateMachine);
+        mWifiController.sendMessage(CMD_RESTART_WIFI);
+        mLooper.dispatchAll();
+        verifyZeroInteractions(mWifiStateMachine);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
index 2e62a30..faa2f71 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiCountryCodeTest.java
@@ -71,7 +71,7 @@
         // Wifi get L2 connected.
         mWifiCountryCode.setReadyForChange(false);
         verify(mWifiNative).setCountryCode(anyString());
-        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 
     /**
@@ -81,13 +81,13 @@
     @Test
     public void useTelephonyCountryCode() throws Exception {
         mWifiCountryCode.setCountryCode(mTelephonyCountryCode, false);
-        assertEquals(null, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(null, mWifiCountryCode.getCountryCodeSentToDriver());
         // Supplicant started.
         mWifiCountryCode.setReadyForChange(true);
         // Wifi get L2 connected.
         mWifiCountryCode.setReadyForChange(false);
         verify(mWifiNative).setCountryCode(anyString());
-        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 
     /**
@@ -98,13 +98,13 @@
     public void setTelephonyCountryCodeAfterSupplicantStarts() throws Exception {
         // Supplicant starts.
         mWifiCountryCode.setReadyForChange(true);
-        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
         // Telephony country code arrives.
         mWifiCountryCode.setCountryCode(mTelephonyCountryCode, false);
         // Wifi get L2 connected.
         mWifiCountryCode.setReadyForChange(false);
         verify(mWifiNative, times(2)).setCountryCode(anyString());
-        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 
     /**
@@ -120,11 +120,11 @@
         // Telephony country code arrives.
         mWifiCountryCode.setCountryCode(mTelephonyCountryCode, false);
         // Telephony coutry code won't be applied at this time.
-        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
         mWifiCountryCode.setReadyForChange(true);
         // Telephony coutry is applied after supplicant is ready.
         verify(mWifiNative, times(2)).setCountryCode(anyString());
-        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 
     /**
@@ -138,15 +138,15 @@
         mWifiCountryCode.setReadyForChange(true);
         // Wifi get L2 connected.
         mWifiCountryCode.setReadyForChange(false);
-        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
         // SIM card is removed.
         mWifiCountryCode.simCardRemoved();
         // Country code restting is not applied yet.
-        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
         mWifiCountryCode.setReadyForChange(true);
         // Country code restting is applied when supplicant is ready.
         verify(mWifiNative, times(2)).setCountryCode(anyString());
-        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 
     /**
@@ -160,15 +160,15 @@
         mWifiCountryCode.setReadyForChange(true);
         // Wifi get L2 connected.
         mWifiCountryCode.setReadyForChange(false);
-        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
         // Airplane mode is enabled.
         mWifiCountryCode.simCardRemoved();
         // Country code restting is not applied yet.
-        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mTelephonyCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
         mWifiCountryCode.setReadyForChange(true);
         // Country code restting is applied when supplicant is ready.
         verify(mWifiNative, times(2)).setCountryCode(anyString());
-        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(mDefaultCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 
     /**
@@ -188,6 +188,6 @@
         // Wifi get L2 connected.
         mWifiCountryCode.setReadyForChange(false);
         verify(mWifiNative).setCountryCode(anyString());
-        assertEquals(persistentCountryCode, mWifiCountryCode.getCurrentCountryCode());
+        assertEquals(persistentCountryCode, mWifiCountryCode.getCountryCodeSentToDriver());
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java b/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
index 64f1ce0..237fc66 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiLastResortWatchdogTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.*;
+import static org.mockito.MockitoAnnotations.*;
 
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiSsid;
@@ -27,6 +28,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -38,7 +40,8 @@
 @SmallTest
 public class WifiLastResortWatchdogTest {
     WifiLastResortWatchdog mLastResortWatchdog;
-    WifiMetrics mWifiMetrics;
+    @Mock WifiMetrics mWifiMetrics;
+    @Mock WifiController mWifiController;
     private String[] mSsids = {"\"test1\"", "\"test2\"", "\"test3\"", "\"test4\""};
     private String[] mBssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4", "de:ad:ba:b1:e5:55",
             "c0:ff:ee:ee:e3:ee"};
@@ -51,8 +54,9 @@
 
     @Before
     public void setUp() throws Exception {
-        mWifiMetrics = mock(WifiMetrics.class);
+        initMocks(this);
         mLastResortWatchdog = new WifiLastResortWatchdog(mWifiMetrics);
+        mLastResortWatchdog.setWifiController(mWifiController);
     }
 
     private List<Pair<ScanDetail, WifiConfiguration>> createFilteredQnsCandidates(String[] ssids,
@@ -1276,6 +1280,8 @@
                     ssids[ssids.length - 1], bssids[ssids.length - 1],
                     WifiLastResortWatchdog.FAILURE_CODE_ASSOCIATION);
         assertEquals(true, watchdogTriggered);
+        verify(mWifiController).sendMessage(WifiController.CMD_RESTART_WIFI);
+        reset(mWifiController);
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java
new file mode 100644
index 0000000..1bbdda9
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/WifiLockManagerTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2010 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.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.*;
+import static org.mockito.Matchers.*;
+import static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.WorkSource;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.app.IBatteryStats;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/** Unit tests for {@link WifiLockManager}. */
+@SmallTest
+public class WifiLockManagerTest {
+
+    private static final int DEFAULT_TEST_UID_1 = 35;
+    private static final int DEFAULT_TEST_UID_2 = 53;
+    private static final int WIFI_LOCK_MODE_INVALID = -1;
+    private static final String TEST_WIFI_LOCK_TAG = "TestTag";
+
+    WifiLockManager mWifiLockManager;
+    @Mock IBatteryStats mBatteryStats;
+    @Mock IBinder mBinder;
+    WorkSource mWorkSource;
+    @Mock Context mContext;
+
+    /**
+     * Method to setup a WifiLockManager for the tests.
+     * The WifiLockManager uses mocks for BatteryStats and Context.
+     */
+    @Before
+    public void setUp() {
+        mWorkSource = new WorkSource(DEFAULT_TEST_UID_1);
+        MockitoAnnotations.initMocks(this);
+        mWifiLockManager = new WifiLockManager(mContext, mBatteryStats);
+    }
+
+    private void acquireWifiLockSuccessful(int lockMode, String tag, IBinder binder, WorkSource ws)
+            throws Exception {
+        ArgumentCaptor<IBinder.DeathRecipient> deathRecipient =
+                ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+
+        assertTrue(mWifiLockManager.acquireWifiLock(lockMode, tag, binder, ws));
+        assertThat(mWifiLockManager.getStrongestLockMode(),
+                not(WifiManager.WIFI_MODE_NO_LOCKS_HELD));
+        InOrder inOrder = inOrder(binder, mBatteryStats);
+        inOrder.verify(binder).linkToDeath(deathRecipient.capture(), eq(0));
+        inOrder.verify(mBatteryStats).noteFullWifiLockAcquiredFromSource(ws);
+    }
+
+    private void releaseWifiLockSuccessful(IBinder binder) throws Exception {
+        ArgumentCaptor<IBinder.DeathRecipient> deathRecipient =
+                ArgumentCaptor.forClass(IBinder.DeathRecipient.class);
+
+        assertTrue(mWifiLockManager.releaseWifiLock(binder));
+        InOrder inOrder = inOrder(binder, mBatteryStats);
+        inOrder.verify(binder).unlinkToDeath(deathRecipient.capture(), eq(0));
+        inOrder.verify(mBatteryStats).noteFullWifiLockReleasedFromSource(any(WorkSource.class));
+    }
+
+    /**
+     * Test to check that a new WifiLockManager should not be holding any locks.
+     */
+    @Test
+    public void newWifiLockManagerShouldNotHaveAnyLocks() {
+        assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD, mWifiLockManager.getStrongestLockMode());
+    }
+
+    /**
+     * Test to verify that the lock mode is verified before adding a lock.
+     *
+     * Steps: call acquireWifiLock with an invalid lock mode.
+     * Expected: the call should throw an IllegalArgumentException.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void acquireWifiLockShouldThrowExceptionOnInivalidLockMode() throws Exception {
+        mWifiLockManager.acquireWifiLock(WIFI_LOCK_MODE_INVALID, "", mBinder, mWorkSource);
+    }
+
+    /**
+     * Test that a call to acquireWifiLock with valid parameters works.
+     *
+     * Steps: call acquireWifiLock on the empty WifiLockManager.
+     * Expected: A subsequent call to getStrongestLockMode should reflect the type of the lock we
+     * just added
+     */
+    @Test
+    public void acquireWifiLockWithValidParamsShouldSucceed() throws Exception {
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL, "", mBinder, mWorkSource);
+        assertEquals(WifiManager.WIFI_MODE_FULL, mWifiLockManager.getStrongestLockMode());
+    }
+
+    /**
+     * Test that a call to acquireWifiLock will not succeed if there is already a lock for the same
+     * binder instance.
+     *
+     * Steps: call acquireWifiLock twice
+     * Expected: Second call should return false
+     */
+    @Test
+    public void secondCallToAcquireWifiLockWithSameBinderShouldFail() throws Exception {
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_SCAN_ONLY, "", mBinder, mWorkSource);
+        assertEquals(WifiManager.WIFI_MODE_SCAN_ONLY, mWifiLockManager.getStrongestLockMode());
+        assertFalse(mWifiLockManager.acquireWifiLock(
+                WifiManager.WIFI_MODE_SCAN_ONLY, "", mBinder, mWorkSource));
+    }
+
+    /**
+     * After acquiring a lock, we should be able to remove it.
+     *
+     * Steps: acquire a WifiLock and then remove it.
+     * Expected: Since a single lock was added, removing it should leave the WifiLockManager without
+     * any locks.  We should not see any errors.
+     */
+    @Test
+    public void releaseWifiLockShouldSucceed() throws Exception {
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL, "", mBinder, mWorkSource);
+        releaseWifiLockSuccessful(mBinder);
+        assertEquals(WifiManager.WIFI_MODE_NO_LOCKS_HELD, mWifiLockManager.getStrongestLockMode());
+    }
+
+    /**
+     * Releasing locks for one caller should not release locks for a different caller.
+     *
+     * Steps: acquire locks for two callers and remove locks for one.
+     * Expected: locks for remaining caller should still be active.
+     */
+    @Test
+    public void releaseLocksForOneCallerNotImpactOtherCallers() throws Exception {
+        IBinder toReleaseBinder = mock(IBinder.class);
+        WorkSource toReleaseWS = new WorkSource(DEFAULT_TEST_UID_1);
+        WorkSource toKeepWS = new WorkSource(DEFAULT_TEST_UID_2);
+
+        acquireWifiLockSuccessful(
+                WifiManager.WIFI_MODE_FULL_HIGH_PERF, "", toReleaseBinder, toReleaseWS);
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_SCAN_ONLY, "", mBinder, toKeepWS);
+        assertEquals(mWifiLockManager.getStrongestLockMode(), WifiManager.WIFI_MODE_FULL_HIGH_PERF);
+        releaseWifiLockSuccessful(toReleaseBinder);
+        assertEquals(mWifiLockManager.getStrongestLockMode(), WifiManager.WIFI_MODE_SCAN_ONLY);
+        releaseWifiLockSuccessful(mBinder);
+        assertEquals(mWifiLockManager.getStrongestLockMode(), WifiManager.WIFI_MODE_NO_LOCKS_HELD);
+    }
+
+    /**
+     * Attempting to release a lock that we do not hold should return false.
+     *
+     * Steps: release a WifiLock
+     * Expected: call to releaseWifiLock should return false.
+     */
+    @Test
+    public void releaseWifiLockWithoutAcquireWillReturnFalse() {
+        assertFalse(mWifiLockManager.releaseWifiLock(mBinder));
+    }
+
+    /**
+     * Test used to verify call for getStrongestLockMode.
+     *
+     * Steps: The test first checks the return value for no held locks and then proceeds to test
+     * with a single lock of each type.
+     * Expected: getStrongestLockMode should reflect the type of lock we just added.
+     */
+    @Test
+    public void checkForProperValueForGetStrongestLockMode() throws Exception {
+        assertEquals(mWifiLockManager.getStrongestLockMode(), WifiManager.WIFI_MODE_NO_LOCKS_HELD);
+
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "", mBinder, mWorkSource);
+        assertEquals(mWifiLockManager.getStrongestLockMode(), WifiManager.WIFI_MODE_FULL_HIGH_PERF);
+        releaseWifiLockSuccessful(mBinder);
+
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL, "", mBinder, mWorkSource);
+        assertEquals(mWifiLockManager.getStrongestLockMode(), WifiManager.WIFI_MODE_FULL);
+        releaseWifiLockSuccessful(mBinder);
+
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_SCAN_ONLY, "", mBinder, mWorkSource);
+        assertEquals(mWifiLockManager.getStrongestLockMode(), WifiManager.WIFI_MODE_SCAN_ONLY);
+    }
+
+    /**
+     * We should be able to create a merged WorkSource holding WorkSources for all active locks.
+     *
+     * Steps: call createMergedWorkSource and verify it is empty, add a lock and call again, it
+     * should have one entry.
+     * Expected: the first call should return a worksource with size 0 and the second should be size
+     * 1.
+     */
+    @Test
+    public void createMergedWorkSourceShouldSucceed() throws Exception {
+        WorkSource checkMWS = mWifiLockManager.createMergedWorkSource();
+        assertEquals(checkMWS.size(), 0);
+
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "", mBinder, mWorkSource);
+        checkMWS = mWifiLockManager.createMergedWorkSource();
+        assertEquals(checkMWS.size(), 1);
+    }
+
+    /**
+     * Test the ability to update a WifiLock WorkSource with a new WorkSource.
+     *
+     * Steps: acquire a WifiLock with the default test worksource, then attempt to update it.
+     * Expected: Verify calls to release the original WorkSource and acquire with the new one to
+     * BatteryStats.
+     */
+    @Test
+    public void testUpdateWifiLockWorkSourceCalledWithWorkSource() throws Exception {
+        WorkSource newWorkSource = new WorkSource(DEFAULT_TEST_UID_2);
+
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "", mBinder, mWorkSource);
+
+        mWifiLockManager.updateWifiLockWorkSource(mBinder, newWorkSource);
+        InOrder inOrder = inOrder(mBatteryStats);
+        inOrder.verify(mBatteryStats).noteFullWifiLockReleasedFromSource(mWorkSource);
+        inOrder.verify(mBatteryStats).noteFullWifiLockAcquiredFromSource(eq(newWorkSource));
+    }
+
+    /**
+     * Test the ability to update a WifiLock WorkSource with the callers UID.
+     *
+     * Steps: acquire a WifiLock with the default test worksource, then attempt to update it.
+     * Expected: Verify calls to release the original WorkSource and acquire with the new one to
+     * BatteryStats.
+     */
+    @Test
+    public void testUpdateWifiLockWorkSourceCalledWithUID()  throws Exception {
+        WorkSource newWorkSource = new WorkSource(Binder.getCallingUid());
+
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "", mBinder, mWorkSource);
+
+        mWifiLockManager.updateWifiLockWorkSource(mBinder, null);
+        InOrder inOrder = inOrder(mBatteryStats);
+        inOrder.verify(mBatteryStats).noteFullWifiLockReleasedFromSource(mWorkSource);
+        inOrder.verify(mBatteryStats).noteFullWifiLockAcquiredFromSource(eq(newWorkSource));
+    }
+
+    /**
+     * Test an attempt to update a WifiLock that is not active.
+     *
+     * Steps: call updateWifiLockWorkSource
+     * Expected: catch an IllegalArgumentException
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testUpdateWifiLockWorkSourceCalledWithoutActiveLock()  throws Exception {
+        mWifiLockManager.updateWifiLockWorkSource(mBinder, null);
+    }
+
+    /**
+     * Verfies that dump() does not fail when no locks are held.
+     */
+    @Test
+    public void dumpDoesNotFailWhenNoLocksAreHeld() {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        mWifiLockManager.dump(pw);
+
+        String wifiLockManagerDumpString = sw.toString();
+        assertTrue(wifiLockManagerDumpString.contains(
+                "Locks acquired: 0 full, 0 full high perf, 0 scan"));
+        assertTrue(wifiLockManagerDumpString.contains(
+                "Locks released: 0 full, 0 full high perf, 0 scan"));
+        assertTrue(wifiLockManagerDumpString.contains("Locks held:"));
+        assertFalse(wifiLockManagerDumpString.contains("WifiLock{"));
+    }
+
+    /**
+     * Verifies that dump() contains lock information when there are locks held.
+     */
+    @Test
+    public void dumpOutputsCorrectInformationWithActiveLocks() throws Exception {
+        acquireWifiLockSuccessful(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "", mBinder, mWorkSource);
+        releaseWifiLockSuccessful(mBinder);
+
+        acquireWifiLockSuccessful(
+                WifiManager.WIFI_MODE_FULL, TEST_WIFI_LOCK_TAG, mBinder, mWorkSource);
+
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        mWifiLockManager.dump(pw);
+
+        String wifiLockManagerDumpString = sw.toString();
+        assertTrue(wifiLockManagerDumpString.contains(
+                "Locks acquired: 1 full, 1 full high perf, 0 scan"));
+        assertTrue(wifiLockManagerDumpString.contains(
+                "Locks released: 0 full, 1 full high perf, 0 scan"));
+        assertTrue(wifiLockManagerDumpString.contains("Locks held:"));
+        assertTrue(wifiLockManagerDumpString.contains(
+                "WifiLock{" + TEST_WIFI_LOCK_TAG + " type=" + WifiManager.WIFI_MODE_FULL
+                + " uid=" + Binder.getCallingUid() + "}"));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java
index 55dc683..d915ff3 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java
@@ -16,7 +16,10 @@
 
 package com.android.server.wifi;
 
+import android.content.Context;
 import android.test.suitebuilder.annotation.SmallTest;
+import com.android.internal.R;
+import android.util.LocalLog;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -52,11 +55,16 @@
     @Mock WifiStateMachine mWsm;
     @Mock WifiNative mWifiNative;
     @Mock BuildProperties mBuildProperties;
+    @Mock Context mContext;
     WifiLogger mWifiLogger;
 
     private static final String FAKE_RING_BUFFER_NAME = "fake-ring-buffer";
-    private WifiNative.RingBufferStatus mFakeRbs;
+    private static final int SMALL_RING_BUFFER_SIZE_KB = 32;
+    private static final int LARGE_RING_BUFFER_SIZE_KB = 1024;
+    private static final int BYTES_PER_KBYTE = 1024;
+    private LocalLog mWifiNativeLocalLog;
 
+    private WifiNative.RingBufferStatus mFakeRbs;
     /**
      * Returns the data that we would dump in a bug report, for our ring buffer.
      * @return a 2-D byte array, where the first dimension is the record number, and the second
@@ -72,20 +80,29 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+
         mFakeRbs = new WifiNative.RingBufferStatus();
         mFakeRbs.name = FAKE_RING_BUFFER_NAME;
-
         WifiNative.RingBufferStatus[] ringBufferStatuses = new WifiNative.RingBufferStatus[] {
                 mFakeRbs
         };
+        mWifiNativeLocalLog = new LocalLog(8192);
 
         when(mWifiNative.getRingBufferStatus()).thenReturn(ringBufferStatuses);
         when(mWifiNative.readKernelLog()).thenReturn("");
+        when(mWifiNative.getLocalLog()).thenReturn(mWifiNativeLocalLog);
         when(mBuildProperties.isEngBuild()).thenReturn(false);
         when(mBuildProperties.isUserdebugBuild()).thenReturn(false);
         when(mBuildProperties.isUserBuild()).thenReturn(true);
 
-        mWifiLogger = new WifiLogger(mWsm, mWifiNative, mBuildProperties);
+        MockResources resources = new MockResources();
+        resources.setInteger(R.integer.config_wifi_logger_ring_buffer_default_size_limit_kb,
+                SMALL_RING_BUFFER_SIZE_KB);
+        resources.setInteger(R.integer.config_wifi_logger_ring_buffer_verbose_size_limit_kb,
+                LARGE_RING_BUFFER_SIZE_KB);
+        when(mContext.getResources()).thenReturn(resources);
+
+        mWifiLogger = new WifiLogger(mContext, mWsm, mWifiNative, mBuildProperties);
         mWifiNative.enableVerboseLogging(0);
     }
 
@@ -197,7 +214,7 @@
         final boolean verbosityToggle = false;
         mWifiLogger.startLogging(verbosityToggle);
 
-        final byte[] data = new byte[WifiLogger.RING_BUFFER_BYTE_LIMIT_SMALL];
+        final byte[] data = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
         mWifiLogger.onRingBufferData(mFakeRbs, data);
         mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
 
@@ -214,7 +231,7 @@
         final boolean verbosityToggle = false;
         mWifiLogger.startLogging(verbosityToggle);
 
-        final byte[] data1 = new byte[WifiLogger.RING_BUFFER_BYTE_LIMIT_SMALL];
+        final byte[] data1 = new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE];
         final byte[] data2 = {1, 2, 3};
         mWifiLogger.onRingBufferData(mFakeRbs, data1);
         mWifiLogger.onRingBufferData(mFakeRbs, data2);
@@ -526,7 +543,7 @@
         final boolean verbosityToggle = false;
         mWifiLogger.startLogging(verbosityToggle);
         mWifiLogger.onRingBufferData(
-                mFakeRbs, new byte[WifiLogger.RING_BUFFER_BYTE_LIMIT_SMALL + 1]);
+                mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
         mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
         assertEquals(0, getLoggerRingBufferData().length);
     }
@@ -540,7 +557,7 @@
         when(mBuildProperties.isUserBuild()).thenReturn(false);
         mWifiLogger.startLogging(verbosityToggle);
         mWifiLogger.onRingBufferData(
-                mFakeRbs, new byte[WifiLogger.RING_BUFFER_BYTE_LIMIT_SMALL + 1]);
+                mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
         mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
         assertEquals(0, getLoggerRingBufferData().length);
     }
@@ -554,7 +571,7 @@
         when(mBuildProperties.isUserBuild()).thenReturn(false);
         mWifiLogger.startLogging(verbosityToggle);
         mWifiLogger.onRingBufferData(
-                mFakeRbs, new byte[WifiLogger.RING_BUFFER_BYTE_LIMIT_SMALL + 1]);
+                mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
         mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
         assertEquals(0, getLoggerRingBufferData().length);
     }
@@ -564,7 +581,8 @@
     public void ringBufferSizeIsLargeInVerboseMode() throws Exception {
         final boolean verbosityToggle = true;
         mWifiLogger.startLogging(verbosityToggle);
-        mWifiLogger.onRingBufferData(mFakeRbs, new byte[WifiLogger.RING_BUFFER_BYTE_LIMIT_LARGE]);
+        mWifiLogger.onRingBufferData(
+                mFakeRbs, new byte[LARGE_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE]);
         mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
         assertEquals(1, getLoggerRingBufferData().length);
     }
@@ -574,7 +592,8 @@
     public void startLoggingGrowsRingBuffersIfNeeded() throws Exception {
         mWifiLogger.startLogging(false  /* verbose disabled */);
         mWifiLogger.startLogging(true  /* verbose enabled */);
-        mWifiLogger.onRingBufferData(mFakeRbs, new byte[WifiLogger.RING_BUFFER_BYTE_LIMIT_LARGE]);
+        mWifiLogger.onRingBufferData(
+                mFakeRbs, new byte[LARGE_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE]);
         mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
         assertEquals(1, getLoggerRingBufferData().length);
     }
@@ -584,7 +603,7 @@
     public void startLoggingShrinksRingBuffersIfNeeded() throws Exception {
         mWifiLogger.startLogging(true  /* verbose enabled */);
         mWifiLogger.onRingBufferData(
-                mFakeRbs, new byte[WifiLogger.RING_BUFFER_BYTE_LIMIT_SMALL + 1]);
+                mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
 
         // Existing data is nuked (too large).
         mWifiLogger.startLogging(false  /* verbose disabled */);
@@ -593,7 +612,7 @@
 
         // New data must obey limit as well.
         mWifiLogger.onRingBufferData(
-                mFakeRbs, new byte[WifiLogger.RING_BUFFER_BYTE_LIMIT_SMALL + 1]);
+                mFakeRbs, new byte[SMALL_RING_BUFFER_SIZE_KB * BYTES_PER_KBYTE + 1]);
         mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_NONE);
         assertEquals(0, getLoggerRingBufferData().length);
     }
@@ -704,4 +723,17 @@
         mWifiLogger.dump(new FileDescriptor(), pw, new String[]{});
         assertFalse(sw.toString().contains(WifiLogger.FIRMWARE_DUMP_SECTION_HEADER));
     }
+
+    /** Verifies that the dump() includes contents of WifiNative's LocalLog. */
+    @Test
+    public void dumpIncludesContentOfWifiNativeLocalLog() {
+        final String wifiNativeLogMessage = "This is a message";
+        mWifiNativeLocalLog.log(wifiNativeLogMessage);
+
+        mWifiLogger.startLogging(false  /* verbose disabled */);
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        mWifiLogger.dump(new FileDescriptor(), pw, new String[]{});
+        assertTrue(sw.toString().contains(wifiNativeLogMessage));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index 530b218..011682b 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -154,6 +154,8 @@
     private static final int NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_AUTHENTICATION = 8;
     private static final int NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_DHCP = 9;
     private static final int NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_OTHER = 10;
+    private static final int NUM_RSSI_LEVELS_TO_INCREMENT = 20;
+    private static final int FIRST_RSSI_LEVEL = -80;
     /**
      * Set simple metrics, increment others
      */
@@ -236,6 +238,11 @@
         for (int i = 0; i < NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_OTHER; i++) {
             mWifiMetrics.incrementNumLastResortWatchdogTriggersWithBadOther();
         }
+        for (int i = 0; i < NUM_RSSI_LEVELS_TO_INCREMENT; i++) {
+            for (int j = 0; j <= i; j++) {
+                mWifiMetrics.incrementRssiPollRssiCount(FIRST_RSSI_LEVEL + i);
+            }
+        }
     }
 
     /**
@@ -313,6 +320,10 @@
                 mDeserializedWifiMetrics.numLastResortWatchdogTriggersWithBadOther);
         assertEquals(TEST_RECORD_DURATION_SEC,
                 mDeserializedWifiMetrics.recordDurationSec);
+        for (int i = 0; i < NUM_RSSI_LEVELS_TO_INCREMENT; i++) {
+            assertEquals(FIRST_RSSI_LEVEL + i, mDeserializedWifiMetrics.rssiPollRssiCount[i].rssi);
+            assertEquals(i + 1, mDeserializedWifiMetrics.rssiPollRssiCount[i].count);
+        }
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java
index 64fee84..92de7d4 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java
@@ -2233,4 +2233,41 @@
         assertEquals(WifiQualifiedNetworkSelector.ExternalScoreEvaluator
                 .BestCandidateType.NONE, evaluator.getBestCandidateType());
     }
+
+    /**
+     * Case #46   Choose 2.4GHz BSSID with stronger RSSI value over
+     *            5GHz BSSID with weaker RSSI value
+     *
+     * In this test. we simulate following scenario:
+     * Two APs are found in scan results
+     * BSSID1 is @ 5GHz with RSSI -82
+     * BSSID2 is @ 2Ghz with RSSI -72
+     * These two BSSIDs get exactly the same QNS score
+     *
+     * expect BSSID2 to be chosen as it has stronger RSSI value
+     */
+    @Test
+    public void chooseStrongerRssiOver5GHz() {
+        String[] ssids = {"\"test1\"", "\"test1\""};
+        String[] bssids = {"6c:f3:7f:ae:8c:f3", "6c:f3:7f:ae:8c:f4"};
+        int[] frequencies = {5220, 2437};
+        String[] caps = {"[ESS]", "[ESS]"};
+        int[] levels = {-82, -72};
+        int[] security = {SECURITY_NONE, SECURITY_NONE};
+
+        List<ScanDetail> scanDetails = getScanDetails(ssids, bssids, frequencies, caps, levels);
+        WifiConfiguration[] savedConfigs = generateWifiConfigurations(ssids, security);
+        prepareConfigStore(savedConfigs);
+
+        final List<WifiConfiguration> savedNetwork = Arrays.asList(savedConfigs);
+        when(mWifiConfigManager.getSavedNetworks()).thenReturn(savedNetwork);
+        scanResultLinkConfiguration(savedConfigs, scanDetails);
+
+        ScanResult chosenScanResult = scanDetails.get(1).getScanResult();
+
+        WifiConfiguration candidate = mWifiQualifiedNetworkSelector.selectQualifiedNetwork(false,
+                false, scanDetails, false, false, true, false);
+
+        verifySelectedResult(chosenScanResult, candidate);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
index e0f94ad..53d8f46 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
@@ -475,6 +475,75 @@
         assertEquals("InitialState", getCurrentState().getName());
     }
 
+    /**
+     * Test to check that mode changes from WifiController will be properly handled in the
+     * InitialState by WifiStateMachine.
+     */
+    @Test
+    public void checkOperationalModeInInitialState() throws Exception {
+        when(mWifiNative.loadDriver()).thenReturn(true);
+        when(mWifiNative.startHal()).thenReturn(true);
+        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(true);
+
+        mLooper.dispatchAll();
+        assertEquals("InitialState", getCurrentState().getName());
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+
+        mWsm.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE,
+                mWsm.getOperationalModeForTest());
+
+        mWsm.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.SCAN_ONLY_MODE, mWsm.getOperationalModeForTest());
+
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+    }
+
+    /**
+     * Test that mode changes for WifiStateMachine in the InitialState are realized when supplicant
+     * is started.
+     */
+    @Test
+    public void checkStartInCorrectStateAfterChangingInitialState() throws Exception {
+        when(mWifiNative.loadDriver()).thenReturn(true);
+        when(mWifiNative.startHal()).thenReturn(true);
+        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(true);
+
+        // Check initial state
+        mLooper.dispatchAll();
+        assertEquals("InitialState", getCurrentState().getName());
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+
+        // Update the mode
+        mWsm.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.SCAN_ONLY_MODE, mWsm.getOperationalModeForTest());
+
+        // Start supplicant so we move to the next state
+        mWsm.setSupplicantRunning(true);
+        mLooper.dispatchAll();
+        assertEquals("SupplicantStartingState", getCurrentState().getName());
+        when(mWifiNative.setBand(anyInt())).thenReturn(true);
+        when(mWifiNative.setDeviceName(anyString())).thenReturn(true);
+        when(mWifiNative.setManufacturer(anyString())).thenReturn(true);
+        when(mWifiNative.setModelName(anyString())).thenReturn(true);
+        when(mWifiNative.setModelNumber(anyString())).thenReturn(true);
+        when(mWifiNative.setSerialNumber(anyString())).thenReturn(true);
+        when(mWifiNative.setConfigMethods(anyString())).thenReturn(true);
+        when(mWifiNative.setDeviceType(anyString())).thenReturn(true);
+        when(mWifiNative.setSerialNumber(anyString())).thenReturn(true);
+        when(mWifiNative.setScanningMacOui(any(byte[].class))).thenReturn(true);
+
+        mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
+        mLooper.dispatchAll();
+
+        assertEquals("ScanModeState", getCurrentState().getName());
+    }
+
     private void addNetworkAndVerifySuccess() throws Exception {
         addNetworkAndVerifySuccess(false);
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java b/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
index 62f5105..03a11dc 100644
--- a/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/scanner/WifiScanningServiceTest.java
@@ -39,6 +39,7 @@
 import android.util.Pair;
 
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.Protocol;
 import com.android.server.wifi.BidirectionalAsyncChannel;
 import com.android.server.wifi.Clock;
@@ -137,13 +138,13 @@
         return controlChannel;
     }
 
-    private Message verifyHandleMessageAndGetMessage(InOrder order, Handler handler) {
+    private static Message verifyHandleMessageAndGetMessage(InOrder order, Handler handler) {
         ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
         order.verify(handler).handleMessage(messageCaptor.capture());
         return messageCaptor.getValue();
     }
 
-    private Message verifyHandleMessageAndGetMessage(InOrder order, Handler handler,
+    private static Message verifyHandleMessageAndGetMessage(InOrder order, Handler handler,
             final int what) {
         CapturingMatcher<Message> messageMatcher = new CapturingMatcher<Message>() {
             public boolean matches(Object argument) {
@@ -155,14 +156,14 @@
         return messageMatcher.getLastValue();
     }
 
-    private void verifyScanResultsRecieved(InOrder order, Handler handler, int listenerId,
+    private static void verifyScanResultsRecieved(InOrder order, Handler handler, int listenerId,
             WifiScanner.ScanData... expected) {
         Message scanResultMessage = verifyHandleMessageAndGetMessage(order, handler,
                 WifiScanner.CMD_SCAN_RESULT);
         assertScanResultsMessage(listenerId, expected, scanResultMessage);
     }
 
-    private void assertScanResultsMessage(int listenerId, WifiScanner.ScanData[] expected,
+    private static void assertScanResultsMessage(int listenerId, WifiScanner.ScanData[] expected,
             Message scanResultMessage) {
         assertEquals("what", WifiScanner.CMD_SCAN_RESULT, scanResultMessage.what);
         assertEquals("listenerId", listenerId, scanResultMessage.arg2);
@@ -170,18 +171,19 @@
                 ((WifiScanner.ParcelableScanData) scanResultMessage.obj).getResults());
     }
 
-    private void verifySingleScanCompletedRecieved(InOrder order, Handler handler, int listenerId) {
+    private static void verifySingleScanCompletedRecieved(InOrder order, Handler handler,
+            int listenerId) {
         Message completedMessage = verifyHandleMessageAndGetMessage(order, handler,
                 WifiScanner.CMD_SINGLE_SCAN_COMPLETED);
         assertSingleScanCompletedMessage(listenerId, completedMessage);
     }
 
-    private void assertSingleScanCompletedMessage(int listenerId, Message completedMessage) {
+    private static void assertSingleScanCompletedMessage(int listenerId, Message completedMessage) {
         assertEquals("what", WifiScanner.CMD_SINGLE_SCAN_COMPLETED, completedMessage.what);
         assertEquals("listenerId", listenerId, completedMessage.arg2);
     }
 
-    private void sendBackgroundScanRequest(BidirectionalAsyncChannel controlChannel,
+    private static void sendBackgroundScanRequest(BidirectionalAsyncChannel controlChannel,
             int scanRequestId, WifiScanner.ScanSettings settings, WorkSource workSource) {
         Bundle scanParams = new Bundle();
         scanParams.putParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
@@ -190,7 +192,7 @@
                         scanRequestId, scanParams));
     }
 
-    private void sendSingleScanRequest(BidirectionalAsyncChannel controlChannel,
+    private static void sendSingleScanRequest(BidirectionalAsyncChannel controlChannel,
             int scanRequestId, WifiScanner.ScanSettings settings, WorkSource workSource) {
         Bundle scanParams = new Bundle();
         scanParams.putParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
@@ -199,12 +201,24 @@
                         scanRequestId, scanParams));
     }
 
-    private void verifySuccessfulResponse(InOrder order, Handler handler, int arg2) {
+    private static void registerScanListener(BidirectionalAsyncChannel controlChannel,
+            int listenerRequestId) {
+        controlChannel.sendMessage(Message.obtain(null, WifiScanner.CMD_REGISTER_SCAN_LISTENER, 0,
+                        listenerRequestId, null));
+    }
+
+    private static void deregisterScanListener(BidirectionalAsyncChannel controlChannel,
+            int listenerRequestId) {
+        controlChannel.sendMessage(Message.obtain(null, WifiScanner.CMD_DEREGISTER_SCAN_LISTENER, 0,
+                        listenerRequestId, null));
+    }
+
+    private static void verifySuccessfulResponse(InOrder order, Handler handler, int arg2) {
         Message response = verifyHandleMessageAndGetMessage(order, handler);
         assertSuccessfulResponse(arg2, response);
     }
 
-    private void assertSuccessfulResponse(int arg2, Message response) {
+    private static void assertSuccessfulResponse(int arg2, Message response) {
         if (response.what == WifiScanner.CMD_OP_FAILED) {
             WifiScanner.OperationResult result = (WifiScanner.OperationResult) response.obj;
             fail("response indicates failure, reason=" + result.reason
@@ -215,13 +229,65 @@
         }
     }
 
-    private void verifyFailedResponse(InOrder order, Handler handler, int arg2,
+    /**
+     * If multiple results are expected for a single hardware scan then the order that they are
+     * dispatched is dependant on the order which they are iterated through internally. This
+     * function validates that the order is either one way or the other. A scan listener can
+     * optionally be provided as well and will be checked after the after the single scan requests.
+     */
+    private static void verifyMultipleSingleScanResults(InOrder handlerOrder, Handler handler,
+            int requestId1, ScanResults results1, int requestId2, ScanResults results2,
+            int listenerRequestId, ScanResults listenerResults) {
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        handlerOrder.verify(handler, times(listenerResults == null ? 4 : 5))
+                .handleMessage(messageCaptor.capture());
+        int firstListenerId = messageCaptor.getAllValues().get(0).arg2;
+        assertTrue(firstListenerId + " was neither " + requestId2 + " nor " + requestId1,
+                firstListenerId == requestId2 || firstListenerId == requestId1);
+        if (firstListenerId == requestId2) {
+            assertScanResultsMessage(requestId2,
+                    new WifiScanner.ScanData[] {results2.getScanData()},
+                    messageCaptor.getAllValues().get(0));
+            assertSingleScanCompletedMessage(requestId2, messageCaptor.getAllValues().get(1));
+            assertScanResultsMessage(requestId1,
+                    new WifiScanner.ScanData[] {results1.getScanData()},
+                    messageCaptor.getAllValues().get(2));
+            assertSingleScanCompletedMessage(requestId1, messageCaptor.getAllValues().get(3));
+            if (listenerResults != null) {
+                assertScanResultsMessage(listenerRequestId,
+                        new WifiScanner.ScanData[] {listenerResults.getScanData()},
+                        messageCaptor.getAllValues().get(4));
+            }
+        } else {
+            assertScanResultsMessage(requestId1,
+                    new WifiScanner.ScanData[] {results1.getScanData()},
+                    messageCaptor.getAllValues().get(0));
+            assertSingleScanCompletedMessage(requestId1, messageCaptor.getAllValues().get(1));
+            assertScanResultsMessage(requestId2,
+                    new WifiScanner.ScanData[] {results2.getScanData()},
+                    messageCaptor.getAllValues().get(2));
+            assertSingleScanCompletedMessage(requestId2, messageCaptor.getAllValues().get(3));
+            if (listenerResults != null) {
+                assertScanResultsMessage(listenerRequestId,
+                        new WifiScanner.ScanData[] {listenerResults.getScanData()},
+                        messageCaptor.getAllValues().get(4));
+            }
+        }
+    }
+
+    private static void verifyMultipleSingleScanResults(InOrder handlerOrder, Handler handler,
+            int requestId1, ScanResults results1, int requestId2, ScanResults results2) {
+        verifyMultipleSingleScanResults(handlerOrder, handler, requestId1, results1, requestId2,
+                results2, -1, null);
+    }
+
+    private static void verifyFailedResponse(InOrder order, Handler handler, int arg2,
             int expectedErrorReason, String expectedErrorDescription) {
         Message response = verifyHandleMessageAndGetMessage(order, handler);
         assertFailedResponse(arg2, expectedErrorReason, expectedErrorDescription, response);
     }
 
-    private void assertFailedResponse(int arg2, int expectedErrorReason,
+    private static void assertFailedResponse(int arg2, int expectedErrorReason,
             String expectedErrorDescription, Message response) {
         if (response.what == WifiScanner.CMD_OP_SUCCEEDED) {
             fail("response indicates success");
@@ -299,8 +365,9 @@
 
     private void assertDumpContainsRequestLog(String type, int id) {
         String serviceDump = dumpService();
-        Pattern logLineRegex = Pattern.compile("^.+" + type + ": ClientInfo\\[uid=\\d+\\],Id=" +
-                id + ".*$", Pattern.MULTILINE);
+        Pattern logLineRegex = Pattern.compile("^.+" + type
+                + ": ClientInfo\\[uid=\\d+,android\\.os\\.Messenger@[a-f0-9]+\\],Id=" + id
+                + ".*$", Pattern.MULTILINE);
         assertTrue("dump did not contain log with type=" + type + ", id=" + id +
                 ": " + serviceDump + "\n",
                 logLineRegex.matcher(serviceDump).find());
@@ -309,8 +376,9 @@
     private void assertDumpContainsCallbackLog(String callback, int id, String extra) {
         String serviceDump = dumpService();
         String extraPattern = extra == null ? "" : "," + extra;
-        Pattern logLineRegex = Pattern.compile("^.+" + callback + ": ClientInfo\\[uid=\\d+\\],Id=" +
-                id + extraPattern + "$", Pattern.MULTILINE);
+        Pattern logLineRegex = Pattern.compile("^.+" + callback
+                + ": ClientInfo\\[uid=\\d+,android\\.os\\.Messenger@[a-f0-9]+\\],Id=" + id
+                + extraPattern + "$", Pattern.MULTILINE);
         assertTrue("dump did not contain callback log with callback=" + callback + ", id=" + id +
                 ", extra=" + extra + ": " + serviceDump + "\n",
                 logLineRegex.matcher(serviceDump).find());
@@ -561,9 +629,95 @@
         verify(mBatteryStats).noteWifiScanStoppedFromSource(eq(workSource));
     }
 
-    // TODO Add more single scan tests
-    // * disable wifi while scanning
-    // * disable wifi while scanning with pending scan
+    /**
+     * Send a single scan request and then disable Wi-Fi before it completes
+     */
+    @Test
+    public void sendSingleScanRequestThenDisableWifi() {
+        WifiScanner.ScanSettings requestSettings = createRequest(channelsToSpec(2400), 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId = 2293;
+
+        startServiceAndLoadDriver();
+
+        when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
+                        any(WifiNative.ScanEventHandler.class))).thenReturn(true);
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl);
+
+        // Run scan 1
+        sendSingleScanRequest(controlChannel, requestId, requestSettings, null);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(order, handler, requestId);
+
+        // disable wifi
+        TestUtil.sendWifiScanAvailable(mBroadcastReceiver, mContext,
+                WifiManager.WIFI_STATE_DISABLED);
+
+        // validate failed response
+        mLooper.dispatchAll();
+        verifyFailedResponse(order, handler, requestId, WifiScanner.REASON_UNSPECIFIED,
+                "Scan was interrupted");
+        verifyNoMoreInteractions(handler);
+    }
+
+    /**
+     * Send a single scan request, schedule a second pending scan and disable Wi-Fi before the first
+     * scan completes.
+     */
+    @Test
+    public void sendSingleScanAndPendingScanAndListenerThenDisableWifi() {
+        WifiScanner.ScanSettings requestSettings1 = createRequest(channelsToSpec(2400), 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId1 = 2293;
+
+        WifiScanner.ScanSettings requestSettings2 = createRequest(channelsToSpec(2450), 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId2 = 2294;
+
+        int listenerRequestId = 2295;
+
+        startServiceAndLoadDriver();
+
+        when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
+                        any(WifiNative.ScanEventHandler.class))).thenReturn(true);
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl);
+
+        // Request scan 1
+        sendSingleScanRequest(controlChannel, requestId1, requestSettings1, null);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(order, handler, requestId1);
+
+        // Request scan 2
+        sendSingleScanRequest(controlChannel, requestId2, requestSettings2, null);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(order, handler, requestId2);
+
+        // Setup scan listener
+        registerScanListener(controlChannel, listenerRequestId);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(order, handler, listenerRequestId);
+
+        // disable wifi
+        TestUtil.sendWifiScanAvailable(mBroadcastReceiver, mContext,
+                WifiManager.WIFI_STATE_DISABLED);
+
+        // validate failed response
+        mLooper.dispatchAll();
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        order.verify(handler, times(2)).handleMessage(messageCaptor.capture());
+        assertFailedResponse(requestId1, WifiScanner.REASON_UNSPECIFIED,
+                "Scan was interrupted", messageCaptor.getAllValues().get(0));
+        assertFailedResponse(requestId2, WifiScanner.REASON_UNSPECIFIED,
+                "Scan was interrupted", messageCaptor.getAllValues().get(1));
+        // No additional callbacks for scan listener
+        verifyNoMoreInteractions(handler);
+    }
 
     /**
      * Send a single scan request and then a second one after the first completes.
@@ -627,8 +781,8 @@
     }
 
     /**
-     * Send a single scan request and then a second one before the first completes.
-     * Verify that both are scheduled and succeed.
+     * Send a single scan request and then a second one not satisfied by the first before the first
+     * completes. Verify that both are scheduled and succeed.
      */
     @Test
     public void sendSingleScanRequestWhilePreviousScanRunning() {
@@ -693,8 +847,8 @@
 
 
     /**
-     * Send a single scan request and then two more before the first completes.
-     * Verify that the first completes and the second two are merged.
+     * Send a single scan request and then two more before the first completes. Neither are
+     * satisfied by the first scan. Verify that the first completes and the second two are merged.
      */
     @Test
     public void sendMultipleSingleScanRequestWhilePreviousScanRunning() throws RemoteException {
@@ -777,32 +931,8 @@
 
         mLooper.dispatchAll();
 
-        // unfortunatally the order that these events are dispatched is dependant on the order which
-        // they are iterated through internally
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-        handlerOrder.verify(handler, times(4)).handleMessage(messageCaptor.capture());
-        int firstListenerId = messageCaptor.getAllValues().get(0).arg2;
-        assertTrue(firstListenerId + " was neither " + requestId2 + " nor " + requestId3,
-                firstListenerId == requestId2 || firstListenerId == requestId3);
-        if (firstListenerId == requestId2) {
-            assertScanResultsMessage(requestId2,
-                    new WifiScanner.ScanData[] {results2.getScanData()},
-                    messageCaptor.getAllValues().get(0));
-            assertSingleScanCompletedMessage(requestId2, messageCaptor.getAllValues().get(1));
-            assertScanResultsMessage(requestId3,
-                    new WifiScanner.ScanData[] {results3.getScanData()},
-                    messageCaptor.getAllValues().get(2));
-            assertSingleScanCompletedMessage(requestId3, messageCaptor.getAllValues().get(3));
-        } else {
-            assertScanResultsMessage(requestId3,
-                    new WifiScanner.ScanData[] {results3.getScanData()},
-                    messageCaptor.getAllValues().get(0));
-            assertSingleScanCompletedMessage(requestId3, messageCaptor.getAllValues().get(1));
-            assertScanResultsMessage(requestId2,
-                    new WifiScanner.ScanData[] {results2.getScanData()},
-                    messageCaptor.getAllValues().get(2));
-            assertSingleScanCompletedMessage(requestId2, messageCaptor.getAllValues().get(3));
-        }
+        verifyMultipleSingleScanResults(handlerOrder, handler, requestId2, results2, requestId3,
+                results3);
         assertEquals(mWifiMetrics.getOneshotScanCount(), 3);
         assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_SUCCESS), 3);
 
@@ -819,6 +949,357 @@
                 "results=" + results3.getRawScanResults().length);
     }
 
+
+    /**
+     * Send a single scan request and then a second one satisfied by the first before the first
+     * completes. Verify that only one scan is scheduled.
+     */
+    @Test
+    public void sendSingleScanRequestWhilePreviousScanRunningAndMergeIntoFirstScan() {
+        // Split by frequency to make it easier to determine which results each request is expecting
+        ScanResults results24GHz = ScanResults.create(0, 2400, 2400, 2400, 2450);
+        ScanResults results5GHz = ScanResults.create(0, 5150, 5150, 5175);
+        ScanResults resultsBoth = ScanResults.merge(results24GHz, results5GHz);
+
+        WifiScanner.ScanSettings requestSettings1 = createRequest(WifiScanner.WIFI_BAND_BOTH, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId1 = 12;
+        ScanResults results1 = resultsBoth;
+
+        WifiScanner.ScanSettings requestSettings2 = createRequest(WifiScanner.WIFI_BAND_24_GHZ, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId2 = 13;
+        ScanResults results2 = results24GHz;
+
+
+        startServiceAndLoadDriver();
+
+        when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
+                        any(WifiNative.ScanEventHandler.class))).thenReturn(true);
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder handlerOrder = inOrder(handler);
+        InOrder nativeOrder = inOrder(mWifiScannerImpl);
+
+        // Run scan 1
+        sendSingleScanRequest(controlChannel, requestId1, requestSettings1, null);
+
+        mLooper.dispatchAll();
+        WifiNative.ScanEventHandler eventHandler = verifyStartSingleScan(nativeOrder,
+                computeSingleScanNativeSettings(requestSettings1));
+        verifySuccessfulResponse(handlerOrder, handler, requestId1);
+
+        // Queue scan 2 (will be folded into ongoing scan)
+        sendSingleScanRequest(controlChannel, requestId2, requestSettings2, null);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(handlerOrder, handler, requestId2);
+
+        // dispatch scan 1 results
+        when(mWifiScannerImpl.getLatestSingleScanResults())
+                .thenReturn(resultsBoth.getScanData());
+        eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+
+        mLooper.dispatchAll();
+        verifyMultipleSingleScanResults(handlerOrder, handler, requestId1, results1, requestId2,
+                results2);
+
+        assertEquals(mWifiMetrics.getOneshotScanCount(), 2);
+        assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_SUCCESS), 2);
+    }
+
+    /**
+     * Send a single scan request and then two more before the first completes, one of which is
+     * satisfied by the first scan. Verify that the first two complete together the second scan is
+     * just for the other scan.
+     */
+    @Test
+    public void sendMultipleSingleScanRequestWhilePreviousScanRunningAndMergeOneIntoFirstScan()
+          throws RemoteException {
+        // Split by frequency to make it easier to determine which results each request is expecting
+        ScanResults results2400 = ScanResults.create(0, 2400, 2400, 2400);
+        ScanResults results2450 = ScanResults.create(0, 2450);
+        ScanResults results1and3 = ScanResults.merge(results2400, results2450);
+
+        WifiScanner.ScanSettings requestSettings1 = createRequest(channelsToSpec(2400, 2450), 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId1 = 12;
+        WorkSource workSource1 = new WorkSource(1121);
+        ScanResults results1 = results1and3;
+
+        WifiScanner.ScanSettings requestSettings2 = createRequest(channelsToSpec(2450, 5175), 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId2 = 13;
+        WorkSource workSource2 = new WorkSource(Binder.getCallingUid()); // don't explicitly set
+        ScanResults results2 = ScanResults.create(0, 2450, 5175, 2450);
+
+        WifiScanner.ScanSettings requestSettings3 = createRequest(channelsToSpec(2400), 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId3 = 15;
+        WorkSource workSource3 = new WorkSource(2292);
+        ScanResults results3 = results2400;
+
+        startServiceAndLoadDriver();
+
+        when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
+                        any(WifiNative.ScanEventHandler.class))).thenReturn(true);
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder handlerOrder = inOrder(handler);
+        InOrder nativeOrder = inOrder(mWifiScannerImpl);
+
+        // Run scan 1
+        sendSingleScanRequest(controlChannel, requestId1, requestSettings1, workSource1);
+
+        mLooper.dispatchAll();
+        WifiNative.ScanEventHandler eventHandler1 = verifyStartSingleScan(nativeOrder,
+                computeSingleScanNativeSettings(requestSettings1));
+        verifySuccessfulResponse(handlerOrder, handler, requestId1);
+        verify(mBatteryStats).noteWifiScanStartedFromSource(eq(workSource1));
+
+
+        // Queue scan 2 (will not run because previous is in progress)
+        // uses uid of calling process
+        sendSingleScanRequest(controlChannel, requestId2, requestSettings2, null);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(handlerOrder, handler, requestId2);
+
+        // Queue scan 3 (will be merged into the active scan)
+        sendSingleScanRequest(controlChannel, requestId3, requestSettings3, workSource3);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(handlerOrder, handler, requestId3);
+
+        // dispatch scan 1 results
+        when(mWifiScannerImpl.getLatestSingleScanResults())
+                .thenReturn(results1and3.getScanData());
+        eventHandler1.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+
+        mLooper.dispatchAll();
+        verifyMultipleSingleScanResults(handlerOrder, handler, requestId1, results1, requestId3,
+                results3);
+        // only the requests know at the beginning of the scan get blamed
+        verify(mBatteryStats).noteWifiScanStoppedFromSource(eq(workSource1));
+        verify(mBatteryStats).noteWifiScanStartedFromSource(eq(workSource2));
+
+        // now that the first scan completed we expect the second and third ones to start
+        WifiNative.ScanEventHandler eventHandler2 = verifyStartSingleScan(nativeOrder,
+                computeSingleScanNativeSettings(requestSettings2));
+
+        // dispatch scan 2 and 3 results
+        when(mWifiScannerImpl.getLatestSingleScanResults())
+                .thenReturn(results2.getScanData());
+        eventHandler2.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+
+        mLooper.dispatchAll();
+
+        verifyScanResultsRecieved(handlerOrder, handler, requestId2, results2.getScanData());
+        verifySingleScanCompletedRecieved(handlerOrder, handler, requestId2);
+        assertEquals(mWifiMetrics.getOneshotScanCount(), 3);
+        assertEquals(mWifiMetrics.getScanReturnEntry(WifiMetricsProto.WifiLog.SCAN_SUCCESS), 3);
+
+        verify(mBatteryStats).noteWifiScanStoppedFromSource(eq(workSource2));
+
+        assertDumpContainsRequestLog("addSingleScanRequest", requestId1);
+        assertDumpContainsRequestLog("addSingleScanRequest", requestId2);
+        assertDumpContainsRequestLog("addSingleScanRequest", requestId3);
+        assertDumpContainsCallbackLog("singleScanResults", requestId1,
+                "results=" + results1.getRawScanResults().length);
+        assertDumpContainsCallbackLog("singleScanResults", requestId2,
+                "results=" + results2.getRawScanResults().length);
+        assertDumpContainsCallbackLog("singleScanResults", requestId3,
+                "results=" + results3.getRawScanResults().length);
+    }
+
+    /**
+     * Register a single scan listener and do a single scan
+     */
+    @Test
+    public void registerScanListener() throws Exception {
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        WifiNative.ScanSettings nativeSettings = computeSingleScanNativeSettings(requestSettings);
+        ScanResults results = ScanResults.create(0, 2400, 5150, 5175);
+
+        int requestId = 12;
+        int listenerRequestId = 13;
+
+        startServiceAndLoadDriver();
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl);
+
+        when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
+                        any(WifiNative.ScanEventHandler.class))).thenReturn(true);
+
+        registerScanListener(controlChannel, listenerRequestId);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(order, handler, listenerRequestId);
+
+        sendSingleScanRequest(controlChannel, requestId, requestSettings, null);
+
+        mLooper.dispatchAll();
+        WifiNative.ScanEventHandler eventHandler = verifyStartSingleScan(order, nativeSettings);
+        verifySuccessfulResponse(order, handler, requestId);
+
+        when(mWifiScannerImpl.getLatestSingleScanResults())
+                .thenReturn(results.getRawScanData());
+        eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+
+        mLooper.dispatchAll();
+        verifyScanResultsRecieved(order, handler, requestId, results.getScanData());
+        verifySingleScanCompletedRecieved(order, handler, requestId);
+        verifyScanResultsRecieved(order, handler, listenerRequestId, results.getScanData());
+        verifyNoMoreInteractions(handler);
+
+        assertDumpContainsRequestLog("registerScanListener", listenerRequestId);
+        assertDumpContainsCallbackLog("singleScanResults", listenerRequestId,
+                "results=" + results.getScanData().getResults().length);
+    }
+
+    /**
+     * Register a single scan listener and do a single scan
+     */
+    @Test
+    public void deregisterScanListener() throws Exception {
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        WifiNative.ScanSettings nativeSettings = computeSingleScanNativeSettings(requestSettings);
+        ScanResults results = ScanResults.create(0, 2400, 5150, 5175);
+
+        int requestId = 12;
+        int listenerRequestId = 13;
+
+        startServiceAndLoadDriver();
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder order = inOrder(handler, mWifiScannerImpl);
+
+        when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
+                        any(WifiNative.ScanEventHandler.class))).thenReturn(true);
+
+        registerScanListener(controlChannel, listenerRequestId);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(order, handler, listenerRequestId);
+
+        sendSingleScanRequest(controlChannel, requestId, requestSettings, null);
+
+        mLooper.dispatchAll();
+        WifiNative.ScanEventHandler eventHandler = verifyStartSingleScan(order, nativeSettings);
+        verifySuccessfulResponse(order, handler, requestId);
+
+        deregisterScanListener(controlChannel, listenerRequestId);
+        mLooper.dispatchAll();
+
+        when(mWifiScannerImpl.getLatestSingleScanResults())
+                .thenReturn(results.getRawScanData());
+        eventHandler.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+
+        mLooper.dispatchAll();
+        verifyScanResultsRecieved(order, handler, requestId, results.getScanData());
+        verifySingleScanCompletedRecieved(order, handler, requestId);
+        verifyNoMoreInteractions(handler);
+
+        assertDumpContainsRequestLog("registerScanListener", listenerRequestId);
+        assertDumpContainsRequestLog("deregisterScanListener", listenerRequestId);
+    }
+
+    /**
+     * Send a single scan request and then two more before the first completes. Neither are
+     * satisfied by the first scan. Verify that the first completes and the second two are merged.
+     */
+    @Test
+    public void scanListenerRecievesAllResults() throws RemoteException {
+        WifiScanner.ScanSettings requestSettings1 = createRequest(channelsToSpec(2400), 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId1 = 12;
+        ScanResults results1 = ScanResults.create(0, 2400);
+
+        WifiScanner.ScanSettings requestSettings2 = createRequest(channelsToSpec(2450, 5175), 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId2 = 13;
+        ScanResults results2 = ScanResults.create(0, 2450, 5175, 2450);
+
+        WifiScanner.ScanSettings requestSettings3 = createRequest(channelsToSpec(5150), 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId3 = 15;
+        ScanResults results3 = ScanResults.create(0, 5150, 5150, 5150, 5150);
+
+        WifiNative.ScanSettings nativeSettings2and3 = createSingleScanNativeSettingsForChannels(
+                WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN, channelsToSpec(2450, 5175, 5150));
+        ScanResults results2and3 = ScanResults.merge(results2, results3);
+
+        int listenerRequestId = 13;
+
+
+        startServiceAndLoadDriver();
+
+        when(mWifiScannerImpl.startSingleScan(any(WifiNative.ScanSettings.class),
+                        any(WifiNative.ScanEventHandler.class))).thenReturn(true);
+
+        Handler handler = mock(Handler.class);
+        BidirectionalAsyncChannel controlChannel = connectChannel(handler);
+        InOrder handlerOrder = inOrder(handler);
+        InOrder nativeOrder = inOrder(mWifiScannerImpl);
+
+        // Run scan 1
+        sendSingleScanRequest(controlChannel, requestId1, requestSettings1, null);
+
+        mLooper.dispatchAll();
+        WifiNative.ScanEventHandler eventHandler1 = verifyStartSingleScan(nativeOrder,
+                computeSingleScanNativeSettings(requestSettings1));
+        verifySuccessfulResponse(handlerOrder, handler, requestId1);
+
+
+        // Queue scan 2 (will not run because previous is in progress)
+        sendSingleScanRequest(controlChannel, requestId2, requestSettings2, null);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(handlerOrder, handler, requestId2);
+
+        // Queue scan 3 (will not run because previous is in progress)
+        sendSingleScanRequest(controlChannel, requestId3, requestSettings3, null);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(handlerOrder, handler, requestId3);
+
+        // Register scan listener
+        registerScanListener(controlChannel, listenerRequestId);
+        mLooper.dispatchAll();
+        verifySuccessfulResponse(handlerOrder, handler, listenerRequestId);
+
+        // dispatch scan 1 results
+        when(mWifiScannerImpl.getLatestSingleScanResults())
+                .thenReturn(results1.getScanData());
+        eventHandler1.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+
+        mLooper.dispatchAll();
+        verifyScanResultsRecieved(handlerOrder, handler, requestId1, results1.getScanData());
+        verifySingleScanCompletedRecieved(handlerOrder, handler, requestId1);
+        verifyScanResultsRecieved(handlerOrder, handler, listenerRequestId, results1.getScanData());
+
+        // now that the first scan completed we expect the second and third ones to start
+        WifiNative.ScanEventHandler eventHandler2and3 = verifyStartSingleScan(nativeOrder,
+                nativeSettings2and3);
+
+        // dispatch scan 2 and 3 results
+        when(mWifiScannerImpl.getLatestSingleScanResults())
+                .thenReturn(results2and3.getScanData());
+        eventHandler2and3.onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
+
+        mLooper.dispatchAll();
+
+        verifyMultipleSingleScanResults(handlerOrder, handler, requestId2, results2, requestId3,
+                results3, listenerRequestId, results2and3);
+
+        assertDumpContainsRequestLog("registerScanListener", listenerRequestId);
+        assertDumpContainsCallbackLog("singleScanResults", listenerRequestId,
+                "results=" + results1.getRawScanResults().length);
+        assertDumpContainsCallbackLog("singleScanResults", listenerRequestId,
+                "results=" + results2and3.getRawScanResults().length);
+    }
+
+
     private void doSuccessfulBackgroundScan(WifiScanner.ScanSettings requestSettings,
             WifiNative.ScanSettings nativeSettings) {
         startServiceAndLoadDriver();
@@ -1155,4 +1636,39 @@
         expectSwPnoScan(order, scanSettings.second, scanResults);
         verifyPnoNetworkFoundRecieved(order, handler, requestId, scanResults.getRawScanResults());
     }
+
+    /**
+     * Tries to simulate the race scenario where a client is disconnected immediately after single
+     * scan request is sent to |SingleScanStateMachine|.
+     */
+    @Test
+    public void processSingleScanRequestAfterDisconnect() throws Exception {
+        startServiceAndLoadDriver();
+        BidirectionalAsyncChannel controlChannel = connectChannel(mock(Handler.class));
+        mLooper.dispatchAll();
+
+        // Send the single scan request and then send the disconnect immediately after.
+        WifiScanner.ScanSettings requestSettings = createRequest(WifiScanner.WIFI_BAND_BOTH, 0,
+                0, 20, WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN);
+        int requestId = 10;
+
+        sendSingleScanRequest(controlChannel, requestId, requestSettings, null);
+        // Can't call |disconnect| here because that sends |CMD_CHANNEL_DISCONNECT| followed by
+        // |CMD_CHANNEL_DISCONNECTED|.
+        controlChannel.sendMessage(Message.obtain(null, AsyncChannel.CMD_CHANNEL_DISCONNECTED, 0,
+                0, null));
+
+        // Now process the above 2 actions. This should result in first processing the single scan
+        // request (which forwards the request to SingleScanStateMachine) and then processing the
+        // disconnect after.
+        mLooper.dispatchAll();
+
+        // Now check that we logged the invalid request.
+        String serviceDump = dumpService();
+        Pattern logLineRegex = Pattern.compile("^.+" + "singleScanInvalidRequest: "
+                + "ClientInfo\\[unknown\\],Id=" + requestId + ",bad request$", Pattern.MULTILINE);
+        assertTrue("dump did not contain log with ClientInfo[unknown]: " + serviceDump + "\n",
+                logLineRegex.matcher(serviceDump).find());
+    }
+
 }