Fix misc-macro-parentheses warnings. am: c3385bb331 am: 625f6c743c am: 85ff322aed
am: 5d9933999c

* commit '5d9933999ca37b2eaf3461230dd4ead87ed212fc':
  Fix misc-macro-parentheses warnings.

Change-Id: I1dc4182a5a62beefa815e2eeace2c97f087bba1c
diff --git a/service/Android.mk b/service/Android.mk
index 5c6c839..827d38c 100644
--- a/service/Android.mk
+++ b/service/Android.mk
@@ -108,7 +108,7 @@
 	jni/com_android_server_wifi_WifiNative.cpp \
 	jni/jni_helper.cpp
 
-ifdef INCLUDE_NAN_FEATURE
+ifeq ($(BOARD_HAS_NAN), true)
 LOCAL_SRC_FILES += \
 	jni/com_android_server_wifi_nan_WifiNanNative.cpp
 endif
@@ -128,7 +128,7 @@
 	$(call all-logtags-files-under, java) \
 	$(call all-proto-files-under, proto)
 
-ifndef INCLUDE_NAN_FEATURE
+ifneq ($(BOARD_HAS_NAN), true)
 LOCAL_SRC_FILES := $(filter-out $(call all-java-files-under, \
           java/com/android/server/wifi/nan),$(LOCAL_SRC_FILES))
 endif
diff --git a/service/java/com/android/server/wifi/ScanDetail.java b/service/java/com/android/server/wifi/ScanDetail.java
index 1a5a923..27c6ee8 100644
--- a/service/java/com/android/server/wifi/ScanDetail.java
+++ b/service/java/com/android/server/wifi/ScanDetail.java
@@ -84,39 +84,6 @@
     }
 
     /**
-     * Update the data stored in the scan result with the provided information.
-     *
-     * @param networkDetail NetworkDetail
-     * @param level int
-     * @param wssid WifiSsid
-     * @param ssid String
-     * @param flags String
-     * @param freq int
-     * @param tsf long
-     */
-    public void updateResults(NetworkDetail networkDetail, int level, WifiSsid wssid, String ssid,
-                              String flags, int freq, long tsf) {
-        mScanResult.level = level;
-        mScanResult.wifiSsid = wssid;
-        // Keep existing API
-        mScanResult.SSID = ssid;
-        mScanResult.capabilities = flags;
-        mScanResult.frequency = freq;
-        mScanResult.timestamp = tsf;
-        mSeen = System.currentTimeMillis();
-        //mScanResult.seen = mSeen;
-        mScanResult.channelWidth = networkDetail.getChannelWidth();
-        mScanResult.centerFreq0 = networkDetail.getCenterfreq0();
-        mScanResult.centerFreq1 = networkDetail.getCenterfreq1();
-        if (networkDetail.is80211McResponderSupport()) {
-            mScanResult.setFlag(ScanResult.FLAG_80211mc_RESPONDER);
-        }
-        if (networkDetail.isInterworking()) {
-            mScanResult.setFlag(ScanResult.FLAG_PASSPOINT_NETWORK);
-        }
-    }
-
-    /**
      * Store ANQ element information
      *
      * @param anqpElements Map<Constants.ANQPElementType, ANQPElement>
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index e2183e3..6bc4e89 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -151,8 +151,6 @@
  *
  */
 public class WifiConfigManager {
-    private static boolean sVDBG = false;
-    private static boolean sVVDBG = false;
     public static final String TAG = "WifiConfigManager";
     public static final int MAX_TX_PACKET_FOR_FULL_SCANS = 8;
     public static final int MAX_RX_PACKET_FOR_FULL_SCANS = 16;
@@ -195,10 +193,12 @@
             5,  //  threshold for DISABLED_AUTHENTICATION_FAILURE
             5,  //  threshold for DISABLED_DHCP_FAILURE
             5,  //  threshold for DISABLED_DNS_FAILURE
+            1,  //  threshold for DISABLED_WPS_START
             6,  //  threshold for DISABLED_TLS_VERSION_MISMATCH
             1,  //  threshold for DISABLED_AUTHENTICATION_NO_CREDENTIALS
             1,  //  threshold for DISABLED_NO_INTERNET
-            1   //  threshold for DISABLED_BY_WIFI_MANAGER
+            1,  //  threshold for DISABLED_BY_WIFI_MANAGER
+            1   //  threshold for DISABLED_BY_USER_SWITCH
     };
 
     /**
@@ -211,10 +211,12 @@
             5,                  // threshold for DISABLED_AUTHENTICATION_FAILURE
             5,                  // threshold for DISABLED_DHCP_FAILURE
             5,                  // threshold for DISABLED_DNS_FAILURE
+            0,                  // threshold for DISABLED_WPS_START
             Integer.MAX_VALUE,  // threshold for DISABLED_TLS_VERSION
             Integer.MAX_VALUE,  // threshold for DISABLED_AUTHENTICATION_NO_CREDENTIALS
             Integer.MAX_VALUE,  // threshold for DISABLED_NO_INTERNET
-            Integer.MAX_VALUE   // threshold for DISABLED_BY_WIFI_MANAGER
+            Integer.MAX_VALUE,  // threshold for DISABLED_BY_WIFI_MANAGER
+            Integer.MAX_VALUE   // threshold for DISABLED_BY_USER_SWITCH
     };
 
     public final AtomicBoolean mEnableAutoJoinWhenAssociated = new AtomicBoolean();
@@ -222,7 +224,6 @@
     public final AtomicBoolean mEnableRssiPollWhenAssociated = new AtomicBoolean(true);
     public final AtomicInteger mThresholdSaturatedRssi5 = new AtomicInteger();
     public final AtomicInteger mThresholdQualifiedRssi24 = new AtomicInteger();
-    public final AtomicInteger mEnableVerboseLogging = new AtomicInteger(0);
     public final AtomicInteger mAlwaysEnableScansWhileAssociated = new AtomicInteger(0);
     public final AtomicInteger mMaxNumActiveChannelsForPartialScans = new AtomicInteger();
 
@@ -272,13 +273,13 @@
     private final UserManager mUserManager;
     private final Object mActiveScanDetailLock = new Object();
 
+    private boolean mVerboseLoggingEnabled = false;
     private Context mContext;
     private FrameworkFacade mFacade;
     private Clock mClock;
     private IpConfigStore mIpconfigStore;
     private DelayedDiskWrite mWriter;
     private boolean mOnlyLinkSameCredentialConfigurations;
-    private boolean mShowNetworks = false;
     private int mCurrentUserId = UserHandle.USER_SYSTEM;
 
     /* Stores a map of NetworkId to ScanCache */
@@ -339,12 +340,7 @@
         mClock = clock;
         mKeyStore = keyStore;
         mUserManager = userManager;
-
-        if (mShowNetworks) {
-            mLocalLog = wifiNative.getLocalLog();
-        } else {
-            mLocalLog = null;
-        }
+        mLocalLog = wifiNative.getLocalLog();
 
         mOnlyLinkSameCredentialConfigurations = mContext.getResources().getBoolean(
                 R.bool.config_wifi_only_link_same_credential_configurations);
@@ -397,8 +393,7 @@
         mWriter = new DelayedDiskWrite();
         mIpconfigStore = new IpConfigStore(mWriter);
         mWifiNetworkHistory = new WifiNetworkHistory(context, mLocalLog, mWriter);
-        mWifiConfigStore =
-                new WifiConfigStore(wifiNative, mKeyStore, mLocalLog, mShowNetworks, true);
+        mWifiConfigStore = new WifiConfigStore(wifiNative, mKeyStore, mLocalLog);
     }
 
     public void trimANQPCache(boolean all) {
@@ -406,18 +401,12 @@
     }
 
     void enableVerboseLogging(int verbose) {
-        mEnableVerboseLogging.set(verbose);
         if (verbose > 0) {
-            sVDBG = true;
-            mShowNetworks = true;
+            mVerboseLoggingEnabled = true;
         } else {
-            sVDBG = false;
+            mVerboseLoggingEnabled = false;
         }
-        if (verbose > 1) {
-            sVVDBG = true;
-        } else {
-            sVVDBG = false;
-        }
+        mWifiConfigStore.enableVerboseLogging(mVerboseLoggingEnabled);
     }
 
     /**
@@ -434,6 +423,10 @@
         return mConfiguredNetworks.sizeForCurrentUser();
     }
 
+    boolean getVerboseLoggingEnabled() {
+        return mVerboseLoggingEnabled;
+    }
+
     /**
      * Fetch the list of currently saved networks (i.e. all configured networks, excluding
      * ephemeral networks).
@@ -541,50 +534,6 @@
     }
 
     /**
-     * Fetch the list of currently saved networks (i.e. all configured networks, excluding
-     * ephemeral networks) that were recently seen.
-     *
-     * @param scanResultAgeMs The maximum age (in ms) of scan results for which we calculate the
-     * RSSI values
-     * @param copy If true, the returned list will contain copies of the configurations for the
-     * saved networks. Otherwise, the returned list will contain references to these
-     * configurations.
-     * @return List of networks
-     */
-    List<WifiConfiguration> getRecentSavedNetworks(int scanResultAgeMs, boolean copy) {
-        List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
-
-        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
-            if (config.ephemeral) {
-                // Do not enumerate and return this configuration to anyone (e.g. WiFi Picker);
-                // treat it as unknown instead. This configuration can still be retrieved
-                // directly by its key or networkId.
-                continue;
-            }
-
-            // Calculate the RSSI for scan results that are more recent than scanResultAgeMs.
-            ScanDetailCache cache = getScanDetailCache(config);
-            if (cache == null) {
-                continue;
-            }
-            config.setVisibility(cache.getVisibility(scanResultAgeMs));
-            if (config.visibility == null) {
-                continue;
-            }
-            if (config.visibility.rssi5 == WifiConfiguration.INVALID_RSSI
-                    && config.visibility.rssi24 == WifiConfiguration.INVALID_RSSI) {
-                continue;
-            }
-            if (copy) {
-                networks.add(new WifiConfiguration(config));
-            } else {
-                networks.add(config);
-            }
-        }
-        return networks;
-    }
-
-    /**
      *  Update the configuration and BSSID with latest RSSI value.
      */
     void updateConfiguration(WifiInfo info) {
@@ -603,7 +552,7 @@
                 // Average the RSSI value
                 result.averageRssi(previousRssi, previousSeen,
                         WifiQualifiedNetworkSelector.SCAN_RESULT_MAXIMUNM_AGE);
-                if (sVDBG) {
+                if (mVerboseLoggingEnabled) {
                     logd("updateConfiguration freq=" + result.frequency
                             + " BSSID=" + result.BSSID
                             + " RSSI=" + result.level
@@ -684,7 +633,7 @@
      * @return false if the network id is invalid
      */
     boolean selectNetwork(WifiConfiguration config, boolean updatePriorities, int uid) {
-        if (sVDBG) localLogNetwork("selectNetwork", config.networkId);
+        if (mVerboseLoggingEnabled) localLogNetwork("selectNetwork", config.networkId);
         if (config.networkId == INVALID_NETWORK_ID) return false;
         if (!WifiConfigurationUtil.isVisibleToAnyProfile(config,
                 mUserManager.getProfiles(mCurrentUserId))) {
@@ -763,8 +712,10 @@
             return new NetworkUpdateResult(INVALID_NETWORK_ID);
         }
 
-        if (sVDBG) localLogNetwork("WifiConfigManager: saveNetwork netId", config.networkId);
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
+            localLogNetwork("WifiConfigManager: saveNetwork netId", config.networkId);
+        }
+        if (mVerboseLoggingEnabled) {
             logd("WifiConfigManager saveNetwork,"
                     + " size=" + Integer.toString(mConfiguredNetworks.sizeForAllUsers())
                     + " (for all users)"
@@ -774,7 +725,7 @@
         }
 
         if (mDeletedEphemeralSSIDs.remove(config.SSID)) {
-            if (sVDBG) {
+            if (mVerboseLoggingEnabled) {
                 logd("WifiConfigManager: removed from ephemeral blacklist: " + config.SSID);
             }
             // NOTE: This will be flushed to disk as part of the addOrUpdateNetworkNative call
@@ -785,18 +736,22 @@
         NetworkUpdateResult result = addOrUpdateNetworkNative(config, uid);
         int netId = result.getNetworkId();
 
-        if (sVDBG) localLogNetwork("WifiConfigManager: saveNetwork got it back netId=", netId);
+        if (mVerboseLoggingEnabled) {
+            localLogNetwork("WifiConfigManager: saveNetwork got it back netId=", netId);
+        }
 
         conf = mConfiguredNetworks.getForCurrentUser(netId);
         if (conf != null) {
             if (!conf.getNetworkSelectionStatus().isNetworkEnabled()) {
-                if (sVDBG) localLog("WifiConfigManager: re-enabling: " + conf.SSID);
+                if (mVerboseLoggingEnabled) {
+                    localLog("WifiConfigManager: re-enabling: " + conf.SSID);
+                }
 
                 // reenable autojoin, since new information has been provided
                 updateNetworkSelectionStatus(netId,
                         WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
             }
-            if (sVDBG) {
+            if (mVerboseLoggingEnabled) {
                 logd("WifiConfigManager: saveNetwork got config back netId="
                         + Integer.toString(netId)
                         + " uid=" + Integer.toString(config.creatorUid));
@@ -889,7 +844,7 @@
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
     boolean forgetNetwork(int netId) {
-        if (mShowNetworks) localLogNetwork("forgetNetwork", netId);
+        if (mVerboseLoggingEnabled) localLogNetwork("forgetNetwork", netId);
         if (!removeNetwork(netId)) {
             loge("Failed to forget network " + netId);
             return false;
@@ -914,7 +869,7 @@
             return WifiConfiguration.INVALID_NETWORK_ID;
         }
 
-        if (mShowNetworks) localLogNetwork("addOrUpdateNetwork id=", config.networkId);
+        if (mVerboseLoggingEnabled) localLogNetwork("addOrUpdateNetwork id=", config.networkId);
         if (config.isPasspoint()) {
             /* create a temporary SSID with providerFriendlyName */
             Long csum = getChecksum(config.FQDN);
@@ -1153,7 +1108,7 @@
      * @return {@code true} if it succeeds, {@code false} otherwise
      */
     boolean removeNetwork(int netId) {
-        if (mShowNetworks) localLogNetwork("removeNetwork", netId);
+        if (mVerboseLoggingEnabled) localLogNetwork("removeNetwork", netId);
         WifiConfiguration config = mConfiguredNetworks.getForCurrentUser(netId);
         if (!removeConfigAndSendBroadcastIfNeeded(config)) {
             return false;
@@ -1191,7 +1146,7 @@
             return false;
         }
         String key = config.configKey();
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
             logd("removeNetwork " + " key=" + key + " config.id=" + config.networkId);
         }
         writeIpAndProxyConfigurations();
@@ -1242,7 +1197,7 @@
             if (app.uid != config.creatorUid || !app.packageName.equals(config.creatorName)) {
                 continue;
             }
-            if (mShowNetworks) {
+            if (mVerboseLoggingEnabled) {
                 localLog("Removing network " + config.SSID
                          + ", application \"" + app.packageName + "\" uninstalled"
                          + " from user " + UserHandle.getUserId(app.uid));
@@ -1265,7 +1220,7 @@
                 continue;
             }
             success &= removeNetwork(config.networkId);
-            if (mShowNetworks) {
+            if (mVerboseLoggingEnabled) {
                 localLog("Removing network " + config.SSID
                         + ", user " + userId + " removed");
             }
@@ -1287,31 +1242,34 @@
         if (config == null) {
             return false;
         }
-
         updateNetworkSelectionStatus(
                 config, WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
         setLatestUserSelectedConfiguration(config);
         boolean ret = true;
         if (disableOthers) {
             ret = selectNetworkWithoutBroadcast(config.networkId);
-            if (sVDBG) {
-                localLogNetwork("enableNetwork(disableOthers=true, uid=" + uid + ") ",
-                        config.networkId);
+            if (ret) {
+                if (mVerboseLoggingEnabled) {
+                    localLogNetwork("enableNetwork(disableOthers=true, uid=" + uid + ") ",
+                            config.networkId);
+                }
+                markAllNetworksDisabledExcept(config.networkId,
+                        WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER);
+                updateLastConnectUid(config, uid);
+                writeKnownNetworkHistory();
+                sendConfiguredNetworksChangedBroadcast();
             }
-            updateLastConnectUid(config, uid);
-            writeKnownNetworkHistory();
-            sendConfiguredNetworksChangedBroadcast();
         } else {
-            if (sVDBG) localLogNetwork("enableNetwork(disableOthers=false) ", config.networkId);
+            if (mVerboseLoggingEnabled) {
+                localLogNetwork("enableNetwork(disableOthers=false) ", config.networkId);
+            }
             sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_CONFIG_CHANGE);
         }
         return ret;
     }
 
     boolean selectNetworkWithoutBroadcast(int netId) {
-        return mWifiConfigStore.selectNetwork(
-                mConfiguredNetworks.getForCurrentUser(netId),
-                mConfiguredNetworks.valuesForCurrentUser());
+        return mWifiConfigStore.selectNetwork(mConfiguredNetworks.getForCurrentUser(netId));
     }
 
     /**
@@ -1328,13 +1286,17 @@
         mWifiConfigStore.disableAllNetworks(mConfiguredNetworks.valuesForCurrentUser());
     }
 
-    /**
-     * Disable a network. Note that there is no saveConfig operation.
-     * @param netId network to be disabled
-     * @return {@code true} if it succeeds, {@code false} otherwise
-     */
-    boolean disableNetwork(int netId) {
-        return mWifiConfigStore.disableNetwork(mConfiguredNetworks.getForCurrentUser(netId));
+    /* Mark all networks except specified netId as disabled */
+    private void markAllNetworksDisabledExcept(int netId, int disableReason) {
+        for (WifiConfiguration config : mConfiguredNetworks.valuesForCurrentUser()) {
+            if (config != null && config.networkId != netId) {
+                updateNetworkSelectionStatus(config, disableReason);
+            }
+        }
+    }
+
+    private void markAllNetworksDisabled(int disableReason) {
+        markAllNetworksDisabledExcept(WifiConfiguration.INVALID_NETWORK_ID, disableReason);
     }
 
     /**
@@ -1385,11 +1347,13 @@
     }
 
     /**
-     * Check the config. If it is temporarily disabled, check the disable time is expired or not, If
-     * expired, enabled it again for qualified network selection.
+     * Attempt to re-enable a network for qualified network selection, if this network was either:
+     *      a) Previously temporarily disabled, but its disable timeout has expired, or
+     *      b) Previously disabled because of a user switch, but is now visible to the current
+     *         user.
      * @param networkId the id of the network to be checked for possible unblock (due to timeout)
-     * @return true if network status has been changed
-     *         false network status is not changed
+     * @return true if the network identified by {@param networkId} was re-enabled for qualified
+     *         network selection, false otherwise.
      */
     public boolean tryEnableQualifiedNetwork(int networkId) {
         WifiConfiguration config = getWifiConfiguration(networkId);
@@ -1401,11 +1365,14 @@
     }
 
     /**
-     * Check the config. If it is temporarily disabled, check the disable is expired or not, If
-     * expired, enabled it again for qualified network selection.
-     * @param config network to be checked for possible unblock (due to timeout)
-     * @return true if network status has been changed
-     *         false network status is not changed
+     * Attempt to re-enable a network for qualified network selection, if this network was either:
+     *      a) Previously temporarily disabled, but its disable timeout has expired, or
+     *      b) Previously disabled because of a user switch, but is now visible to the current
+     *         user.
+     * @param config configuration for the network to be re-enabled for network selection. The
+     *               network corresponding to the config must be visible to the current user.
+     * @return true if the network identified by {@param config} was re-enabled for qualified
+     *         network selection, false otherwise.
      */
     private boolean tryEnableQualifiedNetwork(WifiConfiguration config) {
         WifiConfiguration.NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
@@ -1417,9 +1384,14 @@
                     >= NETWORK_SELECTION_DISABLE_TIMEOUT[
                     networkStatus.getNetworkSelectionDisableReason()]) {
                 updateNetworkSelectionStatus(config.networkId,
-                        networkStatus.NETWORK_SELECTION_ENABLE);
+                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
                 return true;
             }
+        } else if (networkStatus.isDisabledByReason(
+                WifiConfiguration.NetworkSelectionStatus.DISABLED_DUE_TO_USER_SWITCH)) {
+            updateNetworkSelectionStatus(config.networkId,
+                    WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+            return true;
         }
         return false;
     }
@@ -1440,15 +1412,14 @@
         WifiConfiguration.NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
         if (reason < 0 || reason >= WifiConfiguration.NetworkSelectionStatus
                 .NETWORK_SELECTION_DISABLED_MAX) {
-            localLog("Invalid Network disable reason:" + reason);
+            localLog("Invalid Network disable reason: " + reason);
             return false;
         }
 
         if (reason == WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
             if (networkStatus.isNetworkEnabled()) {
                 if (DBG) {
-                    localLog("Need not change Qualified network Selection status since"
-                            + " already enabled");
+                    localLog("Do nothing. Network is already enabled!");
                 }
                 return false;
             }
@@ -1459,17 +1430,13 @@
                     WifiConfiguration.NetworkSelectionStatus
                     .INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
             networkStatus.clearDisableReasonCounter();
-            String disableTime = DateFormat.getDateTimeInstance().format(new Date());
-            if (DBG) {
-                localLog("Re-enable network: " + config.SSID + " at " + disableTime);
-            }
+            config.status = Status.ENABLED;
             sendConfiguredNetworksChangedBroadcast(config, WifiManager.CHANGE_REASON_CONFIG_CHANGE);
         } else {
             //disable the network
             if (networkStatus.isNetworkPermanentlyDisabled()) {
-                //alreay permanent disable
                 if (DBG) {
-                    localLog("Do nothing. Alreay permanent disabled! "
+                    localLog("Do nothing. Network is already permanently disabled! Disable reason: "
                             + WifiConfiguration.NetworkSelectionStatus
                             .getNetworkDisableReasonString(reason));
                 }
@@ -1477,23 +1444,14 @@
             } else if (networkStatus.isNetworkTemporaryDisabled()
                     && reason < WifiConfiguration.NetworkSelectionStatus
                     .DISABLED_TLS_VERSION_MISMATCH) {
-                //alreay temporarily disable
                 if (DBG) {
-                    localLog("Do nothing. Already temporarily disabled! "
+                    localLog("Do nothing. Network is already temporarily disabled! Disable reason: "
                             + WifiConfiguration.NetworkSelectionStatus
                             .getNetworkDisableReasonString(reason));
                 }
                 return false;
             }
-
-            if (networkStatus.isNetworkEnabled()) {
-                disableNetworkNative(config);
-                sendConfiguredNetworksChangedBroadcast(config,
-                        WifiManager.CHANGE_REASON_CONFIG_CHANGE);
-                localLog("Disable network " + config.SSID + " reason:"
-                        + WifiConfiguration.NetworkSelectionStatus
-                        .getNetworkDisableReasonString(reason));
-            }
+            disableNetworkNative(config);
             if (reason < WifiConfiguration.NetworkSelectionStatus.DISABLED_TLS_VERSION_MISMATCH) {
                 networkStatus.setNetworkSelectionStatus(WifiConfiguration.NetworkSelectionStatus
                         .NETWORK_SELECTION_TEMPORARY_DISABLED);
@@ -1501,14 +1459,17 @@
             } else {
                 networkStatus.setNetworkSelectionStatus(WifiConfiguration.NetworkSelectionStatus
                         .NETWORK_SELECTION_PERMANENTLY_DISABLED);
+                config.status = Status.DISABLED;
+                sendConfiguredNetworksChangedBroadcast(
+                        config, WifiManager.CHANGE_REASON_CONFIG_CHANGE);
             }
             networkStatus.setNetworkSelectionDisableReason(reason);
-            if (DBG) {
-                String disableTime = DateFormat.getDateTimeInstance().format(new Date());
-                localLog("Network:" + config.SSID + "Configure new status:"
-                        + networkStatus.getNetworkStatusString() + " with reason:"
-                        + networkStatus.getNetworkDisableReasonString() + " at: " + disableTime);
-            }
+        }
+        if (DBG) {
+            String time = DateFormat.getDateTimeInstance().format(new Date());
+            localLog("Network: " + config.SSID + " Configure new status: "
+                    + networkStatus.getNetworkStatusString() + " with reason: "
+                    + networkStatus.getNetworkDisableReasonString() + " at: " + time);
         }
         return true;
     }
@@ -1528,8 +1489,12 @@
      * @return Wps result containing status and pin
      */
     WpsResult startWpsWithPinFromAccessPoint(WpsInfo config) {
-        return mWifiConfigStore.startWpsWithPinFromAccessPoint(
-                config, mConfiguredNetworks.valuesForCurrentUser());
+        WpsResult result = mWifiConfigStore.startWpsWithPinFromAccessPoint(config);
+        /* WPS leaves all networks disabled */
+        if (result.status == WpsResult.Status.SUCCESS) {
+            markAllNetworksDisabled(WifiConfiguration.NetworkSelectionStatus.DISABLED_WPS_START);
+        }
+        return result;
     }
 
     /**
@@ -1538,8 +1503,12 @@
      * @return WpsResult indicating status and pin
      */
     WpsResult startWpsWithPinFromDevice(WpsInfo config) {
-        return mWifiConfigStore.startWpsWithPinFromDevice(
-            config, mConfiguredNetworks.valuesForCurrentUser());
+        WpsResult result = mWifiConfigStore.startWpsWithPinFromDevice(config);
+        /* WPS leaves all networks disabled */
+        if (result.status == WpsResult.Status.SUCCESS) {
+            markAllNetworksDisabled(WifiConfiguration.NetworkSelectionStatus.DISABLED_WPS_START);
+        }
+        return result;
     }
 
     /**
@@ -1548,8 +1517,12 @@
      * @return WpsResult indicating status and pin
      */
     WpsResult startWpsPbc(WpsInfo config) {
-        return mWifiConfigStore.startWpsPbc(
-            config, mConfiguredNetworks.valuesForCurrentUser());
+        WpsResult result = mWifiConfigStore.startWpsPbc(config);
+        /* WPS leaves all networks disabled */
+        if (result.status == WpsResult.Status.SUCCESS) {
+            markAllNetworksDisabled(WifiConfiguration.NetworkSelectionStatus.DISABLED_WPS_START);
+        }
+        return result;
     }
 
     /**
@@ -1666,7 +1639,7 @@
             final String configKey = entry.getKey();
             final WifiConfiguration config = entry.getValue();
             if (!configKey.equals(config.configKey())) {
-                if (mShowNetworks) {
+                if (mVerboseLoggingEnabled) {
                     log("Ignoring network " + config.networkId + " because the configKey loaded "
                             + "from wpa_supplicant.conf is not valid.");
                 }
@@ -1680,7 +1653,7 @@
 
         sendConfiguredNetworksChangedBroadcast();
 
-        if (mShowNetworks) {
+        if (mVerboseLoggingEnabled) {
             localLog("loadConfiguredNetworks loaded " + mConfiguredNetworks.sizeForAllUsers()
                     + " networks (for all users)");
         }
@@ -1729,7 +1702,7 @@
         Map<String, String> data = mWifiConfigStore.readNetworkVariablesFromSupplicantFile(key);
         long end = SystemClock.elapsedRealtimeNanos();
 
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
             localLog("readNetworkVariableFromSupplicantFile configKey=[" + configKey + "] key="
                     + key + " duration=" + (long) (end - start));
         }
@@ -1842,7 +1815,7 @@
     }
 
     public void setAndEnableLastSelectedConfiguration(int netId) {
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
             logd("setLastSelectedConfiguration " + Integer.toString(netId));
         }
         if (netId == WifiConfiguration.INVALID_NETWORK_ID) {
@@ -1858,7 +1831,7 @@
                 mLastSelectedTimeStamp = mClock.elapsedRealtime();
                 updateNetworkSelectionStatus(netId,
                         WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
-                if (sVDBG) {
+                if (mVerboseLoggingEnabled) {
                     logd("setLastSelectedConfiguration now: " + mLastSelectedConfiguration);
                 }
             }
@@ -1927,7 +1900,9 @@
          * refer to an existing configuration.
          */
 
-        if (sVDBG) localLog("addOrUpdateNetworkNative " + config.getPrintableSsid());
+        if (mVerboseLoggingEnabled) {
+            localLog("addOrUpdateNetworkNative " + config.getPrintableSsid());
+        }
         if (config.isPasspoint() && !mMOManager.isEnabled()) {
             Log.e(TAG, "Passpoint is not enabled");
             return new NetworkUpdateResult(INVALID_NETWORK_ID);
@@ -2052,17 +2027,14 @@
         sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
 
         if (newNetwork) {
+            // New networks start out disabled according to public API.
+            updateNetworkSelectionStatus(currentConfig,
+                    WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER);
             currentConfig.creationTime = sb.toString();
         } else {
             currentConfig.updateTime = sb.toString();
         }
 
-        if (currentConfig.status == WifiConfiguration.Status.ENABLED) {
-            // Make sure autojoin remain in sync with user modifying the configuration
-            updateNetworkSelectionStatus(currentConfig.networkId,
-                    WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
-        }
-
         if (currentConfig.configKey().equals(getLastSelectedConfiguration())
                 && currentConfig.ephemeral) {
             // Make the config non-ephemeral since the user just explicitly clicked it.
@@ -2073,7 +2045,9 @@
             }
         }
 
-        if (sVDBG) log("will read network variables netId=" + Integer.toString(netId));
+        if (mVerboseLoggingEnabled) {
+            log("will read network variables netId=" + Integer.toString(netId));
+        }
 
         readNetworkVariables(currentConfig);
         // When we read back the config from wpa_supplicant, some of the default values are set
@@ -2305,7 +2279,7 @@
             if (config.defaultGwMacAddress != null && link.defaultGwMacAddress != null) {
                 // If both default GW are known, link only if they are equal
                 if (config.defaultGwMacAddress.equals(link.defaultGwMacAddress)) {
-                    if (sVDBG) {
+                    if (mVerboseLoggingEnabled) {
                         logd("linkConfiguration link due to same gw " + link.SSID
                                 + " and " + config.SSID + " GW " + config.defaultGwMacAddress);
                     }
@@ -2321,7 +2295,7 @@
 
                     for (String abssid : getScanDetailCache(config).keySet()) {
                         for (String bbssid : linkedScanDetailCache.keySet()) {
-                            if (sVVDBG) {
+                            if (mVerboseLoggingEnabled) {
                                 logd("linkConfiguration try to link due to DBDC BSSID match "
                                         + link.SSID + " and " + config.SSID + " bssida " + abssid
                                         + " bssidb " + bbssid);
@@ -2350,7 +2324,7 @@
             }
 
             if (doLink) {
-                if (sVDBG) {
+                if (mVerboseLoggingEnabled) {
                     logd("linkConfiguration: will link " + link.configKey()
                             + " and " + config.configKey());
                 }
@@ -2369,7 +2343,7 @@
             } else {
                 if (link.linkedConfigurations != null
                         && (link.linkedConfigurations.get(config.configKey()) != null)) {
-                    if (sVDBG) {
+                    if (mVerboseLoggingEnabled) {
                         logd("linkConfiguration: un-link " + config.configKey()
                                 + " from " + link.configKey());
                     }
@@ -2377,7 +2351,7 @@
                 }
                 if (config.linkedConfigurations != null
                         && (config.linkedConfigurations.get(link.configKey()) != null)) {
-                    if (sVDBG) {
+                    if (mVerboseLoggingEnabled) {
                         logd("linkConfiguration: un-link " + link.configKey()
                                 + " from " + config.configKey());
                     }
@@ -2400,7 +2374,7 @@
             return null;
         }
 
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
             StringBuilder dbg = new StringBuilder();
             dbg.append("makeChannelList age=" + Integer.toString(age)
                     + " for " + config.configKey()
@@ -2422,7 +2396,7 @@
                 if (numChannels > mMaxNumActiveChannelsForPartialScans.get()) {
                     break;
                 }
-                if (sVDBG) {
+                if (mVerboseLoggingEnabled) {
                     boolean test = (now_ms - result.seen) < age;
                     logd("has " + result.BSSID + " freq=" + Integer.toString(result.frequency)
                             + " age=" + Long.toString(now_ms - result.seen) + " ?=" + test);
@@ -2446,7 +2420,7 @@
                 }
                 for (ScanDetail scanDetail : getScanDetailCache(linked).values()) {
                     ScanResult result = scanDetail.getScanResult();
-                    if (sVDBG) {
+                    if (mVerboseLoggingEnabled) {
                         logd("has link: " + result.BSSID
                                 + " freq=" + Integer.toString(result.frequency)
                                 + " age=" + Long.toString(now_ms - result.seen));
@@ -2635,7 +2609,7 @@
 
         if (scanDetailCache.size() > (MAX_NUM_SCAN_CACHE_ENTRIES + 64)) {
             long now_dbg = 0;
-            if (sVVDBG) {
+            if (mVerboseLoggingEnabled) {
                 logd(" Will trim config " + config.configKey()
                         + " size " + scanDetailCache.size());
 
@@ -2648,7 +2622,7 @@
             // Since this operation is expensive, make sure it is not performed
             // until the cache has grown significantly above the trim treshold
             scanDetailCache.trim(MAX_NUM_SCAN_CACHE_ENTRIES);
-            if (sVVDBG) {
+            if (mVerboseLoggingEnabled) {
                 long diff = SystemClock.elapsedRealtimeNanos() - now_dbg;
                 logd(" Finished trimming config, time(ns) " + diff);
                 for (ScanDetail sd : scanDetailCache.values()) {
@@ -2800,7 +2774,8 @@
         final List<WifiConfiguration> hiddenConfigurations =
                 mConfiguredNetworks.handleUserSwitch(mCurrentUserId);
         for (WifiConfiguration network : hiddenConfigurations) {
-            disableNetworkNative(network);
+            updateNetworkSelectionStatus(
+                    network, WifiConfiguration.NetworkSelectionStatus.DISABLED_DUE_TO_USER_SWITCH);
         }
         enableAllNetworks();
 
@@ -2899,7 +2874,7 @@
         }
 
         if (ipChanged || proxyChanged || isNewNetwork) {
-            if (sVDBG) {
+            if (mVerboseLoggingEnabled) {
                 logd("writeIpAndProxyConfigurationsOnChange: " + currentConfig.SSID + " -> "
                         + newConfig.SSID + " path: " + IP_CONFIG_FILE);
             }
@@ -2926,7 +2901,7 @@
 
         config.SSID = "\"" + result.SSID + "\"";
 
-        if (sVDBG) {
+        if (mVerboseLoggingEnabled) {
             logd("WifiConfiguration from scan results "
                     + config.SSID + " cap " + result.capabilities);
         }
diff --git a/service/java/com/android/server/wifi/WifiConfigStore.java b/service/java/com/android/server/wifi/WifiConfigStore.java
index 25ab449..68796f6 100644
--- a/service/java/com/android/server/wifi/WifiConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiConfigStore.java
@@ -19,7 +19,6 @@
 import android.net.IpConfiguration.IpAssignment;
 import android.net.IpConfiguration.ProxySettings;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.Status;
 import android.net.wifi.WifiEnterpriseConfig;
 import android.net.wifi.WifiSsid;
 import android.net.wifi.WpsInfo;
@@ -86,35 +85,25 @@
 
     // Value stored by supplicant to requirePMF
     public static final int STORED_VALUE_FOR_REQUIRE_PMF = 2;
-
     private static final boolean DBG = true;
-    private static boolean VDBG = false;
 
     private final LocalLog mLocalLog;
     private final WpaConfigFileObserver mFileObserver;
     private final WifiNative mWifiNative;
     private final KeyStore mKeyStore;
-    private final boolean mShowNetworks;
     private final HashSet<String> mBssidBlacklist = new HashSet<String>();
-
     private final BackupManagerProxy mBackupManagerProxy;
 
-    WifiConfigStore(WifiNative wifiNative, KeyStore keyStore, LocalLog localLog,
-            boolean showNetworks, boolean verboseDebug) {
+    private boolean mVerboseLoggingEnabled = false;
+
+    WifiConfigStore(WifiNative wifiNative, KeyStore keyStore, LocalLog localLog) {
         mWifiNative = wifiNative;
         mKeyStore = keyStore;
-        mShowNetworks = showNetworks;
         mBackupManagerProxy = new BackupManagerProxy();
 
-        if (mShowNetworks) {
-            mLocalLog = localLog;
-            mFileObserver = new WpaConfigFileObserver();
-            mFileObserver.startWatching();
-        } else {
-            mLocalLog = null;
-            mFileObserver = null;
-        }
-        VDBG = verboseDebug;
+        mLocalLog = localLog;
+        mFileObserver = new WpaConfigFileObserver();
+        mFileObserver.startWatching();
     }
 
     private static String removeDoubleQuotes(String string) {
@@ -247,7 +236,7 @@
         if (config == null) {
             return;
         }
-        if (VDBG) localLog("readNetworkVariables: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("readNetworkVariables: " + config.networkId);
         int netId = config.networkId;
         if (netId < 0) {
             return;
@@ -372,7 +361,7 @@
                 return lastPriority;
             }
             String[] lines = listStr.split("\n");
-            if (mShowNetworks) {
+            if (mVerboseLoggingEnabled) {
                 localLog("loadNetworks:  ");
                 for (String net : lines) {
                     localLog(net);
@@ -418,7 +407,7 @@
                 config.setIpAssignment(IpAssignment.DHCP);
                 config.setProxySettings(ProxySettings.NONE);
                 if (!WifiServiceImpl.isValid(config)) {
-                    if (mShowNetworks) {
+                    if (mVerboseLoggingEnabled) {
                         localLog("Ignoring network " + config.networkId + " because configuration "
                                 + "loaded from wpa_supplicant.conf is not valid.");
                     }
@@ -438,7 +427,7 @@
                 final WifiConfiguration duplicateConfig = configs.put(configKey, config);
                 if (duplicateConfig != null) {
                     // The network is already known. Overwrite the duplicate entry.
-                    if (mShowNetworks) {
+                    if (mVerboseLoggingEnabled) {
                         localLog("Replacing duplicate network " + duplicateConfig.networkId
                                 + " with " + config.networkId + ".");
                     }
@@ -608,7 +597,7 @@
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("saveNetwork: " + netId);
+        if (mVerboseLoggingEnabled) localLog("saveNetwork: " + netId);
         if (config.SSID != null && !mWifiNative.setNetworkVariable(
                 netId,
                 WifiConfiguration.ssidVarName,
@@ -792,7 +781,7 @@
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("addOrUpdateNetwork: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("addOrUpdateNetwork: " + config.networkId);
         int netId = config.networkId;
         boolean newNetwork = false;
         /*
@@ -838,7 +827,7 @@
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("removeNetwork: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("removeNetwork: " + config.networkId);
         if (!mWifiNative.removeNetwork(config.networkId)) {
             loge("Remove network in wpa_supplicant failed on " + config.networkId);
             return false;
@@ -858,17 +847,15 @@
      * @param config Config corresponding to the network.
      * @return true if successful, false otherwise.
      */
-    public boolean selectNetwork(WifiConfiguration config, Collection<WifiConfiguration> configs) {
+    public boolean selectNetwork(WifiConfiguration config) {
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("selectNetwork: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("selectNetwork: " + config.networkId);
         if (!mWifiNative.selectNetwork(config.networkId)) {
             loge("Select network in wpa_supplicant failed on " + config.networkId);
             return false;
         }
-        config.status = Status.ENABLED;
-        markAllNetworksDisabledExcept(config.networkId, configs);
         return true;
     }
 
@@ -882,12 +869,11 @@
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("disableNetwork: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("disableNetwork: " + config.networkId);
         if (!mWifiNative.disableNetwork(config.networkId)) {
             loge("Disable network in wpa_supplicant failed on " + config.networkId);
             return false;
         }
-        config.status = Status.DISABLED;
         return true;
     }
 
@@ -901,7 +887,7 @@
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("setNetworkPriority: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("setNetworkPriority: " + config.networkId);
         if (!mWifiNative.setNetworkVariable(config.networkId,
                 WifiConfiguration.priorityVarName, Integer.toString(priority))) {
             loge("Set priority of network in wpa_supplicant failed on " + config.networkId);
@@ -921,7 +907,7 @@
         if (config == null) {
             return false;
         }
-        if (VDBG) localLog("setNetworkSSID: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("setNetworkSSID: " + config.networkId);
         if (!mWifiNative.setNetworkVariable(config.networkId, WifiConfiguration.ssidVarName,
                 encodeSSID(ssid))) {
             loge("Set SSID of network in wpa_supplicant failed on " + config.networkId);
@@ -945,7 +931,7 @@
                 && config.SSID == null)) {
             return false;
         }
-        if (VDBG) localLog("setNetworkBSSID: " + config.networkId);
+        if (mVerboseLoggingEnabled) localLog("setNetworkBSSID: " + config.networkId);
         if (!mWifiNative.setNetworkVariable(config.networkId, WifiConfiguration.bssidVarName,
                 bssid)) {
             loge("Set BSSID of network in wpa_supplicant failed on " + config.networkId);
@@ -971,7 +957,7 @@
      * @return true if successful, false otherwise.
      */
     public boolean disableAllNetworks(Collection<WifiConfiguration> configs) {
-        if (VDBG) localLog("disableAllNetworks");
+        if (mVerboseLoggingEnabled) localLog("disableAllNetworks");
         boolean networkDisabled = false;
         for (WifiConfiguration enabled : configs) {
             if (disableNetwork(enabled)) {
@@ -1002,16 +988,16 @@
             reader = new BufferedReader(new FileReader(SUPPLICANT_CONFIG_FILE));
             result = readNetworkVariablesFromReader(reader, key);
         } catch (FileNotFoundException e) {
-            if (VDBG) loge("Could not open " + SUPPLICANT_CONFIG_FILE + ", " + e);
+            if (mVerboseLoggingEnabled) loge("Could not open " + SUPPLICANT_CONFIG_FILE + ", " + e);
         } catch (IOException e) {
-            if (VDBG) loge("Could not read " + SUPPLICANT_CONFIG_FILE + ", " + e);
+            if (mVerboseLoggingEnabled) loge("Could not read " + SUPPLICANT_CONFIG_FILE + ", " + e);
         } finally {
             try {
                 if (reader != null) {
                     reader.close();
                 }
             } catch (IOException e) {
-                if (VDBG) {
+                if (mVerboseLoggingEnabled) {
                     loge("Could not close reader for " + SUPPLICANT_CONFIG_FILE + ", " + e);
                 }
             }
@@ -1030,7 +1016,7 @@
     public Map<String, String> readNetworkVariablesFromReader(BufferedReader reader, String key)
             throws IOException {
         Map<String, String> result = new HashMap<>();
-        if (VDBG) localLog("readNetworkVariablesFromReader key=" + key);
+        if (mVerboseLoggingEnabled) localLog("readNetworkVariablesFromReader key=" + key);
         boolean found = false;
         String configKey = null;
         String value = null;
@@ -1061,7 +1047,7 @@
                             }
                         }
                     } catch (JSONException e) {
-                        if (VDBG) {
+                        if (mVerboseLoggingEnabled) {
                             loge("Could not get "+ WifiConfigStore.ID_STRING_KEY_CONFIG_KEY
                                     + ", " + e);
                         }
@@ -1105,7 +1091,7 @@
      * @param configs List of all the networks.
      */
     public void resetSimNetworks(Collection<WifiConfiguration> configs) {
-        if (VDBG) localLog("resetSimNetworks");
+        if (mVerboseLoggingEnabled) localLog("resetSimNetworks");
         for (WifiConfiguration config : configs) {
             if (isSimConfig(config)) {
                 /* This configuration may have cached Pseudonym IDs; lets remove them */
@@ -1119,7 +1105,7 @@
      * Clear BSSID blacklist in wpa_supplicant.
      */
     public void clearBssidBlacklist() {
-        if (VDBG) localLog("clearBlacklist");
+        if (mVerboseLoggingEnabled) localLog("clearBlacklist");
         mBssidBlacklist.clear();
         mWifiNative.clearBlacklist();
         mWifiNative.setBssidBlacklist(null);
@@ -1134,7 +1120,7 @@
         if (bssid == null) {
             return;
         }
-        if (VDBG) localLog("blackListBssid: " + bssid);
+        if (mVerboseLoggingEnabled) localLog("blackListBssid: " + bssid);
         mBssidBlacklist.add(bssid);
         // Blacklist at wpa_supplicant
         mWifiNative.addToBlacklist(bssid);
@@ -1153,21 +1139,6 @@
         return mBssidBlacklist.contains(bssid);
     }
 
-    /* Mark all networks except specified netId as disabled */
-    private void markAllNetworksDisabledExcept(int netId, Collection<WifiConfiguration> configs) {
-        for (WifiConfiguration config : configs) {
-            if (config != null && config.networkId != netId) {
-                if (config.status != Status.DISABLED) {
-                    config.status = Status.DISABLED;
-                }
-            }
-        }
-    }
-
-    private void markAllNetworksDisabled(Collection<WifiConfiguration> configs) {
-        markAllNetworksDisabledExcept(WifiConfiguration.INVALID_NETWORK_ID, configs);
-    }
-
     /**
      * Start WPS pin method configuration with pin obtained
      * from the access point
@@ -1175,12 +1146,9 @@
      * @param config WPS configuration
      * @return Wps result containing status and pin
      */
-    public WpsResult startWpsWithPinFromAccessPoint(WpsInfo config,
-            Collection<WifiConfiguration> configs) {
+    public WpsResult startWpsWithPinFromAccessPoint(WpsInfo config) {
         WpsResult result = new WpsResult();
         if (mWifiNative.startWpsRegistrar(config.BSSID, config.pin)) {
-            /* WPS leaves all networks disabled */
-            markAllNetworksDisabled(configs);
             result.status = WpsResult.Status.SUCCESS;
         } else {
             loge("Failed to start WPS pin method configuration");
@@ -1195,13 +1163,10 @@
      *
      * @return WpsResult indicating status and pin
      */
-    public WpsResult startWpsWithPinFromDevice(WpsInfo config,
-            Collection<WifiConfiguration> configs) {
+    public WpsResult startWpsWithPinFromDevice(WpsInfo config) {
         WpsResult result = new WpsResult();
         result.pin = mWifiNative.startWpsPinDisplay(config.BSSID);
-        /* WPS leaves all networks disabled */
         if (!TextUtils.isEmpty(result.pin)) {
-            markAllNetworksDisabled(configs);
             result.status = WpsResult.Status.SUCCESS;
         } else {
             loge("Failed to start WPS pin method configuration");
@@ -1216,12 +1181,9 @@
      * @param config WPS configuration
      * @return WpsResult indicating status and pin
      */
-    public WpsResult startWpsPbc(WpsInfo config,
-            Collection<WifiConfiguration> configs) {
+    public WpsResult startWpsPbc(WpsInfo config) {
         WpsResult result = new WpsResult();
         if (mWifiNative.startWpsPbc(config.BSSID)) {
-            /* WPS leaves all networks disabled */
-            markAllNetworksDisabled(configs);
             result.status = WpsResult.Status.SUCCESS;
         } else {
             loge("Failed to start WPS push button configuration");
@@ -1268,6 +1230,10 @@
         Log.d(TAG, s);
     }
 
+    void enableVerboseLogging(boolean verbose) {
+        mVerboseLoggingEnabled = verbose;
+    }
+
     private class SupplicantSaver implements WifiEnterpriseConfig.SupplicantSaver {
         private final int mNetId;
         private final String mSetterSSID;
@@ -1336,7 +1302,7 @@
         }
     }
 
-    // TODO(rpius): Remove this.
+    // TODO(rpius): Remove this (see b/27377614).
     private class WpaConfigFileObserver extends FileObserver {
 
         WpaConfigFileObserver() {
@@ -1347,7 +1313,9 @@
         public void onEvent(int event, String path) {
             if (event == CLOSE_WRITE) {
                 File file = new File(SUPPLICANT_CONFIG_FILE);
-                if (VDBG) localLog("wpa_supplicant.conf changed; new size = " + file.length());
+                if (mVerboseLoggingEnabled) {
+                    localLog("wpa_supplicant.conf changed; new size = " + file.length());
+                }
             }
         }
     }
diff --git a/service/java/com/android/server/wifi/WifiController.java b/service/java/com/android/server/wifi/WifiController.java
index 8fb3789..07bb882 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.
@@ -135,14 +139,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 +340,7 @@
     private void updateBatteryWorkSource() {
         mTmpWorkSource.clear();
         if (mDeviceIdle) {
-            mLocks.updateWorkSource(mTmpWorkSource);
+            mTmpWorkSource.add(mWifiLockManager.createMergedWorkSource());
         }
         mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
     }
@@ -882,26 +886,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/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/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index f3ab02c..67914ae 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -966,7 +966,7 @@
     public byte[] toByteArray() {
         synchronized (mLock) {
             consolidateProto(false);
-            return mWifiLogProto.toByteArray(mWifiLogProto);
+            return WifiMetricsProto.WifiLog.toByteArray(mWifiLogProto);
         }
     }
 
diff --git a/service/java/com/android/server/wifi/WifiMonitor.java b/service/java/com/android/server/wifi/WifiMonitor.java
index 2c1bde1..ba7d61e 100644
--- a/service/java/com/android/server/wifi/WifiMonitor.java
+++ b/service/java/com/android/server/wifi/WifiMonitor.java
@@ -59,8 +59,7 @@
  */
 public class WifiMonitor {
 
-    private static boolean DBG = false;
-    private static final boolean VDBG = false;
+    private static final boolean DBG = false;
     private static final String TAG = "WifiMonitor";
 
     /** Events we receive from the supplicant daemon */
@@ -536,12 +535,13 @@
     }
 
     private int mRecvErrors = 0;
+    private boolean mVerboseLoggingEnabled = false;
 
     void enableVerboseLogging(int verbose) {
         if (verbose > 0) {
-            DBG = true;
+            mVerboseLoggingEnabled = true;
         } else {
-            DBG = false;
+            mVerboseLoggingEnabled = false;
         }
     }
 
@@ -589,7 +589,7 @@
             return true;
         }
 
-        if (DBG) Log.d(TAG, "connecting to supplicant");
+        if (mVerboseLoggingEnabled) Log.d(TAG, "connecting to supplicant");
         int connectTries = 0;
         while (true) {
             if (mWifiNative.connectToSupplicant()) {
@@ -625,7 +625,7 @@
     }
 
     public synchronized void stopMonitoring(String iface) {
-        if (DBG) Log.d(TAG, "stopMonitoring(" + iface + ")");
+        if (mVerboseLoggingEnabled) Log.d(TAG, "stopMonitoring(" + iface + ")");
         setMonitoring(iface, true);
         sendMessage(iface, SUP_DISCONNECTION_EVENT);
         setMonitoring(iface, false);
@@ -693,10 +693,14 @@
                     }
                 }
             } else {
-                if (DBG) Log.d(TAG, "Dropping event because (" + iface + ") is stopped");
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "Dropping event because (" + iface + ") is stopped");
+                }
             }
         } else {
-            if (DBG) Log.d(TAG, "Sending to all monitors because there's no matching iface");
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Sending to all monitors because there's no matching iface");
+            }
             boolean firstHandler = true;
             for (Map.Entry<String, SparseArray<Set<Handler>>> entry : mHandlerMap.entrySet()) {
                 if (isMonitoring(entry.getKey())) {
@@ -723,32 +727,36 @@
     }
 
     private class MonitorThread extends Thread {
-        private final LocalLog mLocalLog = mWifiNative.getLocalLog();
+        private final LocalLog mLocalLog = WifiNative.getLocalLog();
 
         public MonitorThread() {
             super("WifiMonitor");
         }
 
         public void run() {
-            if (DBG) {
+            if (mVerboseLoggingEnabled) {
                 Log.d(TAG, "MonitorThread start with mConnected=" + mConnected);
             }
             //noinspection InfiniteLoopStatement
             for (;;) {
                 if (!mConnected) {
-                    if (DBG) Log.d(TAG, "MonitorThread exit because mConnected is false");
+                    if (mVerboseLoggingEnabled) {
+                        Log.d(TAG, "MonitorThread exit because mConnected is false");
+                    }
                     break;
                 }
                 String eventStr = mWifiNative.waitForEvent();
 
                 // Skip logging the common but mostly uninteresting events
                 if (!eventStr.contains(BSS_ADDED_STR) && !eventStr.contains(BSS_REMOVED_STR)) {
-                    if (DBG) Log.d(TAG, "Event [" + eventStr + "]");
+                    if (mVerboseLoggingEnabled) Log.d(TAG, "Event [" + eventStr + "]");
                     mLocalLog.log("Event [" + eventStr + "]");
                 }
 
                 if (dispatchEvent(eventStr)) {
-                    if (DBG) Log.d(TAG, "Disconnecting from the supplicant, no more events");
+                    if (mVerboseLoggingEnabled) {
+                        Log.d(TAG, "Disconnecting from the supplicant, no more events");
+                    }
                     break;
                 }
             }
@@ -781,7 +789,7 @@
             iface = "p2p0";
         }
 
-        if (VDBG) Log.d(TAG, "Dispatching event to interface: " + iface);
+        if (DBG) Log.d(TAG, "Dispatching event to interface: " + iface);
 
         if (dispatchEvent(eventStr, iface)) {
             mConnected = false;
@@ -802,7 +810,7 @@
 
     /* @return true if the event was supplicant disconnection */
     private boolean dispatchEvent(String eventStr, String iface) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             // Dont log CTRL-EVENT-BSS-ADDED which are too verbose and not handled
             if (eventStr != null && !eventStr.contains("CTRL-EVENT-BSS-ADDED")) {
                 Log.d(TAG, iface + " cnt=" + Integer.toString(eventLogCounter)
@@ -855,7 +863,9 @@
                     eventStr.endsWith(AUTH_TIMEOUT_STR)) {
                 sendMessage(iface, AUTHENTICATION_FAILURE_EVENT);
             } else {
-                if (DBG) Log.w(TAG, "couldn't identify event type - " + eventStr);
+                if (mVerboseLoggingEnabled) {
+                    Log.w(TAG, "couldn't identify event type - " + eventStr);
+                }
             }
             eventLogCounter++;
             return false;
@@ -866,7 +876,9 @@
         if (nameEnd != -1)
             eventName = eventName.substring(0, nameEnd);
         if (eventName.length() == 0) {
-            if (DBG) Log.i(TAG, "Received wpa_supplicant event with empty event name");
+            if (mVerboseLoggingEnabled) {
+                Log.i(TAG, "Received wpa_supplicant event with empty event name");
+            }
             eventLogCounter++;
             return false;
         }
@@ -977,7 +989,7 @@
              */
             if (eventData.startsWith(WPA_RECV_ERROR_STR)) {
                 if (++mRecvErrors > MAX_RECV_ERRORS) {
-                    if (DBG) {
+                    if (mVerboseLoggingEnabled) {
                         Log.d(TAG, "too many recv errors, closing connection");
                     }
                 } else {
@@ -998,7 +1010,9 @@
             String BSSID = "";
             int status = -1;
             if (!match.find()) {
-                if (DBG) Log.d(TAG, "Assoc Reject: Could not parse assoc reject string");
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "Assoc Reject: Could not parse assoc reject string");
+                }
             } else {
                 int groupNumber = match.groupCount();
                 int statusGroupNumber = -1;
@@ -1017,9 +1031,9 @@
                 }
             }
             sendMessage(iface, ASSOCIATION_REJECTION_EVENT, eventLogCounter, status, BSSID);
-        } else if (event == BSS_ADDED && !VDBG) {
+        } else if (event == BSS_ADDED && !DBG) {
             // Ignore that event - it is not handled, and dont log it as it is too verbose
-        } else if (event == BSS_REMOVED && !VDBG) {
+        } else if (event == BSS_REMOVED && !DBG) {
             // Ignore that event - it is not handled, and dont log it as it is too verbose
         }  else {
             handleEvent(event, eventData, iface);
@@ -1045,7 +1059,7 @@
      * event name and &quot;&#8195;&#8212;&#8195;&quot;
      */
     private void handleEvent(int event, String remainder, String iface) {
-        if (DBG) {
+        if (mVerboseLoggingEnabled) {
             Log.d(TAG, "handleEvent " + Integer.toString(event) + " " + remainder);
         }
         switch (event) {
@@ -1066,7 +1080,7 @@
                 break;
 
             case UNKNOWN:
-                if (DBG) {
+                if (mVerboseLoggingEnabled) {
                     Log.w(TAG, "handleEvent unknown: " + Integer.toString(event) + " " + remainder);
                 }
                 break;
@@ -1344,7 +1358,9 @@
             }
 
         } else {
-            if (DBG) Log.w(TAG, "couldn't identify request type - " + dataString);
+            if (mVerboseLoggingEnabled) {
+                Log.w(TAG, "couldn't identify request type - " + dataString);
+            }
         }
     }
 
@@ -1417,7 +1433,9 @@
         if (newState == NetworkInfo.DetailedState.CONNECTED) {
             match = mConnectedEventPattern.matcher(data);
             if (!match.find()) {
-               if (DBG) Log.d(TAG, "handleNetworkStateChange: Couldnt find BSSID in event string");
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "handleNetworkStateChange: Couldnt find BSSID in event string");
+                }
             } else {
                 BSSID = match.group(1);
                 try {
@@ -1430,7 +1448,9 @@
         } else if (newState == NetworkInfo.DetailedState.DISCONNECTED) {
             match = mDisconnectedEventPattern.matcher(data);
             if (!match.find()) {
-               if (DBG) Log.d(TAG, "handleNetworkStateChange: Could not parse disconnect string");
+                if (mVerboseLoggingEnabled) {
+                    Log.d(TAG, "handleNetworkStateChange: Could not parse disconnect string");
+                }
             } else {
                 BSSID = match.group(1);
                 try {
@@ -1444,9 +1464,10 @@
                     local = -1;
                 }
             }
-            if (DBG) Log.d(TAG, "WifiMonitor notify network disconnect: "
-                    + BSSID
-                    + " reason=" + Integer.toString(reason));
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "WifiMonitor notify network disconnect: " + BSSID
+                        + " reason=" + Integer.toString(reason));
+            }
             sendMessage(iface, NETWORK_DISCONNECTION_EVENT, local, reason, BSSID);
         }
     }
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index fa8ce49..3e276c6 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -338,24 +338,6 @@
     }
 
     /**
-     * Create a comma separate string from integer set.
-     * @param values List of integers.
-     * @return comma separated string.
-     */
-    private static String createCSVStringFromIntegerSet(Set<Integer> values) {
-        StringBuilder list = new StringBuilder();
-        boolean first = true;
-        for (Integer value : values) {
-            if (!first) {
-                list.append(",");
-            }
-            list.append(value);
-            first = false;
-        }
-        return list.toString();
-    }
-
-    /**
      * Start a scan using wpa_supplicant for the given frequencies.
      * @param freqs list of frequencies to scan for, if null scan all supported channels.
      * @param hiddenNetworkIds List of hidden networks to be scanned for.
@@ -364,10 +346,10 @@
         String freqList = null;
         String hiddenNetworkIdList = null;
         if (freqs != null && freqs.size() != 0) {
-            freqList = createCSVStringFromIntegerSet(freqs);
+            freqList = TextUtils.join(",", freqs);
         }
         if (hiddenNetworkIds != null && hiddenNetworkIds.size() != 0) {
-            hiddenNetworkIdList = createCSVStringFromIntegerSet(hiddenNetworkIds);
+            hiddenNetworkIdList = TextUtils.join(",", hiddenNetworkIds);
         }
         return scanWithParams(freqList, hiddenNetworkIdList);
     }
diff --git a/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java b/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java
index 82dc9e8..4a7bbda 100644
--- a/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiQualifiedNetworkSelector.java
@@ -432,8 +432,9 @@
             sbuf.append("    " + getNetworkString(network) + " " + " User Preferred BSSID:"
                     + network.BSSID + " FQDN:" + network.FQDN + " "
                     + status.getNetworkStatusString() + " Disable account: ");
-            for (int index = status.NETWORK_SELECTION_ENABLE;
-                    index < status.NETWORK_SELECTION_DISABLED_MAX; index++) {
+            for (int index = WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE;
+                    index < WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_DISABLED_MAX;
+                    index++) {
                 sbuf.append(status.getDisableReasonCounter(index) + " ");
             }
             sbuf.append("Connect Choice:" + status.getConnectChoice() + " set time:"
diff --git a/service/java/com/android/server/wifi/WifiScoreReport.java b/service/java/com/android/server/wifi/WifiScoreReport.java
index 50e28bf..dfdf3cd 100644
--- a/service/java/com/android/server/wifi/WifiScoreReport.java
+++ b/service/java/com/android/server/wifi/WifiScoreReport.java
@@ -97,12 +97,8 @@
                                                  WifiConfigManager wifiConfigManager,
                                                  NetworkAgent networkAgent,
                                                  WifiScoreReport lastReport,
-                                                 int aggressiveHandover) {
-        boolean debugLogging = false;
-        if (wifiConfigManager.mEnableVerboseLogging.get() > 0) {
-            debugLogging = true;
-        }
-
+                                                 int aggressiveHandover,
+                                                 boolean verboseLogging) {
         StringBuilder sb = new StringBuilder();
 
         int score = STARTING_SCORE;
@@ -258,7 +254,7 @@
                     currentConfiguration.numTicksAtNotHighRSSI));
         }
 
-        if (debugLogging) {
+        if (verboseLogging) {
             String rssiStatus = "";
             if (isBadRSSI) {
                 rssiStatus += " badRSSI ";
@@ -286,7 +282,7 @@
                 wifiInfo.linkStuckCount += 1;
             }
             sb.append(String.format(" ls+=%d", wifiInfo.linkStuckCount));
-            if (debugLogging) {
+            if (verboseLogging) {
                 Log.d(TAG, " bad link -> stuck count ="
                         + Integer.toString(wifiInfo.linkStuckCount));
             }
@@ -295,7 +291,7 @@
                 wifiInfo.linkStuckCount -= 1;
             }
             sb.append(String.format(" ls-=%d", wifiInfo.linkStuckCount));
-            if (debugLogging) {
+            if (verboseLogging) {
                 Log.d(TAG, " good link -> stuck count ="
                         + Integer.toString(wifiInfo.linkStuckCount));
             }
@@ -311,7 +307,7 @@
 
         if (isBadLinkspeed) {
             score -= BAD_LINKSPEED_PENALTY;
-            if (debugLogging) {
+            if (verboseLogging) {
                 Log.d(TAG, " isBadLinkspeed   ---> count=" + badLinkspeedcount
                         + " score=" + Integer.toString(score));
             }
@@ -338,7 +334,7 @@
         score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount;
         sb.append(String.format(",%d", score));
 
-        if (debugLogging) {
+        if (verboseLogging) {
             Log.d(TAG, " badRSSI count" + Integer.toString(wifiInfo.badRssiCount)
                     + " lowRSSI count" + Integer.toString(wifiInfo.lowRssiCount)
                     + " --> score " + Integer.toString(score));
@@ -346,7 +342,7 @@
 
         if (isHighRSSI) {
             score += 5;
-            if (debugLogging) Log.d(TAG, " isHighRSSI       ---> score=" + Integer.toString(score));
+            if (verboseLogging) Log.d(TAG, " isHighRSSI       ---> score=" + score);
         }
         sb.append(String.format(",%d]", score));
 
@@ -362,7 +358,7 @@
 
         //report score
         if (score != wifiInfo.score) {
-            if (debugLogging) {
+            if (verboseLogging) {
                 Log.d(TAG, "calculateWifiScore() report new score " + Integer.toString(score));
             }
             wifiInfo.score = score;
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index ab4f234..a169c9b 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -129,15 +129,6 @@
 
     private final Context mContext;
 
-    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;
@@ -262,7 +253,7 @@
         }
 
         private void replyFailed(Message msg, int what, int why) {
-            Message reply = msg.obtain();
+            Message reply = Message.obtain();
             reply.what = what;
             reply.arg1 = why;
             try {
@@ -316,6 +307,7 @@
     WifiStateMachineHandler mWifiStateMachineHandler;
 
     private WifiController mWifiController;
+    private final WifiLockManager mWifiLockManager;
 
     public WifiServiceImpl(Context context) {
         mContext = context;
@@ -346,10 +338,11 @@
         mNotificationController = new WifiNotificationController(mContext,
                 wifiThread.getLooper(), mWifiStateMachine, facade, 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(), facade);
+                mSettingsStore, mWifiLockManager, wifiThread.getLooper(), facade);
     }
 
 
@@ -418,6 +411,7 @@
      * see {@link android.net.wifi.WifiManager#pingSupplicant()}
      * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
+    @Override
     public boolean pingSupplicant() {
         enforceAccessPermission();
         if (mWifiStateMachineChannel != null) {
@@ -435,6 +429,7 @@
      * @param settings If null, use default parameter, i.e. full scan.
      * @param workSource If null, all blame is given to the calling uid.
      */
+    @Override
     public void startScan(ScanSettings settings, WorkSource workSource) {
         enforceChangePermission();
         synchronized (this) {
@@ -474,6 +469,7 @@
                 settings, workSource);
     }
 
+    @Override
     public String getWpsNfcConfigurationToken(int netId) {
         enforceConnectivityInternalPermission();
         return mWifiStateMachine.syncGetWpsNfcConfigurationToken(netId);
@@ -546,6 +542,7 @@
      * @return {@code true} if the enable/disable operation was
      *         started or is already in the queue.
      */
+    @Override
     public synchronized boolean setWifiEnabled(boolean enable) {
         enforceChangePermission();
         Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
@@ -578,6 +575,7 @@
      *         {@link WifiManager#WIFI_STATE_ENABLING},
      *         {@link WifiManager#WIFI_STATE_UNKNOWN}
      */
+    @Override
     public int getWifiEnabledState() {
         enforceAccessPermission();
         return mWifiStateMachine.syncGetWifiState();
@@ -589,6 +587,7 @@
      *        part of WifiConfiguration
      * @param enabled true to enable and false to disable
      */
+    @Override
     public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
         enforceChangePermission();
         ConnectivityManager.enforceTetherChangePermission(mContext);
@@ -611,6 +610,7 @@
      *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
      *         {@link WifiManager#WIFI_AP_STATE_FAILED}
      */
+    @Override
     public int getWifiApEnabledState() {
         enforceAccessPermission();
         return mWifiStateMachine.syncGetWifiApState();
@@ -620,6 +620,7 @@
      * see {@link WifiManager#getWifiApConfiguration()}
      * @return soft access point configuration
      */
+    @Override
     public WifiConfiguration getWifiApConfiguration() {
         enforceAccessPermission();
         return mWifiStateMachine.syncGetWifiApConfiguration();
@@ -629,6 +630,7 @@
      * see {@link WifiManager#buildWifiConfig()}
      * @return a WifiConfiguration.
      */
+    @Override
     public WifiConfiguration buildWifiConfig(String uriString, String mimeType, byte[] data) {
         if (mimeType.equals(ConfigBuilder.WifiConfigType)) {
             try {
@@ -648,6 +650,7 @@
      * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
      * @param wifiConfig WifiConfiguration details for soft access point
      */
+    @Override
     public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
         enforceChangePermission();
         if (wifiConfig == null)
@@ -660,10 +663,9 @@
     }
 
     /**
-     * @param enable {@code true} to enable, {@code false} to disable.
-     * @return {@code true} if the enable/disable operation was
-     *         started or is already in the queue.
+     * see {@link android.net.wifi.WifiManager#isScanAlwaysAvailable()}
      */
+    @Override
     public boolean isScanAlwaysAvailable() {
         enforceAccessPermission();
         return mSettingsStore.isScanAlwaysAvailable();
@@ -672,6 +674,7 @@
     /**
      * see {@link android.net.wifi.WifiManager#disconnect()}
      */
+    @Override
     public void disconnect() {
         enforceChangePermission();
         mWifiStateMachine.disconnectCommand();
@@ -680,6 +683,7 @@
     /**
      * see {@link android.net.wifi.WifiManager#reconnect()}
      */
+    @Override
     public void reconnect() {
         enforceChangePermission();
         mWifiStateMachine.reconnectCommand();
@@ -688,6 +692,7 @@
     /**
      * see {@link android.net.wifi.WifiManager#reassociate()}
      */
+    @Override
     public void reassociate() {
         enforceChangePermission();
         mWifiStateMachine.reassociateCommand();
@@ -696,6 +701,7 @@
     /**
      * see {@link android.net.wifi.WifiManager#getSupportedFeatures}
      */
+    @Override
     public int getSupportedFeatures() {
         enforceAccessPermission();
         if (mWifiStateMachineChannel != null) {
@@ -716,6 +722,7 @@
     /**
      * see {@link android.net.wifi.WifiManager#getControllerActivityEnergyInfo(int)}
      */
+    @Override
     public WifiActivityEnergyInfo reportActivityInfo() {
         enforceAccessPermission();
         if ((getSupportedFeatures() & WifiManager.WIFI_FEATURE_LINK_LAYER_STATS) == 0) {
@@ -787,6 +794,7 @@
      * see {@link android.net.wifi.WifiManager#getConfiguredNetworks()}
      * @return the list of configured networks
      */
+    @Override
     public List<WifiConfiguration> getConfiguredNetworks() {
         enforceAccessPermission();
         if (mWifiStateMachineChannel != null) {
@@ -802,6 +810,7 @@
      * see {@link android.net.wifi.WifiManager#getPrivilegedConfiguredNetworks()}
      * @return the list of configured networks with real preSharedKey
      */
+    @Override
     public List<WifiConfiguration> getPrivilegedConfiguredNetworks() {
         enforceReadCredentialPermission();
         enforceAccessPermission();
@@ -818,6 +827,7 @@
      * @param scanResult scanResult that represents the BSSID
      * @return {@link WifiConfiguration} that matches this BSSID or null
      */
+    @Override
     public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
         enforceAccessPermission();
         return mWifiStateMachine.syncGetMatchingWifiConfig(scanResult, mWifiStateMachineChannel);
@@ -828,6 +838,7 @@
      * @return the supplicant-assigned identifier for the new or updated
      * network if the operation succeeds, or {@code -1} if it fails
      */
+    @Override
     public int addOrUpdateNetwork(WifiConfiguration config) {
         enforceChangePermission();
         if (isValid(config) && isValidPasspoint(config)) {
@@ -898,6 +909,7 @@
      * to the supplicant
      * @return {@code true} if the operation succeeded
      */
+    @Override
     public boolean removeNetwork(int netId) {
         enforceChangePermission();
 
@@ -916,6 +928,7 @@
      * @param disableOthers if true, disable all other networks.
      * @return {@code true} if the operation succeeded
      */
+    @Override
     public boolean enableNetwork(int netId, boolean disableOthers) {
         enforceChangePermission();
         if (mWifiStateMachineChannel != null) {
@@ -933,6 +946,7 @@
      * to the supplicant
      * @return {@code true} if the operation succeeded
      */
+    @Override
     public boolean disableNetwork(int netId) {
         enforceChangePermission();
         if (mWifiStateMachineChannel != null) {
@@ -947,6 +961,7 @@
      * See {@link android.net.wifi.WifiManager#getConnectionInfo()}
      * @return the Wi-Fi information, contained in {@link WifiInfo}.
      */
+    @Override
     public WifiInfo getConnectionInfo() {
         enforceAccessPermission();
         /*
@@ -961,6 +976,7 @@
      * a list of {@link ScanResult} objects.
      * @return the list of results
      */
+    @Override
     public List<ScanResult> getScanResults(String callingPackage) {
         enforceAccessPermission();
         int userId = UserHandle.getCallingUserId();
@@ -997,6 +1013,7 @@
      * @param mo The MO in XML form
      * @return -1 for failure
      */
+    @Override
     public int addPasspointManagementObject(String mo) {
         return mWifiStateMachine.syncAddPasspointManagementObject(mWifiStateMachineChannel, mo);
     }
@@ -1007,6 +1024,7 @@
      * @param mos A List of MO definitions to be updated
      * @return the number of nodes updated, or -1 for failure
      */
+    @Override
     public int modifyPasspointManagementObject(String fqdn, List<PasspointManagementObjectDefinition> mos) {
         return mWifiStateMachine.syncModifyPasspointManagementObject(mWifiStateMachineChannel, fqdn, mos);
     }
@@ -1016,6 +1034,7 @@
      * @param bssid The BSSID of the AP
      * @param fileName Icon file name
      */
+    @Override
     public void queryPasspointIcon(long bssid, String fileName) {
         mWifiStateMachine.syncQueryPasspointIcon(mWifiStateMachineChannel, bssid, fileName);
     }
@@ -1025,6 +1044,7 @@
      * @param fqdn FQDN of the SP
      * @return ordinal [HomeProvider, RoamingProvider, Incomplete, None, Declined]
      */
+    @Override
     public int matchProviderWithCurrentNetwork(String fqdn) {
         return mWifiStateMachine.matchProviderWithCurrentNetwork(mWifiStateMachineChannel, fqdn);
     }
@@ -1034,6 +1054,7 @@
      * @param holdoff hold off time in milliseconds
      * @param ess set if the hold off pertains to an ESS rather than a BSS
      */
+    @Override
     public void deauthenticateNetwork(long holdoff, boolean ess) {
         mWifiStateMachine.deauthenticateNetwork(mWifiStateMachineChannel, holdoff, ess);
     }
@@ -1087,8 +1108,8 @@
      *
      * TODO: deprecate this
      */
+    @Override
     public boolean saveConfiguration() {
-        boolean result = true;
         enforceChangePermission();
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
@@ -1107,6 +1128,7 @@
      * persisted country code on a restart, when the locale information is
      * not available from telephony.
      */
+    @Override
     public void setCountryCode(String countryCode, boolean persist) {
         Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
                 " with persist set to " + persist);
@@ -1123,6 +1145,7 @@
      * Get the country code
      * @return ISO 3166 country code.
      */
+    @Override
     public String getCountryCode() {
         enforceConnectivityInternalPermission();
         String country = mCountryCode.getCurrentCountryCode();
@@ -1137,6 +1160,7 @@
      * @param persist {@code true} if the setting should be remembered.
      *
      */
+    @Override
     public void setFrequencyBand(int band, boolean persist) {
         enforceChangePermission();
         if (!isDualBandSupported()) return;
@@ -1154,11 +1178,13 @@
     /**
      * Get the operational frequency band
      */
+    @Override
     public int getFrequencyBand() {
         enforceAccessPermission();
         return mWifiStateMachine.getFrequencyBand();
     }
 
+    @Override
     public boolean isDualBandSupported() {
         //TODO: Should move towards adding a driver API that checks at runtime
         return mContext.getResources().getBoolean(
@@ -1171,6 +1197,8 @@
      * @return the DHCP information
      * @deprecated
      */
+    @Override
+    @Deprecated
     public DhcpInfo getDhcpInfo() {
         enforceAccessPermission();
         DhcpResults dhcpResults = mWifiStateMachine.syncGetDhcpResults();
@@ -1210,6 +1238,7 @@
      * see {@link android.net.wifi.WifiManager#addToBlacklist}
      *
      */
+    @Override
     public void addToBlacklist(String bssid) {
         enforceChangePermission();
 
@@ -1220,6 +1249,7 @@
      * see {@link android.net.wifi.WifiManager#clearBlacklist}
      *
      */
+    @Override
     public void clearBlacklist() {
         enforceChangePermission();
 
@@ -1300,6 +1330,7 @@
         }
     }
 
+    @Override
     public void enableTdls(String remoteAddress, boolean enable) {
         if (remoteAddress == null) {
           throw new IllegalArgumentException("remoteAddress cannot be null");
@@ -1312,6 +1343,7 @@
     }
 
 
+    @Override
     public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) {
         if (remoteMacAddress == null) {
           throw new IllegalArgumentException("remoteMacAddress cannot be null");
@@ -1324,6 +1356,7 @@
      * Get a reference to handler. This is used by a client to establish
      * an AsyncChannel communication with WifiService
      */
+    @Override
     public Messenger getWifiServiceMessenger() {
         enforceAccessPermission();
         enforceChangePermission();
@@ -1333,6 +1366,7 @@
     /**
      * Disable an ephemeral network, i.e. network that is created thru a WiFi Scorer
      */
+    @Override
     public void disableEphemeralNetwork(String SSID) {
         enforceAccessPermission();
         enforceChangePermission();
@@ -1342,6 +1376,7 @@
     /**
      * Get the IP and proxy configuration file
      */
+    @Override
     public String getConfigFile() {
         enforceAccessPermission();
         return mWifiStateMachine.getConfigFile();
@@ -1502,16 +1537,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("    ");
@@ -1524,251 +1552,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;
@@ -1779,20 +1591,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) {
@@ -1803,11 +1602,20 @@
             }
         }
 
+        void unlinkDeathRecipient() {
+            mBinder.unlinkToDeath(this, 0);
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
         public String toString() {
             return "Multicaster{" + mTag + " uid=" + mUid  + "}";
         }
     }
 
+    @Override
     public void initializeMulticastFiltering() {
         enforceMulticastChangePermission();
 
@@ -1821,6 +1629,7 @@
         }
     }
 
+    @Override
     public void acquireMulticastLock(IBinder binder, String tag) {
         enforceMulticastChangePermission();
 
@@ -1844,6 +1653,7 @@
         }
     }
 
+    @Override
     public void releaseMulticastLock() {
         enforceMulticastChangePermission();
 
@@ -1880,6 +1690,7 @@
         }
     }
 
+    @Override
     public boolean isMulticastEnabled() {
         enforceAccessPermission();
 
@@ -1888,47 +1699,57 @@
         }
     }
 
+    @Override
     public void enableVerboseLogging(int verbose) {
         enforceAccessPermission();
         mWifiStateMachine.enableVerboseLogging(verbose);
+        mWifiLockManager.enableVerboseLogging(verbose);
     }
 
+    @Override
     public int getVerboseLoggingLevel() {
         enforceAccessPermission();
         return mWifiStateMachine.getVerboseLoggingLevel();
     }
 
+    @Override
     public void enableAggressiveHandover(int enabled) {
         enforceAccessPermission();
         mWifiStateMachine.enableAggressiveHandover(enabled);
     }
 
+    @Override
     public int getAggressiveHandover() {
         enforceAccessPermission();
         return mWifiStateMachine.getAggressiveHandover();
     }
 
+    @Override
     public void setAllowScansWithTraffic(int enabled) {
         enforceAccessPermission();
         mWifiStateMachine.setAllowScansWithTraffic(enabled);
     }
 
+    @Override
     public int getAllowScansWithTraffic() {
         enforceAccessPermission();
         return mWifiStateMachine.getAllowScansWithTraffic();
     }
 
+    @Override
     public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
         enforceChangePermission();
         return mWifiStateMachine.setEnableAutoJoinWhenAssociated(enabled);
     }
 
+    @Override
     public boolean getEnableAutoJoinWhenAssociated() {
         enforceAccessPermission();
         return mWifiStateMachine.getEnableAutoJoinWhenAssociated();
     }
 
     /* Return the Wifi Connection statistics object */
+    @Override
     public WifiConnectionStatistics getConnectionStatistics() {
         enforceAccessPermission();
         enforceReadCredentialPermission();
@@ -1940,6 +1761,7 @@
         }
     }
 
+    @Override
     public void factoryReset() {
         enforceConnectivityInternalPermission();
 
@@ -2028,6 +1850,7 @@
         return null;
     }
 
+    @Override
     public Network getCurrentNetwork() {
         enforceAccessPermission();
         return mWifiStateMachine.getCurrentNetwork();
@@ -2109,6 +1932,7 @@
      *
      * @param enabled true-enable; false-disable
      */
+    @Override
     public void enableWifiConnectivityManager(boolean enabled) {
         enforceConnectivityInternalPermission();
         mWifiStateMachine.enableWifiConnectivityManager(enabled);
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index 2181311..c52bcd1 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -29,7 +29,6 @@
 
 import android.Manifest;
 import android.app.ActivityManager;
-import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
@@ -73,6 +72,7 @@
 import android.net.wifi.WpsInfo;
 import android.net.wifi.WpsResult;
 import android.net.wifi.WpsResult.Status;
+import android.net.wifi.nan.WifiNanManager;
 import android.net.wifi.p2p.IWifiP2pManager;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -119,12 +119,10 @@
 import java.net.InetAddress;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Calendar;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Queue;
-import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -153,7 +151,6 @@
     @VisibleForTesting public static final short NUM_LOG_RECS_VERBOSE_LOW_MEMORY = 200;
     @VisibleForTesting public static final short NUM_LOG_RECS_VERBOSE = 3000;
     private static boolean DBG = false;
-    private static boolean USE_PAUSE_SCANS = false;
     private static final String TAG = "WifiStateMachine";
 
     private static final int ONE_HOUR_MILLI = 1000 * 60 * 60;
@@ -171,13 +168,16 @@
      *
      * @param s is string log
      */
+    @Override
     protected void loge(String s) {
         Log.e(getName(), s);
     }
+    @Override
     protected void logd(String s) {
         Log.d(getName(), s);
     }
-    protected void log(String s) {;
+    @Override
+    protected void log(String s) {
         Log.d(getName(), s);
     }
     private WifiLastResortWatchdog mWifiLastResortWatchdog;
@@ -194,6 +194,7 @@
     private WifiApConfigStore mWifiApConfigStore;
     private final boolean mP2pSupported;
     private final AtomicBoolean mP2pConnected = new AtomicBoolean(false);
+    private final boolean mNanSupported;
     private boolean mTemporarilyDisconnectWifi = false;
     private final String mPrimaryDeviceType;
     private final Clock mClock;
@@ -212,12 +213,7 @@
 
     private boolean mScreenOn = false;
 
-    /* Chipset supports background scan */
-    private final boolean mBackgroundScanSupported;
-
     private final String mInterfaceName;
-    /* Tethering interface could be separate from wlan interface */
-    private String mTetherInterfaceName;
 
     private int mLastSignalLevel = -1;
     private String mLastBssid;
@@ -246,7 +242,7 @@
                 // This value of hw has to be believed as this value is averaged and has breached
                 // the rssi thresholds and raised event to host. This would be eggregious if this
                 // value is invalid
-                mWifiInfo.setRssi((int) curRssi);
+                mWifiInfo.setRssi(curRssi);
                 updateCapabilities(getCurrentWifiConfiguration());
                 int ret = startRssiMonitoringOffload(maxRssi, minRssi);
                 Log.d(TAG, "Re-program RSSI thresholds for " + smToString(reason) +
@@ -273,12 +269,8 @@
     private boolean mSendScanResultsBroadcast = false;
 
     private final Queue<Message> mBufferedScanMsg = new LinkedList<Message>();
-    private WorkSource mScanWorkSource = null;
     private static final int UNKNOWN_SCAN_SOURCE = -1;
     private static final int ADD_OR_UPDATE_SOURCE = -3;
-    private static final int SET_ALLOW_UNTRUSTED_SOURCE = -4;
-    private static final int ENABLE_WIFI = -5;
-    public static final int DFS_RESTRICTED_SCAN_REQUEST = -6;
 
     private static final int SCAN_REQUEST_BUFFER_MAX_SIZE = 10;
     private static final String CUSTOMIZED_SCAN_SETTING = "customized_scan_settings";
@@ -321,14 +313,6 @@
     private int mSupplicantStopFailureToken = 0;
 
     /**
-     * Tether state change notification time out
-     */
-    private static final int TETHER_NOTIFICATION_TIME_OUT_MSECS = 5000;
-
-    /* Tracks sequence number on a tether notification time out */
-    private int mTetherToken = 0;
-
-    /**
      * Driver start time out.
      */
     private static final int DRIVER_START_TIME_OUT_MSECS = 10000;
@@ -337,12 +321,6 @@
     private int mDriverStartToken = 0;
 
     /**
-     * Don't select new network when previous network selection is
-     * pending connection for this much time
-     */
-    private static final int CONNECT_TIMEOUT_MSEC = 3000;
-
-    /**
      * The link properties of the wifi interface.
      * Do not modify this directly; use updateLinkProperties instead.
      */
@@ -393,9 +371,6 @@
     // Used as debug to indicate which configuration last was removed
     private WifiConfiguration lastForgetConfigurationAttempt = null;
 
-    //Random used by softAP channel Selection
-    private static Random mRandom = new Random(Calendar.getInstance().getTimeInMillis());
-
     boolean isRoaming() {
         return mAutoRoaming;
     }
@@ -416,7 +391,7 @@
         }
         if (!mTargetRoamBSSID.equals("any") && bssid.equals("any")) {
             // Changing to ANY
-            if (!mWifiConfigManager.ROAM_ON_ANY) {
+            if (!WifiConfigManager.ROAM_ON_ANY) {
                 ret = false; // Nothing to do
             }
         }
@@ -527,9 +502,6 @@
 
     private final IpManager mIpManager;
 
-    private AlarmManager mAlarmManager;
-    private PendingIntent mScanIntent;
-
     /* Tracks current frequency mode */
     private AtomicInteger mFrequencyBand = new AtomicInteger(WifiManager.WIFI_FREQUENCY_BAND_AUTO);
 
@@ -537,6 +509,7 @@
     private AsyncChannel mReplyChannel = new AsyncChannel();
 
     private WifiP2pServiceImpl mWifiP2pServiceImpl;
+    private WifiNanManager mWifiNanManager;
 
     // Used to initiate a connection with WifiP2pService
     private AsyncChannel mWifiP2pChannel;
@@ -548,8 +521,6 @@
     private UntrustedWifiNetworkFactory mUntrustedNetworkFactory;
     private WifiNetworkAgent mNetworkAgent;
 
-    private String[] mWhiteListedSsids = null;
-
     private byte[] mRssiRanges;
 
     // Keep track of various statistics, for retrieval by System Apps, i.e. under @SystemApi
@@ -770,9 +741,6 @@
 
     static final int CMD_ACCEPT_UNVALIDATED                             = BASE + 153;
 
-    /* used to log if PNO was started */
-    static final int CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION              = BASE + 158;
-
     /* used to offload sending IP packet */
     static final int CMD_START_IP_PACKET_OFFLOAD                        = BASE + 160;
 
@@ -845,16 +813,6 @@
     private AtomicBoolean mUserWantsSuspendOpt = new AtomicBoolean(true);
 
     /**
-     * Default framework scan interval in milliseconds. This is used in the scenario in which
-     * wifi chipset does not support background scanning to set up a
-     * periodic wake up scan so that the device can connect to a new access
-     * point on the move. {@link Settings.Global#WIFI_FRAMEWORK_SCAN_INTERVAL_MS} can
-     * override this.
-     */
-    private final int mDefaultFrameworkScanIntervalMs;
-
-
-    /**
      * Scan period for the NO_NETWORKS_PERIIDOC_SCAN_FEATURE
      */
     private final int mNoNetworksPeriodicScan;
@@ -947,8 +905,6 @@
      */
     private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED);
 
-    private static final int SCAN_REQUEST = 0;
-
     /**
      * Work source to use to blame usage on the WiFi service
      */
@@ -981,11 +937,6 @@
     // Used for debug and stats gathering
     private static int sScanAlarmIntentCount = 0;
 
-    private static final int sFrameworkMinScanIntervalSaneValue = 10000;
-
-    private long mGScanStartTimeMilli;
-    private long mGScanPeriodMilli;
-
     private FrameworkFacade mFacade;
 
     private final BackupManagerProxy mBackupManagerProxy;
@@ -1018,6 +969,8 @@
 
         mP2pSupported = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_DIRECT);
+        mNanSupported = mContext.getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_WIFI_NAN);
 
         mWifiConfigManager = mFacade.makeWifiConfigManager(context, mWifiNative, facade,
                 mWifiInjector.getClock(), userManager, mWifiInjector.getKeyStore());
@@ -1044,6 +997,8 @@
         IBinder s1 = mFacade.getService(Context.WIFI_P2P_SERVICE);
         mWifiP2pServiceImpl = (WifiP2pServiceImpl) IWifiP2pManager.Stub.asInterface(s1);
 
+        mWifiNanManager = (WifiNanManager) mContext.getSystemService(Context.WIFI_NAN_SERVICE);
+
         mNetworkInfo.setIsAvailable(false);
         mLastBssid = null;
         mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
@@ -1052,21 +1007,12 @@
         mIpManager = mFacade.makeIpManager(mContext, mInterfaceName, new IpManagerCallback());
         mIpManager.setMulticastFilter(true);
 
-        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-
-        // Make sure the interval is not configured less than 10 seconds
-        int period = mContext.getResources().getInteger(
-                R.integer.config_wifi_framework_scan_interval);
-        if (period < sFrameworkMinScanIntervalSaneValue) {
-            period = sFrameworkMinScanIntervalSaneValue;
-        }
-        mDefaultFrameworkScanIntervalMs = period;
-
         mNoNetworksPeriodicScan = mContext.getResources().getInteger(
                 R.integer.config_wifi_no_network_periodic_scan_interval);
 
-        mBackgroundScanSupported = mContext.getResources().getBoolean(
-                R.bool.config_wifi_background_scan_support);
+        // TODO: remove these settings from the config file since we no longer obey them
+        // mContext.getResources().getInteger(R.integer.config_wifi_framework_scan_interval);
+        // mContext.getResources().getBoolean(R.bool.config_wifi_background_scan_support);
 
         mPrimaryDeviceType = mContext.getResources().getString(
                 R.string.config_wifi_p2p_device_type);
@@ -1326,15 +1272,6 @@
                 enableVerbose ? LOGD_LEVEL_VERBOSE : LOGD_LEVEL_DEBUG);
     }
 
-    long mLastScanPermissionUpdate = 0;
-    boolean mConnectedModeGScanOffloadStarted = false;
-    // Don't do a G-scan enable/re-enable cycle more than once within 20seconds
-    // The function updateAssociatedScanPermission() can be called quite frequently, hence
-    // we want to throttle the GScan Stop->Start transition
-    static final long SCAN_PERMISSION_UPDATE_THROTTLE_MILLI = 20000;
-    void updateAssociatedScanPermission() {
-    }
-
     private int mAggressiveHandover = 0;
 
     int getAggressiveHandover() {
@@ -1529,7 +1466,7 @@
         return sb.toString();
     }
 
-    WifiLinkLayerStats getWifiLinkLayerStats(boolean dbg) {
+    WifiLinkLayerStats getWifiLinkLayerStats() {
         WifiLinkLayerStats stats = null;
         if (mWifiLinkLayerStatsSupported > 0) {
             String name = "wlan0";
@@ -1685,16 +1622,21 @@
         }
         WifiScanner.ScanListener nativeScanListener = new WifiScanner.ScanListener() {
                 // ignore all events since WifiStateMachine is registered for the supplicant events
+                @Override
                 public void onSuccess() {
                 }
+                @Override
                 public void onFailure(int reason, String description) {
                     mIsScanOngoing = false;
                     mIsFullScanOngoing = false;
                 }
+                @Override
                 public void onResults(WifiScanner.ScanData[] results) {
                 }
+                @Override
                 public void onFullResult(ScanResult fullScanResult) {
                 }
+                @Override
                 public void onPeriodChanged(int periodInMs) {
                 }
             };
@@ -1798,19 +1740,19 @@
     }
 
     public boolean isSupplicantTransientState() {
-        SupplicantState SupplicantState = mWifiInfo.getSupplicantState();
-        if (SupplicantState == SupplicantState.ASSOCIATING
-                || SupplicantState == SupplicantState.AUTHENTICATING
-                || SupplicantState == SupplicantState.FOUR_WAY_HANDSHAKE
-                || SupplicantState == SupplicantState.GROUP_HANDSHAKE) {
+        SupplicantState supplicantState = mWifiInfo.getSupplicantState();
+        if (supplicantState == SupplicantState.ASSOCIATING
+                || supplicantState == SupplicantState.AUTHENTICATING
+                || supplicantState == SupplicantState.FOUR_WAY_HANDSHAKE
+                || supplicantState == SupplicantState.GROUP_HANDSHAKE) {
 
             if (DBG) {
-                Log.d(TAG, "Supplicant is under transient state: " + SupplicantState);
+                Log.d(TAG, "Supplicant is under transient state: " + supplicantState);
             }
             return true;
         } else {
             if (DBG) {
-                Log.d(TAG, "Supplicant is under steady state: " + SupplicantState);
+                Log.d(TAG, "Supplicant is under steady state: " + supplicantState);
             }
         }
 
@@ -2286,14 +2228,6 @@
         } else {
             pw.println("CurrentCountryCode is not initialized");
         }
-        pw.println("mConnectedModeGScanOffloadStarted " + mConnectedModeGScanOffloadStarted);
-        pw.println("mGScanPeriodMilli " + mGScanPeriodMilli);
-        if (mWhiteListedSsids != null && mWhiteListedSsids.length > 0) {
-            pw.println("SSID whitelist :" );
-            for (int i=0; i < mWhiteListedSsids.length; i++) {
-                pw.println("       " + mWhiteListedSsids[i]);
-            }
-        }
         if (mNetworkFactory != null) {
             mNetworkFactory.dump(fd, pw, args);
         } else {
@@ -2355,6 +2289,7 @@
      * @param msg that was processed
      * @return information to be logged as a String
      */
+    @Override
     protected String getLogRecString(Message msg) {
         WifiConfiguration config;
         Long now;
@@ -2373,20 +2308,6 @@
         }
         sb.append(" ").append(printTime());
         switch (msg.what) {
-            case CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION:
-                sb.append(" ");
-                sb.append(Integer.toString(msg.arg1));
-                sb.append(" ");
-                sb.append(Integer.toString(msg.arg2));
-                sb.append(" autojoinAllowed=");
-                sb.append(mWifiConfigManager.getEnableAutoJoinWhenAssociated());
-                sb.append(" withTraffic=").append(getAllowScansWithTraffic());
-                sb.append(" tx=").append(mWifiInfo.txSuccessRate);
-                sb.append("/").append(mWifiConfigManager.MAX_TX_PACKET_FOR_FULL_SCANS);
-                sb.append(" rx=").append(mWifiInfo.rxSuccessRate);
-                sb.append("/").append(mWifiConfigManager.MAX_RX_PACKET_FOR_FULL_SCANS);
-                sb.append(" -> ").append(mConnectedModeGScanOffloadStarted);
-                break;
             case CMD_START_SCAN:
                 now = System.currentTimeMillis();
                 sb.append(" ");
@@ -2628,11 +2549,6 @@
                 if (mWifiScoreReport != null) {
                     sb.append(mWifiScoreReport.getReport());
                 }
-                if (mConnectedModeGScanOffloadStarted) {
-                    sb.append(" offload-started periodMilli " + mGScanPeriodMilli);
-                } else {
-                    sb.append(" offload-stopped");
-                }
                 break;
             case CMD_AUTO_CONNECT:
             case WifiManager.CONNECT_NETWORK:
@@ -2889,7 +2805,7 @@
         }
         mScreenBroadcastReceived.set(true);
 
-        getWifiLinkLayerStats(false);
+        getWifiLinkLayerStats();
         mOnTimeScreenStateChange = mOnTime;
         lastScreenStateChangeTimeStamp = lastLinkLayerStatsUpdate;
 
@@ -3456,30 +3372,6 @@
 
         mWifiInfo.setBSSID(stateChangeResult.BSSID);
 
-        if (mWhiteListedSsids != null
-                && mWhiteListedSsids.length > 0
-                && stateChangeResult.wifiSsid != null) {
-            String SSID = stateChangeResult.wifiSsid.toString();
-            String currentSSID = mWifiInfo.getSSID();
-            if (SSID != null && currentSSID != null && !SSID.equals(WifiSsid.NONE)) {
-                // Remove quote before comparing
-                if (SSID.length() >= 2 && SSID.charAt(0) == '"'
-                        && SSID.charAt(SSID.length() - 1) == '"') {
-                    SSID = SSID.substring(1, SSID.length() - 1);
-                }
-                if (currentSSID.length() >= 2 && currentSSID.charAt(0) == '"'
-                        && currentSSID.charAt(currentSSID.length() - 1) == '"') {
-                    currentSSID = currentSSID.substring(1, currentSSID.length() - 1);
-                }
-                if ((!SSID.equals(currentSSID)) && (getCurrentState() == mConnectedState)) {
-                    lastConnectAttemptTimestamp = System.currentTimeMillis();
-                    targetWificonfiguration =
-                            mWifiConfigManager.getWifiConfiguration(mWifiInfo.getNetworkId());
-                    transitionTo(mRoamingState);
-                }
-            }
-        }
-
         mWifiInfo.setSSID(stateChangeResult.wifiSsid);
         mWifiInfo.setEphemeral(mWifiConfigManager.isEphemeral(mWifiInfo.getNetworkId()));
         if (!mWifiInfo.getMeteredHint()) { // don't override the value if already set.
@@ -3567,7 +3459,7 @@
              */
             // Disable the coexistence mode
             mWifiNative.setBluetoothCoexistenceMode(
-                    mWifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
+                    WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
         }
 
         // Disable power save and suspend optimizations during DHCP
@@ -3578,7 +3470,7 @@
         mWifiNative.setPowerSave(false);
 
         // Update link layer stats
-        getWifiLinkLayerStats(false);
+        getWifiLinkLayerStats();
 
         /* P2p discovery breaks dhcp, shut it down in order to get through this */
         Message msg = new Message();
@@ -3599,7 +3491,7 @@
 
         // Set the coexistence mode back to its default value
         mWifiNative.setBluetoothCoexistenceMode(
-                mWifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
+                WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
     }
 
     /**
@@ -3611,6 +3503,7 @@
         switch (level2FailureCode) {
             case WifiMetrics.ConnectionEvent.FAILURE_NONE:
             case WifiMetrics.ConnectionEvent.FAILURE_REDUNDANT_CONNECTION_ATTEMPT:
+            case WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED:
                 // WifiLogger doesn't care about success, or pre-empted connections.
                 break;
             default:
@@ -3724,53 +3617,6 @@
         mWifiNative.disconnect();
     }
 
-    private int convertFrequencyToChannelNumber(int frequency) {
-        if (frequency >= 2412 && frequency <= 2484) {
-            return (frequency -2412) / 5 + 1;
-        } else if (frequency >= 5170  &&  frequency <=5825) {
-            //DFS is included
-            return (frequency -5170) / 5 + 34;
-        } else {
-            return 0;
-        }
-    }
-
-    private int chooseApChannel(int apBand) {
-        int apChannel;
-        int[] channel;
-
-        if (apBand == 0)  {
-            ArrayList<Integer> allowed2GChannel =
-                    mWifiApConfigStore.getAllowed2GChannel();
-            if (allowed2GChannel == null || allowed2GChannel.size() == 0) {
-                //most safe channel to use
-                if (DBG) {
-                    Log.d(TAG, "No specified 2G allowed channel list");
-                }
-                apChannel = 6;
-            } else {
-                int index = mRandom.nextInt(allowed2GChannel.size());
-                apChannel = allowed2GChannel.get(index).intValue();
-            }
-        } else {
-            //5G without DFS
-            channel = mWifiNative.getChannelsForBand(2);
-            if (channel != null && channel.length > 0) {
-                apChannel = channel[mRandom.nextInt(channel.length)];
-                apChannel = convertFrequencyToChannelNumber(apChannel);
-            } else {
-                Log.e(TAG, "SoftAp do not get available channel list");
-                apChannel = 0;
-            }
-        }
-
-        if (DBG) {
-            Log.d(TAG, "SoftAp set on channel " + apChannel);
-        }
-
-        return apChannel;
-    }
-
     /* Driver/firmware setup for soft AP. */
     private boolean setupDriverForSoftAp() {
         if (!mWifiNative.loadDriver()) {
@@ -3882,6 +3728,7 @@
             --mConnectionRequests;
         }
 
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             pw.println("mConnectionRequests " + mConnectionRequests);
         }
@@ -3919,6 +3766,7 @@
             }
         }
 
+        @Override
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             pw.println("mUntrustedReqCount " + mUntrustedReqCount);
         }
@@ -4065,7 +3913,6 @@
                 case CMD_DISCONNECTING_WATCHDOG_TIMER:
                 case CMD_ROAM_WATCHDOG_TIMER:
                 case CMD_DISABLE_EPHEMERAL_NETWORK:
-                case CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION:
                     messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD;
                     break;
                 case CMD_SET_SUSPEND_OPT_ENABLED:
@@ -4354,8 +4201,8 @@
                     setFrequencyBand();
                     mWifiNative.enableSaveConfig();
                     mWifiConfigManager.loadAndEnableAllNetworks();
-                    if (mWifiConfigManager.mEnableVerboseLogging.get() > 0) {
-                        enableVerboseLogging(mWifiConfigManager.mEnableVerboseLogging.get());
+                    if (mWifiConfigManager.getVerboseLoggingEnabled()) {
+                        enableVerboseLogging(1);
                     }
                     initializeWpsDetails();
 
@@ -4484,7 +4331,7 @@
                     }
                     break;
                 case CMD_GET_LINK_LAYER_STATS:
-                    WifiLinkLayerStats stats = getWifiLinkLayerStats(DBG);
+                    WifiLinkLayerStats stats = getWifiLinkLayerStats();
                     if (stats == null) {
                         // When firmware doesnt support link layer stats, return an empty object
                         stats = new WifiLinkLayerStats();
@@ -4700,7 +4547,7 @@
             if (mScreenBroadcastReceived.get() == false) {
                 PowerManager powerManager = (PowerManager)mContext.getSystemService(
                         Context.POWER_SERVICE);
-                handleScreenStateChanged(powerManager.isScreenOn());
+                handleScreenStateChanged(powerManager.isInteractive());
             } else {
                 // Set the right suspend mode settings
                 mWifiNative.setSuspendOptimizations(mSuspendOptNeedsDisabled == 0
@@ -4723,6 +4570,17 @@
                 }
             }
 
+            if (mNanSupported && mWifiNanManager != null) {
+                if (mOperationalMode == CONNECT_MODE) {
+                    mWifiNanManager.enableUsage();
+                } else {
+                    /*
+                     * NAN state machine starts in disabled state. Nothing
+                     * needed to keep it disabled.
+                     */
+                }
+            }
+
             final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_ENABLED);
@@ -4763,8 +4621,6 @@
                     mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
                     break;
                 case CMD_STOP_DRIVER:
-                    int mode = message.arg1;
-
                     log("stop driver");
                     mWifiConfigManager.disableAllNetworksNative();
 
@@ -4864,6 +4720,10 @@
             intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WIFI_STATE_DISABLED);
             mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
             mBufferedScanMsg.clear();
+
+            if (mNanSupported && mWifiNanManager != null) {
+                mWifiNanManager.disableUsage();
+            }
         }
     }
 
@@ -6241,6 +6101,8 @@
                 NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
             super(l, c, TAG, ni, nc, lp, score, misc);
         }
+
+        @Override
         protected void unwanted() {
             // Ignore if we're not the current networkAgent.
             if (this != mNetworkAgent) return;
@@ -6526,8 +6388,11 @@
                     break;
                 case CMD_IP_CONFIGURATION_LOST:
                     // Get Link layer stats so that we get fresh tx packet counters.
-                    getWifiLinkLayerStats(true);
+                    getWifiLinkLayerStats();
                     handleIpConfigurationLost();
+                    reportConnectionAttemptEnd(
+                            WifiMetrics.ConnectionEvent.FAILURE_DHCP,
+                            WifiMetricsProto.ConnectionEvent.HLF_NONE);
                     transitionTo(mDisconnectingState);
                     break;
                 case CMD_IP_REACHABILITY_LOST:
@@ -6569,7 +6434,7 @@
                     mWifiInfo.setBSSID((String) message.obj);
                     mLastNetworkId = message.arg1;
                     mWifiInfo.setNetworkId(mLastNetworkId);
-                    if(!mLastBssid.equals((String) message.obj)) {
+                    if(!mLastBssid.equals(message.obj)) {
                         mLastBssid = (String) message.obj;
                         sendNetworkStateChangeBroadcast(mLastBssid);
                     }
@@ -6578,7 +6443,7 @@
                     if (message.arg1 == mRssiPollToken) {
                         if (mWifiConfigManager.mEnableChipWakeUpWhenAssociated.get()) {
                             if (DBG) log(" get link layer stats " + mWifiLinkLayerStatsSupported);
-                            WifiLinkLayerStats stats = getWifiLinkLayerStats(DBG);
+                            WifiLinkLayerStats stats = getWifiLinkLayerStats();
                             if (stats != null) {
                                 // Sanity check the results provided by driver
                                 if (mWifiInfo.getRssi() != WifiInfo.INVALID_RSSI
@@ -6595,7 +6460,8 @@
                                                                    mWifiConfigManager,
                                                                    mNetworkAgent,
                                                                    mWifiScoreReport,
-                                                                   mAggressiveHandover);
+                                                                   mAggressiveHandover,
+                                                                   mVerboseLoggingLevel > 0);
                         }
                         sendMessageDelayed(obtainMessage(CMD_RSSI_POLL,
                                 mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
@@ -6731,7 +6597,7 @@
 
             if (!mWifiConfigManager.isUsingStaticIp(mLastNetworkId)) {
                 final IpManager.ProvisioningConfiguration prov =
-                        mIpManager.buildProvisioningConfiguration()
+                        IpManager.buildProvisioningConfiguration()
                             .withPreDhcpAction()
                             .withApfCapabilities(mWifiNative.getApfCapabilities())
                             .build();
@@ -6739,7 +6605,7 @@
                 obtainingIpWatchdogCount++;
                 logd("Start Dhcp Watchdog " + obtainingIpWatchdogCount);
                 // Get Link layer stats so as we get fresh tx packet counters
-                getWifiLinkLayerStats(true);
+                getWifiLinkLayerStats();
                 sendMessageDelayed(obtainMessage(CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER,
                         obtainingIpWatchdogCount, 0), OBTAINING_IP_ADDRESS_GUARD_TIMER_MSEC);
             } else {
@@ -6750,7 +6616,7 @@
                     sendMessage(CMD_IPV4_PROVISIONING_FAILURE);
                 } else {
                     final IpManager.ProvisioningConfiguration prov =
-                            mIpManager.buildProvisioningConfiguration()
+                            IpManager.buildProvisioningConfiguration()
                                 .withStaticConfiguration(config)
                                 .withApfCapabilities(mWifiNative.getApfCapabilities())
                                 .build();
@@ -6794,6 +6660,9 @@
                         logd("ObtainingIpAddress: Watchdog Triggered, count="
                                 + obtainingIpWatchdogCount);
                         handleIpConfigurationLost();
+                        reportConnectionAttemptEnd(
+                                WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION,
+                                WifiMetricsProto.ConnectionEvent.HLF_NONE);
                         transitionTo(mDisconnectingState);
                         break;
                     }
@@ -6895,7 +6764,7 @@
                         // We completed the layer2 roaming part
                         mAssociated = true;
                         if (stateChangeResult.BSSID != null) {
-                            mTargetRoamBSSID = (String) stateChangeResult.BSSID;
+                            mTargetRoamBSSID = stateChangeResult.BSSID;
                         }
                     }
                     break;
@@ -7000,7 +6869,6 @@
     class ConnectedState extends State {
         @Override
         public void enter() {
-            String address;
             updateDefaultRouteMacAddress(1000);
             if (DBG) {
                 log("Enter ConnectedState "
@@ -7041,9 +6909,6 @@
             logStateAndMessage(message, this);
 
             switch (message.what) {
-                case CMD_UPDATE_ASSOCIATED_SCAN_PERMISSION:
-                    updateAssociatedScanPermission();
-                    break;
                 case CMD_UNWANTED_NETWORK:
                     if (message.arg1 == NETWORK_STATUS_UNWANTED_DISCONNECT) {
                         mWifiConfigManager.handleBadNetworkDisconnectReport(
@@ -7297,7 +7162,6 @@
             }
 
             mLastDriverRoamAttempt = 0;
-            mWhiteListedSsids = null;
             mWifiLastResortWatchdog.connectedStateTransition(false);
         }
     }
@@ -7726,7 +7590,7 @@
 
     /**
      * @param wifiCredentialEventType WIFI_CREDENTIAL_SAVED or WIFI_CREDENTIAL_FORGOT
-     * @param msg Must have a WifiConfiguration obj to succeed
+     * @param config Must have a WifiConfiguration object to succeed
      */
     private void broadcastWifiCredentialChanged(int wifiCredentialEventType,
             WifiConfiguration config) {
@@ -7789,54 +7653,6 @@
         return sb.toString();
     }
 
-    private static byte[] concat(byte[] array1, byte[] array2, byte[] array3) {
-
-        int len = array1.length + array2.length + array3.length;
-
-        if (array1.length != 0) {
-            len++;                      /* add another byte for size */
-        }
-
-        if (array2.length != 0) {
-            len++;                      /* add another byte for size */
-        }
-
-        if (array3.length != 0) {
-            len++;                      /* add another byte for size */
-        }
-
-        byte[] result = new byte[len];
-
-        int index = 0;
-        if (array1.length != 0) {
-            result[index] = (byte) (array1.length & 0xFF);
-            index++;
-            for (byte b : array1) {
-                result[index] = b;
-                index++;
-            }
-        }
-
-        if (array2.length != 0) {
-            result[index] = (byte) (array2.length & 0xFF);
-            index++;
-            for (byte b : array2) {
-                result[index] = b;
-                index++;
-            }
-        }
-
-        if (array3.length != 0) {
-            result[index] = (byte) (array3.length & 0xFF);
-            index++;
-            for (byte b : array3) {
-                result[index] = b;
-                index++;
-            }
-        }
-        return result;
-    }
-
     private static byte[] concatHex(byte[] array1, byte[] array2) {
 
         int len = array1.length + array2.length;
@@ -7881,13 +7697,13 @@
                     rand, android.util.Base64.NO_WRAP);
 
             // Try USIM first for authentication.
-            String tmResponse = tm.getIccAuthentication(tm.APPTYPE_USIM,
-                    tm.AUTHTYPE_EAP_SIM, base64Challenge);
+            String tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                    TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
             if (tmResponse == null) {
                 /* Then, in case of failure, issue may be due to sim type, retry as a simple sim
                  */
-                tmResponse = tm.getIccAuthentication(tm.APPTYPE_SIM,
-                        tm.AUTHTYPE_EAP_SIM, base64Challenge);
+                tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_SIM,
+                        TelephonyManager.AUTHTYPE_EAP_SIM, base64Challenge);
             }
             logv("Raw Response - " + tmResponse);
 
@@ -7981,8 +7797,8 @@
             TelephonyManager tm = (TelephonyManager)
                     mContext.getSystemService(Context.TELEPHONY_SERVICE);
             if (tm != null) {
-                tmResponse = tm.getIccAuthentication(tm.APPTYPE_USIM,
-                        tm.AUTHTYPE_EAP_AKA, base64Challenge);
+                tmResponse = tm.getIccAuthentication(TelephonyManager.APPTYPE_USIM,
+                        TelephonyManager.AUTHTYPE_EAP_AKA, base64Challenge);
                 logv("Raw Response - " + tmResponse);
             } else {
                 loge("could not get telephony manager");
@@ -8122,7 +7938,7 @@
     }
 
     private static String getLinkPropertiesSummary(LinkProperties lp) {
-        List<String> attributes = new ArrayList(6);
+        List<String> attributes = new ArrayList<>(6);
         if (lp.hasIPv4Address()) {
             attributes.add("v4");
         }
diff --git a/service/java/com/android/server/wifi/WifiTrafficPoller.java b/service/java/com/android/server/wifi/WifiTrafficPoller.java
index 336e0d7..d05353b 100644
--- a/service/java/com/android/server/wifi/WifiTrafficPoller.java
+++ b/service/java/com/android/server/wifi/WifiTrafficPoller.java
@@ -41,9 +41,7 @@
 /* Polls for traffic stats and notifies the clients */
 final class WifiTrafficPoller {
 
-    private boolean DBG = false;
-    private boolean VDBG = false;
-
+    private static final boolean DBG = false;
     private static final String TAG = "WifiTrafficPoller";
     /**
      * Interval in milliseconds between polling for traffic
@@ -71,6 +69,8 @@
     private NetworkInfo mNetworkInfo;
     private final String mInterface;
 
+    private boolean mVerboseLoggingEnabled = false;
+
     WifiTrafficPoller(Context context, Looper looper, String iface) {
         mInterface = iface;
         mTrafficHandler = new TrafficHandler(looper);
@@ -110,10 +110,10 @@
     }
 
     void enableVerboseLogging(int verbose) {
-        if (verbose > 0 ) {
-            DBG = true;
+        if (verbose > 0) {
+            mVerboseLoggingEnabled = true;
         } else {
-            DBG = false;
+            mVerboseLoggingEnabled = false;
         }
     }
 
@@ -126,8 +126,8 @@
             switch (msg.what) {
                 case ENABLE_TRAFFIC_STATS_POLL:
                     mEnableTrafficStatsPoll = (msg.arg1 == 1);
-                    if (DBG) {
-                        Log.e(TAG, "ENABLE_TRAFFIC_STATS_POLL "
+                    if (mVerboseLoggingEnabled) {
+                        Log.d(TAG, "ENABLE_TRAFFIC_STATS_POLL "
                                 + mEnableTrafficStatsPoll + " Token "
                                 + Integer.toString(mTrafficStatsPollToken));
                     }
@@ -139,8 +139,8 @@
                     }
                     break;
                 case TRAFFIC_STATS_POLL:
-                    if (VDBG) {
-                        Log.e(TAG, "TRAFFIC_STATS_POLL "
+                    if (DBG) {
+                        Log.d(TAG, "TRAFFIC_STATS_POLL "
                                 + mEnableTrafficStatsPoll + " Token "
                                 + Integer.toString(mTrafficStatsPollToken)
                                 + " num clients " + mClients.size());
@@ -153,8 +153,8 @@
                     break;
                 case ADD_CLIENT:
                     mClients.add((Messenger) msg.obj);
-                    if (DBG) {
-                        Log.e(TAG, "ADD_CLIENT: "
+                    if (mVerboseLoggingEnabled) {
+                        Log.d(TAG, "ADD_CLIENT: "
                                 + Integer.toString(mClients.size()));
                     }
                     break;
@@ -187,8 +187,8 @@
         mTxPkts = TrafficStats.getTxPackets(mInterface);
         mRxPkts = TrafficStats.getRxPackets(mInterface);
 
-        if (VDBG) {
-            Log.e(TAG, " packet count Tx="
+        if (DBG) {
+            Log.d(TAG, " packet count Tx="
                     + Long.toString(mTxPkts)
                     + " Rx="
                     + Long.toString(mRxPkts));
@@ -206,7 +206,7 @@
 
             if (dataActivity != mDataActivity && mScreenOn.get()) {
                 mDataActivity = dataActivity;
-                if (DBG) {
+                if (mVerboseLoggingEnabled) {
                     Log.e(TAG, "notifying of data activity "
                             + Integer.toString(mDataActivity));
                 }
diff --git a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
index 402c0a8..5abf164 100644
--- a/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
+++ b/service/java/com/android/server/wifi/hotspot2/NetworkDetail.java
@@ -24,9 +24,7 @@
 
 public class NetworkDetail {
 
-    //turn off when SHIP
-    private static final boolean DBG = true;
-    private static final boolean VDBG = false;
+    private static final boolean DBG = false;
 
     private static final String TAG = "NetworkDetail:";
 
@@ -290,7 +288,7 @@
             mMaxRate = 0;
             Log.w("WifiMode", mSSID + ", Invalid SupportedRates!!!");
         }
-        if (VDBG) {
+        if (DBG) {
             Log.d(TAG, mSSID + "ChannelWidth is: " + mChannelWidth + " PrimaryFreq: " + mPrimaryFreq
                     + " mCenterfreq0: " + mCenterfreq0 + " mCenterfreq1: " + mCenterfreq1
                     + (extendedCapabilities.is80211McRTTResponder ? "Support RTT reponder"
diff --git a/service/java/com/android/server/wifi/nan/WifiNanClientState.java b/service/java/com/android/server/wifi/nan/WifiNanClientState.java
index 0707270..a2bfc04 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanClientState.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanClientState.java
@@ -16,10 +16,9 @@
 
 package com.android.server.wifi.nan;
 
+import android.net.wifi.RttManager;
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.IWifiNanEventListener;
-import android.net.wifi.nan.IWifiNanSessionListener;
-import android.net.wifi.nan.WifiNanEventListener;
+import android.net.wifi.nan.IWifiNanEventCallback;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.SparseArray;
@@ -27,48 +26,68 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+/**
+ * Manages the service-side NAN state of an individual "client". A client
+ * corresponds to a single instantiation of the WifiNanManager - there could be
+ * multiple ones per UID/process (each of which is a separate client with its
+ * own session namespace). The client state is primarily: (1) callback (a
+ * singleton per client) through which NAN-wide events are called, and (2) a set
+ * of discovery sessions (publish and/or subscribe) which are created through
+ * this client and whose lifetime is tied to the lifetime of the client.
+ */
 public class WifiNanClientState {
     private static final String TAG = "WifiNanClientState";
     private static final boolean DBG = false;
-    private static final boolean VDBG = false; // STOPSHIP if true
+    private static final boolean VDBG = true; // STOPSHIP if true
 
     /* package */ static final int CLUSTER_CHANGE_EVENT_STARTED = 0;
     /* package */ static final int CLUSTER_CHANGE_EVENT_JOINED = 1;
 
-    private IWifiNanEventListener mListener;
-    private int mEvents;
+    private IWifiNanEventCallback mCallback;
     private final SparseArray<WifiNanSessionState> mSessions = new SparseArray<>();
 
-    private int mUid;
+    private int mClientId;
     private ConfigRequest mConfigRequest;
 
-    public WifiNanClientState(int uid, IWifiNanEventListener listener, int events) {
-        mUid = uid;
-        mListener = listener;
-        mEvents = events;
+    public WifiNanClientState(int clientId, IWifiNanEventCallback callback,
+            ConfigRequest configRequest) {
+        mClientId = clientId;
+        mCallback = callback;
+        mConfigRequest = configRequest;
     }
 
+    /**
+     * Destroy the current client - corresponds to a disconnect() request from
+     * the client. Destroys all discovery sessions belonging to this client.
+     */
     public void destroy() {
-        mListener = null;
         for (int i = 0; i < mSessions.size(); ++i) {
-            mSessions.valueAt(i).destroy();
+            mSessions.valueAt(i).terminate();
         }
         mSessions.clear();
         mConfigRequest = null;
     }
 
-    public void setConfigRequest(ConfigRequest configRequest) {
-        mConfigRequest = configRequest;
-    }
-
     public ConfigRequest getConfigRequest() {
         return mConfigRequest;
     }
 
-    public int getUid() {
-        return mUid;
+    public int getClientId() {
+        return mClientId;
     }
 
+    public IWifiNanEventCallback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Searches the discovery sessions of this client and returns the one
+     * corresponding to the publish/subscribe ID. Used on callbacks from HAL to
+     * map callbacks to the correct discovery session.
+     *
+     * @param pubSubId The publish/subscribe match session ID.
+     * @return NAN session corresponding to the requested ID.
+     */
     public WifiNanSessionState getNanSessionStateForPubSubId(int pubSubId) {
         for (int i = 0; i < mSessions.size(); ++i) {
             WifiNanSessionState session = mSessions.valueAt(i);
@@ -80,68 +99,77 @@
         return null;
     }
 
-    public void createSession(int sessionId, IWifiNanSessionListener listener, int events) {
-        WifiNanSessionState session = mSessions.get(sessionId);
-        if (session != null) {
-            Log.e(TAG, "createSession: sessionId already exists (replaced) - " + sessionId);
+    /**
+     * Add the session to the client database.
+     *
+     * @param session Session to be added.
+     */
+    public void addSession(WifiNanSessionState session) {
+        int sessionId = session.getSessionId();
+        if (mSessions.get(sessionId) != null) {
+            Log.w(TAG, "createSession: sessionId already exists (replaced) - " + sessionId);
         }
 
-        mSessions.put(sessionId, new WifiNanSessionState(sessionId, listener, events));
+        mSessions.put(sessionId, session);
     }
 
-    public void destroySession(int sessionId) {
-        WifiNanSessionState session = mSessions.get(sessionId);
-        if (session == null) {
-            Log.e(TAG, "destroySession: sessionId doesn't exist - " + sessionId);
+    /**
+     * Remove the specified session from the client database - without doing a
+     * terminate on the session. The assumption is that it is already
+     * terminated.
+     *
+     * @param sessionId The session ID of the session to be removed.
+     */
+    public void removeSession(int sessionId) {
+        if (mSessions.get(sessionId) == null) {
+            Log.e(TAG, "removeSession: sessionId doesn't exist - " + sessionId);
             return;
         }
 
         mSessions.delete(sessionId);
-        session.destroy();
     }
 
+    /**
+     * Destroy the discovery session: terminates discovery and frees up
+     * resources.
+     *
+     * @param sessionId The session ID of the session to be destroyed.
+     */
+    public void terminateSession(int sessionId) {
+        WifiNanSessionState session = mSessions.get(sessionId);
+        if (session == null) {
+            Log.e(TAG, "terminateSession: sessionId doesn't exist - " + sessionId);
+            return;
+        }
+
+        session.terminate();
+        mSessions.delete(sessionId);
+    }
+
+    /**
+     * Retrieve a session.
+     *
+     * @param sessionId Session ID of the session to be retrieved.
+     * @return Session or null if there's no session corresponding to the
+     *         sessionId.
+     */
     public WifiNanSessionState getSession(int sessionId) {
         return mSessions.get(sessionId);
     }
 
-    public void onConfigCompleted(ConfigRequest completedConfig) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_CONFIG_COMPLETED) != 0) {
-            try {
-                mListener.onConfigCompleted(completedConfig);
-            } catch (RemoteException e) {
-                Log.w(TAG, "onConfigCompleted: RemoteException - ignored: " + e);
-            }
-        }
-    }
-
-    public void onConfigFailed(ConfigRequest failedConfig, int reason) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_CONFIG_FAILED) != 0) {
-            try {
-                mListener.onConfigFailed(failedConfig, reason);
-            } catch (RemoteException e) {
-                Log.w(TAG, "onConfigFailed: RemoteException - ignored: " + e);
-            }
-        }
-    }
-
-    public int onNanDown(int reason) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_NAN_DOWN) != 0) {
-            try {
-                mListener.onNanDown(reason);
-            } catch (RemoteException e) {
-                Log.w(TAG, "onNanDown: RemoteException - ignored: " + e);
-            }
-
-            return 1;
-        }
-
-        return 0;
-    }
-
+    /**
+     * Called to dispatch the NAN interface address change to the client - as an
+     * identity change (interface address information not propagated to client -
+     * privacy concerns).
+     *
+     * @param mac The new MAC address of the discovery interface - not
+     *            propagated to client!
+     * @return A 1 if registered to listen for event, 0 otherwise.
+     */
     public int onInterfaceAddressChange(byte[] mac) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_IDENTITY_CHANGED) != 0) {
+        if (mConfigRequest.mEnableIdentityChangeCallback) {
             try {
-                mListener.onIdentityChanged();
+                mCallback.onIdentityChanged();
             } catch (RemoteException e) {
                 Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
             }
@@ -152,10 +180,21 @@
         return 0;
     }
 
+    /**
+     * Called to dispatch the NAN cluster change (due to joining of a new
+     * cluster or starting a cluster) to the client - as an identity change
+     * (interface address information not propagated to client - privacy
+     * concerns). Dispatched if the client registered for the identity changed
+     * event.
+     *
+     * @param mac The (new) MAC address of the discovery interface - not
+     *            propagated to client!
+     * @return A 1 if registered to listen for event, 0 otherwise.
+     */
     public int onClusterChange(int flag, byte[] mac) {
-        if (mListener != null && (mEvents & WifiNanEventListener.LISTEN_IDENTITY_CHANGED) != 0) {
+        if (mConfigRequest.mEnableIdentityChangeCallback) {
             try {
-                mListener.onIdentityChanged();
+                mCallback.onIdentityChanged();
             } catch (RemoteException e) {
                 Log.w(TAG, "onIdentityChanged: RemoteException - ignored: " + e);
             }
@@ -166,12 +205,55 @@
         return 0;
     }
 
+    /**
+     * Called on RTT success - forwards call to client.
+     */
+    public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
+        if (VDBG) {
+            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
+        }
+        try {
+            mCallback.onRangingSuccess(rangingId, results);
+        } catch (RemoteException e) {
+            Log.w(TAG, "onRangingSuccess: RemoteException - ignored: " + e);
+        }
+    }
+
+    /**
+     * Called on RTT failure - forwards call to client.
+     */
+    public void onRangingFailure(int rangingId, int reason, String description) {
+        if (VDBG) {
+            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
+                    + ", description=" + description);
+        }
+        try {
+            mCallback.onRangingFailure(rangingId, reason, description);
+        } catch (RemoteException e) {
+            Log.w(TAG, "onRangingFailure: RemoteException - ignored: " + e);
+        }
+    }
+
+    /**
+     * Called on RTT operation aborted - forwards call to client.
+     */
+    public void onRangingAborted(int rangingId) {
+        if (VDBG) Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId);
+        try {
+            mCallback.onRangingAborted(rangingId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "onRangingAborted: RemoteException - ignored: " + e);
+        }
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NanClientState:");
-        pw.println("  mUid: " + mUid);
+        pw.println("  mClientId: " + mClientId);
         pw.println("  mConfigRequest: " + mConfigRequest);
-        pw.println("  mListener: " + mListener);
-        pw.println("  mEvents: " + mEvents);
+        pw.println("  mCallback: " + mCallback);
         pw.println("  mSessions: [" + mSessions + "]");
         for (int i = 0; i < mSessions.size(); ++i) {
             mSessions.valueAt(i).dump(fd, pw, args);
diff --git a/service/java/com/android/server/wifi/nan/WifiNanNative.java b/service/java/com/android/server/wifi/nan/WifiNanNative.java
index 8715719..13e8e1e 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanNative.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanNative.java
@@ -17,23 +17,22 @@
 package com.android.server.wifi.nan;
 
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
-import android.net.wifi.nan.WifiNanSessionListener;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanSessionCallback;
 import android.util.Log;
 
 import com.android.server.wifi.WifiNative;
 
 import libcore.util.HexEncoding;
 
+import java.util.Arrays;
+
 /**
  * Native calls to access the Wi-Fi NAN HAL.
  *
  * Relies on WifiNative to perform the actual HAL registration.
- *
- * {@hide}
  */
 public class WifiNanNative {
     private static final String TAG = "WifiNanNative";
@@ -42,12 +41,18 @@
 
     private static final int WIFI_SUCCESS = 0;
 
-    private static boolean sNanNativeInit = false;
-
     private static WifiNanNative sWifiNanNativeSingleton;
 
+    private boolean mNanNativeInit = false;
+
     private static native int registerNanNatives();
 
+    /**
+     * Returns the singleton WifiNanNative used to manage the actual NAN HAL
+     * interface.
+     *
+     * @return Singleton object.
+     */
     public static WifiNanNative getInstance() {
         // dummy reference - used to make sure that WifiNative is loaded before
         // us since it is the one to load the shared library and starts its
@@ -66,6 +71,10 @@
         return sWifiNanNativeSingleton;
     }
 
+    /**
+     * A container class for NAN (vendor) implementation capabilities (or
+     * limitations). Filled-in by the firmware.
+     */
     public static class Capabilities {
         public int maxConcurrentNanClusters;
         public int maxPublishes;
@@ -79,6 +88,7 @@
         public int maxNdiInterfaces;
         public int maxNdpSessions;
         public int maxAppInfoLen;
+        public int maxQueuedTransmitMessages;
 
         @Override
         public String toString() {
@@ -89,42 +99,39 @@
                     + ", maxServiceSpecificInfoLen=" + maxServiceSpecificInfoLen
                     + ", maxVsaDataLen=" + maxVsaDataLen + ", maxMeshDataLen=" + maxMeshDataLen
                     + ", maxNdiInterfaces=" + maxNdiInterfaces + ", maxNdpSessions="
-                    + maxNdpSessions + ", maxAppInfoLen=" + maxAppInfoLen + "]";
+                    + maxNdpSessions + ", maxAppInfoLen=" + maxAppInfoLen
+                    + ", maxQueuedTransmitMessages=" + maxQueuedTransmitMessages + "]";
         }
     }
 
-    /* package */ static native int initNanHandlersNative(Object cls, int iface);
+    /* package */ static native int initNanHandlersNative(Class<WifiNative> cls, int iface);
 
-    private static native int getCapabilitiesNative(short transactionId, Object cls, int iface);
+    private static native int getCapabilitiesNative(short transactionId, Class<WifiNative> cls,
+            int iface);
 
-    private boolean isNanInit(boolean tryToInit) {
-        if (!tryToInit || sNanNativeInit) {
-            return sNanNativeInit;
-        }
-
-        if (DBG) Log.d(TAG, "isNanInit: trying to init");
+    private boolean isNanInit() {
         synchronized (WifiNative.sLock) {
-            boolean halStarted = WifiNative.getWlanNativeInterface().isHalStarted();
-            if (!halStarted) {
-                halStarted = WifiNative.getWlanNativeInterface().startHal();
-            }
-            if (halStarted) {
+            if (!WifiNative.getWlanNativeInterface().isHalStarted()) {
+                /*
+                 * We should never start the HAL - that's done at a higher level
+                 * by the Wi-Fi state machine.
+                 */
+                mNanNativeInit = false;
+                return false;
+            } else if (!mNanNativeInit) {
                 int ret = initNanHandlersNative(WifiNative.class, WifiNative.sWlan0Index);
                 if (DBG) Log.d(TAG, "initNanHandlersNative: res=" + ret);
-                sNanNativeInit = ret == WIFI_SUCCESS;
+                mNanNativeInit = ret == WIFI_SUCCESS;
 
-                if (sNanNativeInit) {
-                    ret = getCapabilitiesNative(
-                            WifiNanStateManager.getInstance().createNextTransactionId(),
-                            WifiNative.class,
+                if (mNanNativeInit) {
+                    ret = getCapabilitiesNative((short) 0, WifiNative.class,
                             WifiNative.sWlan0Index);
                     if (DBG) Log.d(TAG, "getCapabilitiesNative: res=" + ret);
                 }
 
-                return sNanNativeInit;
+                return mNanNativeInit;
             } else {
-                Log.w(TAG, "isNanInit: HAL not initialized");
-                return false;
+                return true;
             }
         }
     }
@@ -133,115 +140,151 @@
         // do nothing
     }
 
-    private static native int enableAndConfigureNative(short transactionId, Object cls, int iface,
-            ConfigRequest configRequest);
+    private static native int enableAndConfigureNative(short transactionId, Class<WifiNative> cls,
+            int iface, ConfigRequest configRequest);
 
-    public void enableAndConfigure(short transactionId, ConfigRequest configRequest) {
-        boolean success;
+    private static native int updateConfigurationNative(short transactionId, Class<WifiNative> cls,
+            int iface, ConfigRequest configRequest);
 
+    /**
+     * Enable and configure NAN.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the
+     *            async callback to match with the original request.
+     * @param configRequest Requested NAN configuration.
+     * @param initialConfiguration Specifies whether initial configuration
+     *            (true) or an update (false) to the configuration.
+     */
+    public boolean enableAndConfigure(short transactionId, ConfigRequest configRequest,
+            boolean initialConfiguration) {
         if (VDBG) Log.d(TAG, "enableAndConfigure: configRequest=" + configRequest);
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
-            synchronized (WifiNative.sLock) {
-                ret = enableAndConfigureNative(transactionId, WifiNative.class,
-                        WifiNative.sWlan0Index, configRequest);
+            if (initialConfiguration) {
+                synchronized (WifiNative.sLock) {
+                    ret = enableAndConfigureNative(transactionId, WifiNative.class,
+                            WifiNative.sWlan0Index, configRequest);
+                }
+                if (DBG) Log.d(TAG, "enableAndConfigureNative: ret=" + ret);
+            } else {
+                synchronized (WifiNative.sLock) {
+                    ret = updateConfigurationNative(transactionId, WifiNative.class,
+                            WifiNative.sWlan0Index, configRequest);
+                }
+                if (DBG) Log.d(TAG, "updateConfigurationNative: ret=" + ret);
             }
-            if (DBG) Log.d(TAG, "enableAndConfigureNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "enableAndConfigure: NanInit fails");
-            success = false;
+            return false;
         }
-
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int disableNative(short transactionId, Object cls, int iface);
+    private static native int disableNative(short transactionId, Class<WifiNative> cls, int iface);
 
-    public void disable(short transactionId) {
-        boolean success;
-
+    /**
+     * Disable NAN.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     */
+    public boolean disable(short transactionId) {
         if (VDBG) Log.d(TAG, "disableNan");
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = disableNative(transactionId, WifiNative.class, WifiNative.sWlan0Index);
             }
             if (DBG) Log.d(TAG, "disableNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "disable: cannot initialize NAN");
-            success = false;
+            return false;
         }
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int publishNative(short transactionId, int publishId, Object cls,
-            int iface,
-            PublishData publishData, PublishSettings publishSettings);
+    private static native int publishNative(short transactionId, int publishId,
+            Class<WifiNative> cls, int iface, PublishConfig publishConfig);
 
-    public void publish(short transactionId, int publishId, PublishData publishData,
-            PublishSettings publishSettings) {
-        boolean success;
-
+    /**
+     * Start or modify a service publish session.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     * @param publishId ID of the requested session - 0 to request a new publish
+     *            session.
+     * @param publishConfig Configuration of the discovery session.
+     */
+    public boolean publish(short transactionId, int publishId, PublishConfig publishConfig) {
         if (VDBG) {
-            Log.d(TAG, "publish: transactionId=" + transactionId + ",data='" + publishData
-                    + "', settings=" + publishSettings);
+            Log.d(TAG, "publish: transactionId=" + transactionId + ", config=" + publishConfig);
         }
 
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = publishNative(transactionId, publishId, WifiNative.class,
-                        WifiNative.sWlan0Index, publishData, publishSettings);
+                        WifiNative.sWlan0Index, publishConfig);
             }
             if (DBG) Log.d(TAG, "publishNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "publish: cannot initialize NAN");
-            success = false;
+            return false;
         }
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int subscribeNative(short transactionId, int subscribeId, Object cls,
-            int iface, SubscribeData subscribeData, SubscribeSettings subscribeSettings);
+    private static native int subscribeNative(short transactionId, int subscribeId,
+            Class<WifiNative> cls, int iface, SubscribeConfig subscribeConfig);
 
-    public void subscribe(short transactionId, int subscribeId, SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings) {
-        boolean success;
-
+    /**
+     * Start or modify a service subscription session.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     * @param subscribeId ID of the requested session - 0 to request a new
+     *            subscribe session.
+     * @param subscribeConfig Configuration of the discovery session.
+     */
+    public boolean subscribe(short transactionId, int subscribeId,
+            SubscribeConfig subscribeConfig) {
         if (VDBG) {
-            Log.d(TAG, "subscribe: transactionId=" + transactionId + ", data='" + subscribeData
-                    + "', settings=" + subscribeSettings);
+            Log.d(TAG, "subscribe: transactionId=" + transactionId + ", config=" + subscribeConfig);
         }
 
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = subscribeNative(transactionId, subscribeId, WifiNative.class,
-                        WifiNative.sWlan0Index, subscribeData, subscribeSettings);
+                        WifiNative.sWlan0Index, subscribeConfig);
             }
             if (DBG) Log.d(TAG, "subscribeNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "subscribe: cannot initialize NAN");
-            success = false;
+            return false;
         }
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int sendMessageNative(short transactionId, Object cls, int iface,
-            int pubSubId, int requestorInstanceId, byte[] dest, byte[] message, int messageLength);
+    private static native int sendMessageNative(short transactionId, Class<WifiNative> cls,
+            int iface, int pubSubId, int requestorInstanceId, byte[] dest, byte[] message,
+            int messageLength);
 
-    public void sendMessage(short transactionId, int pubSubId, int requestorInstanceId, byte[] dest,
-            byte[] message, int messageLength) {
-        boolean success;
-
+    /**
+     * Send a message through an existing discovery session.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     * @param pubSubId ID of the existing publish/subscribe session.
+     * @param requestorInstanceId ID of the peer to communicate with - obtained
+     *            through a previous discovery (match) operation with that peer.
+     * @param dest MAC address of the peer to communicate with - obtained
+     *            together with requestorInstanceId.
+     * @param message Message.
+     * @param messageLength Message byte array length.
+     */
+    public boolean sendMessage(short transactionId, int pubSubId, int requestorInstanceId,
+            byte[] dest, byte[] message, int messageLength) {
         if (VDBG) {
             Log.d(TAG,
                     "sendMessage: transactionId=" + transactionId + ", pubSubId=" + pubSubId
@@ -250,72 +293,78 @@
                             + messageLength);
         }
 
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = sendMessageNative(transactionId, WifiNative.class, WifiNative.sWlan0Index,
                         pubSubId, requestorInstanceId, dest, message, messageLength);
             }
             if (DBG) Log.d(TAG, "sendMessageNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "sendMessage: cannot initialize NAN");
-            success = false;
+            return false;
         }
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int stopPublishNative(short transactionId, Object cls, int iface,
-            int pubSubId);
+    private static native int stopPublishNative(short transactionId, Class<WifiNative> cls,
+            int iface, int pubSubId);
 
-    public void stopPublish(short transactionId, int pubSubId) {
-        boolean success;
-
+    /**
+     * Terminate a publish discovery session.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     * @param pubSubId ID of the publish/subscribe session - obtained when
+     *            creating a session.
+     */
+    public boolean stopPublish(short transactionId, int pubSubId) {
         if (VDBG) {
             Log.d(TAG, "stopPublish: transactionId=" + transactionId + ", pubSubId=" + pubSubId);
         }
 
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = stopPublishNative(transactionId, WifiNative.class, WifiNative.sWlan0Index,
                         pubSubId);
             }
             if (DBG) Log.d(TAG, "stopPublishNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "stopPublish: cannot initialize NAN");
-            success = false;
+            return false;
         }
-
-        // TODO: do something on !success - send failure message back
     }
 
-    private static native int stopSubscribeNative(short transactionId, Object cls, int iface,
-            int pubSubId);
+    private static native int stopSubscribeNative(short transactionId, Class<WifiNative> cls,
+            int iface, int pubSubId);
 
-    public void stopSubscribe(short transactionId, int pubSubId) {
-        boolean success;
-
+    /**
+     * Terminate a subscribe discovery session.
+     *
+     * @param transactionId transactionId Transaction ID for the transaction -
+     *            used in the async callback to match with the original request.
+     * @param pubSubId ID of the publish/subscribe session - obtained when
+     *            creating a session.
+     */
+    public boolean stopSubscribe(short transactionId, int pubSubId) {
         if (VDBG) {
             Log.d(TAG, "stopSubscribe: transactionId=" + transactionId + ", pubSubId=" + pubSubId);
         }
 
-        if (isNanInit(true)) {
+        if (isNanInit()) {
             int ret;
             synchronized (WifiNative.sLock) {
                 ret = stopSubscribeNative(transactionId, WifiNative.class, WifiNative.sWlan0Index,
                         pubSubId);
             }
             if (DBG) Log.d(TAG, "stopSubscribeNative: ret=" + ret);
-            success = ret == WIFI_SUCCESS;
+            return ret == WIFI_SUCCESS;
         } else {
             Log.w(TAG, "stopSubscribe: cannot initialize NAN");
-            success = false;
+            return false;
         }
-
-        // TODO: do something on !success - send failure message back
     }
 
     // EVENTS
@@ -327,9 +376,11 @@
     public static final int NAN_RESPONSE_TRANSMIT_FOLLOWUP = 4;
     public static final int NAN_RESPONSE_SUBSCRIBE = 5;
     public static final int NAN_RESPONSE_SUBSCRIBE_CANCEL = 6;
+    public static final int NAN_RESPONSE_CONFIG = 8;
     public static final int NAN_RESPONSE_GET_CAPABILITIES = 12;
 
     // direct copy from wifi_nan.h: need to keep in sync
+    /* NAN Protocol Response Codes */
     public static final int NAN_STATUS_SUCCESS = 0;
     public static final int NAN_STATUS_TIMEOUT = 1;
     public static final int NAN_STATUS_DE_FAILURE = 2;
@@ -350,8 +401,14 @@
     public static final int NAN_STATUS_INVALID_TLV_VALUE = 17;
     public static final int NAN_STATUS_INVALID_TX_PRIORITY = 18;
     public static final int NAN_STATUS_INVALID_CONNECTION_MAP = 19;
+    public static final int NAN_STATUS_INVALID_TCA_ID = 20;
+    public static final int NAN_STATUS_INVALID_STATS_ID = 21;
+    public static final int NAN_STATUS_NAN_NOT_ALLOWED = 22;
+    public static final int NAN_STATUS_NO_OTA_ACK = 23;
+    public static final int NAN_STATUS_TX_FAIL = 24;
+    public static final int NAN_STATUS_ALREADY_ENABLED = 25;
 
-    // NAN Configuration Response codes
+    /* NAN Configuration Response codes */
     public static final int NAN_STATUS_INVALID_RSSI_CLOSE_VALUE = 4096;
     public static final int NAN_STATUS_INVALID_RSSI_MIDDLE_VALUE = 4097;
     public static final int NAN_STATUS_INVALID_HOP_COUNT_LIMIT = 4098;
@@ -374,8 +431,12 @@
     public static final int NAN_STATUS_INVALID_POST_NAN_DISCOVERY_BITMAP_VALUE = 4115;
     public static final int NAN_STATUS_MISSING_FUTHER_AVAILABILITY_MAP = 4116;
     public static final int NAN_STATUS_INVALID_BAND_CONFIG_FLAGS = 4117;
+    public static final int NAN_STATUS_INVALID_RANDOM_FACTOR_UPDATE_TIME_VALUE = 4118;
+    public static final int NAN_STATUS_INVALID_ONGOING_SCAN_PERIOD = 4119;
+    public static final int NAN_STATUS_INVALID_DW_INTERVAL_VALUE = 4120;
+    public static final int NAN_STATUS_INVALID_DB_INTERVAL_VALUE = 4121;
 
-    // publish/subscribe termination reasons
+    /* publish/subscribe termination reasons */
     public static final int NAN_TERMINATED_REASON_INVALID = 8192;
     public static final int NAN_TERMINATED_REASON_TIMEOUT = 8193;
     public static final int NAN_TERMINATED_REASON_USER_REQUEST = 8194;
@@ -387,31 +448,34 @@
     public static final int NAN_TERMINATED_REASON_POST_DISC_LEN_EXCEEDED = 8200;
     public static final int NAN_TERMINATED_REASON_FURTHER_AVAIL_MAP_EMPTY = 8201;
 
-    private static int translateHalStatusToPublicStatus(int halStatus) {
+    /* 9000-9500 NDP Status type */
+    public static final int NAN_STATUS_NDP_UNSUPPORTED_CONCURRENCY = 9000;
+    public static final int NAN_STATUS_NDP_NAN_DATA_IFACE_CREATE_FAILED = 9001;
+    public static final int NAN_STATUS_NDP_NAN_DATA_IFACE_DELETE_FAILED = 9002;
+    public static final int NAN_STATUS_NDP_DATA_INITIATOR_REQUEST_FAILED = 9003;
+    public static final int NAN_STATUS_NDP_DATA_RESPONDER_REQUEST_FAILED = 9004;
+    public static final int NAN_STATUS_NDP_INVALID_SERVICE_INSTANCE_ID = 9005;
+    public static final int NAN_STATUS_NDP_INVALID_NDP_INSTANCE_ID = 9006;
+    public static final int NAN_STATUS_NDP_INVALID_RESPONSE_CODE = 9007;
+    public static final int NAN_STATUS_NDP_INVALID_APP_INFO_LEN = 9008;
+
+    /* OTA failures and timeouts during negotiation */
+    public static final int NAN_STATUS_NDP_MGMT_FRAME_REQUEST_FAILED = 9009;
+    public static final int NAN_STATUS_NDP_MGMT_FRAME_RESPONSE_FAILED = 9010;
+    public static final int NAN_STATUS_NDP_MGMT_FRAME_CONFIRM_FAILED = 9011;
+    public static final int NAN_STATUS_NDP_END_FAILED = 9012;
+    public static final int NAN_STATUS_NDP_MGMT_FRAME_END_REQUEST_FAILED = 9013;
+
+    /* 9500 onwards vendor specific error codes */
+    public static final int NAN_STATUS_NDP_VENDOR_SPECIFIC_ERROR = 9500;
+
+    private static int translateHalStatusToNanEventCallbackReason(int halStatus) {
         switch (halStatus) {
-            case NAN_STATUS_NO_SPACE_AVAILABLE:
-                return WifiNanSessionListener.FAIL_REASON_NO_RESOURCES;
-
-            case NAN_STATUS_TIMEOUT:
-            case NAN_STATUS_DE_FAILURE:
-            case NAN_STATUS_DISABLE_IN_PROGRESS:
-                return WifiNanSessionListener.FAIL_REASON_OTHER;
-
-            case NAN_STATUS_INVALID_MSG_VERSION:
-            case NAN_STATUS_INVALID_MSG_LEN:
-            case NAN_STATUS_INVALID_MSG_ID:
-            case NAN_STATUS_INVALID_HANDLE:
-            case NAN_STATUS_INVALID_PUBLISH_TYPE:
-            case NAN_STATUS_INVALID_TX_TYPE:
-            case NAN_STATUS_INVALID_MATCH_ALGORITHM:
-            case NAN_STATUS_INVALID_TLV_LEN:
-            case NAN_STATUS_INVALID_TLV_TYPE:
-            case NAN_STATUS_MISSING_TLV_TYPE:
-            case NAN_STATUS_INVALID_TOTAL_TLVS_LEN:
-            case NAN_STATUS_INVALID_MATCH_HANDLE:
-            case NAN_STATUS_INVALID_TLV_VALUE:
-            case NAN_STATUS_INVALID_TX_PRIORITY:
-            case NAN_STATUS_INVALID_CONNECTION_MAP:
+            case NAN_STATUS_SUCCESS:
+                /*
+                 * TODO: b/27914592 all of these codes will be cleaned-up/reduced.
+                 */
+                return WifiNanEventCallback.REASON_OTHER;
             case NAN_STATUS_INVALID_RSSI_CLOSE_VALUE:
             case NAN_STATUS_INVALID_RSSI_MIDDLE_VALUE:
             case NAN_STATUS_INVALID_HOP_COUNT_LIMIT:
@@ -434,13 +498,22 @@
             case NAN_STATUS_INVALID_POST_NAN_DISCOVERY_BITMAP_VALUE:
             case NAN_STATUS_MISSING_FUTHER_AVAILABILITY_MAP:
             case NAN_STATUS_INVALID_BAND_CONFIG_FLAGS:
-                return WifiNanSessionListener.FAIL_REASON_INVALID_ARGS;
+            case NAN_STATUS_INVALID_RANDOM_FACTOR_UPDATE_TIME_VALUE:
+            case NAN_STATUS_INVALID_ONGOING_SCAN_PERIOD:
+            case NAN_STATUS_INVALID_DW_INTERVAL_VALUE:
+            case NAN_STATUS_INVALID_DB_INTERVAL_VALUE:
+                return WifiNanEventCallback.REASON_INVALID_ARGS;
+        }
 
-                // publish/subscribe termination reasons
+        return WifiNanEventCallback.REASON_OTHER;
+    }
+
+    private static int translateHalStatusToNanSessionCallbackTerminate(int halStatus) {
+        switch (halStatus) {
             case NAN_TERMINATED_REASON_TIMEOUT:
             case NAN_TERMINATED_REASON_USER_REQUEST:
             case NAN_TERMINATED_REASON_COUNT_REACHED:
-                return WifiNanSessionListener.TERMINATE_REASON_DONE;
+                return WifiNanSessionCallback.TERMINATE_REASON_DONE;
 
             case NAN_TERMINATED_REASON_INVALID:
             case NAN_TERMINATED_REASON_FAILURE:
@@ -449,10 +522,46 @@
             case NAN_TERMINATED_REASON_POST_DISC_ATTR_EXPIRED:
             case NAN_TERMINATED_REASON_POST_DISC_LEN_EXCEEDED:
             case NAN_TERMINATED_REASON_FURTHER_AVAIL_MAP_EMPTY:
-                return WifiNanSessionListener.TERMINATE_REASON_FAIL;
+                return WifiNanSessionCallback.TERMINATE_REASON_FAIL;
         }
 
-        return WifiNanSessionListener.FAIL_REASON_OTHER;
+        return WifiNanSessionCallback.TERMINATE_REASON_FAIL;
+    }
+
+    private static int translateHalStatusToNanSessionCallbackReason(int halStatus) {
+        switch (halStatus) {
+            case NAN_STATUS_TIMEOUT:
+            case NAN_STATUS_DE_FAILURE:
+            case NAN_STATUS_INVALID_MSG_VERSION:
+            case NAN_STATUS_INVALID_MSG_LEN:
+            case NAN_STATUS_INVALID_MSG_ID:
+            case NAN_STATUS_INVALID_HANDLE:
+                return WifiNanSessionCallback.REASON_OTHER;
+            case NAN_STATUS_NO_SPACE_AVAILABLE:
+                return WifiNanSessionCallback.REASON_NO_RESOURCES;
+            case NAN_STATUS_INVALID_PUBLISH_TYPE:
+            case NAN_STATUS_INVALID_TX_TYPE:
+            case NAN_STATUS_INVALID_MATCH_ALGORITHM:
+                return WifiNanSessionCallback.REASON_INVALID_ARGS;
+            case NAN_STATUS_DISABLE_IN_PROGRESS:
+            case NAN_STATUS_INVALID_TLV_LEN:
+            case NAN_STATUS_INVALID_TLV_TYPE:
+            case NAN_STATUS_MISSING_TLV_TYPE:
+            case NAN_STATUS_INVALID_TOTAL_TLVS_LEN:
+            case NAN_STATUS_INVALID_MATCH_HANDLE:
+            case NAN_STATUS_INVALID_TLV_VALUE:
+            case NAN_STATUS_INVALID_TX_PRIORITY:
+            case NAN_STATUS_INVALID_CONNECTION_MAP:
+            case NAN_STATUS_INVALID_TCA_ID:
+            case NAN_STATUS_INVALID_STATS_ID:
+            case NAN_STATUS_NAN_NOT_ALLOWED:
+                return WifiNanSessionCallback.REASON_OTHER;
+            case NAN_STATUS_NO_OTA_ACK:
+            case NAN_STATUS_TX_FAIL:
+                return WifiNanSessionCallback.REASON_TX_FAIL;
+        }
+
+        return WifiNanSessionCallback.REASON_OTHER;
     }
 
     // callback from native
@@ -463,14 +572,17 @@
                     "onNanNotifyResponse: transactionId=" + transactionId + ", responseType="
                     + responseType + ", status=" + status + ", value=" + value);
         }
+        WifiNanStateManager stateMgr = WifiNanStateManager.getInstance();
 
         switch (responseType) {
             case NAN_RESPONSE_ENABLED:
+                /* fall through */
+            case NAN_RESPONSE_CONFIG:
                 if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onConfigCompleted(transactionId);
+                    stateMgr.onConfigSuccessResponse(transactionId);
                 } else {
-                    WifiNanStateManager.getInstance().onConfigFailed(transactionId,
-                            translateHalStatusToPublicStatus(status));
+                    stateMgr.onConfigFailedResponse(transactionId,
+                            translateHalStatusToNanEventCallbackReason(status));
                 }
                 break;
             case NAN_RESPONSE_PUBLISH_CANCEL:
@@ -481,10 +593,10 @@
                 break;
             case NAN_RESPONSE_TRANSMIT_FOLLOWUP:
                 if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onMessageSendSuccess(transactionId);
+                    stateMgr.onMessageSendQueuedSuccessResponse(transactionId);
                 } else {
-                    WifiNanStateManager.getInstance().onMessageSendFail(transactionId,
-                            translateHalStatusToPublicStatus(status));
+                    stateMgr.onMessageSendQueuedFailResponse(transactionId,
+                            translateHalStatusToNanSessionCallbackReason(status));
                 }
                 break;
             case NAN_RESPONSE_SUBSCRIBE_CANCEL:
@@ -494,8 +606,7 @@
                 }
                 break;
             default:
-                WifiNanStateManager.getInstance().onUnknownTransaction(responseType, transactionId,
-                        translateHalStatusToPublicStatus(status));
+                Log.e(TAG, "onNanNotifyResponse: unclassified responseType=" + responseType);
                 break;
         }
     }
@@ -512,23 +623,25 @@
         switch (responseType) {
             case NAN_RESPONSE_PUBLISH:
                 if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onPublishSuccess(transactionId, pubSubId);
+                    WifiNanStateManager.getInstance().onSessionConfigSuccessResponse(transactionId,
+                            true, pubSubId);
                 } else {
-                    WifiNanStateManager.getInstance().onPublishFail(transactionId,
-                            translateHalStatusToPublicStatus(status));
+                    WifiNanStateManager.getInstance().onSessionConfigFailResponse(transactionId,
+                            true, translateHalStatusToNanSessionCallbackReason(status));
                 }
                 break;
             case NAN_RESPONSE_SUBSCRIBE:
                 if (status == NAN_STATUS_SUCCESS) {
-                    WifiNanStateManager.getInstance().onSubscribeSuccess(transactionId, pubSubId);
+                    WifiNanStateManager.getInstance().onSessionConfigSuccessResponse(transactionId,
+                            false, pubSubId);
                 } else {
-                    WifiNanStateManager.getInstance().onSubscribeFail(transactionId,
-                            translateHalStatusToPublicStatus(status));
+                    WifiNanStateManager.getInstance().onSessionConfigFailResponse(transactionId,
+                            false, translateHalStatusToNanSessionCallbackReason(status));
                 }
                 break;
             default:
-                WifiNanStateManager.getInstance().onUnknownTransaction(responseType, transactionId,
-                        translateHalStatusToPublicStatus(status));
+                Log.wtf(TAG, "onNanNotifyResponsePublishSubscribe: unclassified responseType="
+                        + responseType);
                 break;
         }
     }
@@ -541,7 +654,7 @@
         }
 
         if (status == NAN_STATUS_SUCCESS) {
-            WifiNanStateManager.getInstance().onCapabilitiesUpdate(transactionId, capabilities);
+            WifiNanStateManager.getInstance().onCapabilitiesUpdateNotification(capabilities);
         } else {
             Log.e(TAG,
                     "onNanNotifyResponseCapabilities: error status=" + status + ", value=" + value);
@@ -560,13 +673,13 @@
         }
 
         if (eventType == NAN_EVENT_ID_DISC_MAC_ADDR) {
-            WifiNanStateManager.getInstance().onInterfaceAddressChange(mac);
+            WifiNanStateManager.getInstance().onInterfaceAddressChangeNotification(mac);
         } else if (eventType == NAN_EVENT_ID_STARTED_CLUSTER) {
-            WifiNanStateManager.getInstance()
-                    .onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_STARTED, mac);
+            WifiNanStateManager.getInstance().onClusterChangeNotification(
+                    WifiNanClientState.CLUSTER_CHANGE_EVENT_STARTED, mac);
         } else if (eventType == NAN_EVENT_ID_JOINED_CLUSTER) {
-            WifiNanStateManager.getInstance()
-                    .onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_JOINED, mac);
+            WifiNanStateManager.getInstance().onClusterChangeNotification(
+                    WifiNanClientState.CLUSTER_CHANGE_EVENT_JOINED, mac);
         } else {
             Log.w(TAG, "onDiscoveryEngineEvent: invalid eventType=" + eventType);
         }
@@ -579,11 +692,12 @@
         if (VDBG) {
             Log.v(TAG, "onMatchEvent: pubSubId=" + pubSubId + ", requestorInstanceId="
                     + requestorInstanceId + ", mac=" + String.valueOf(HexEncoding.encode(mac))
-                    + ", serviceSpecificInfo=" + serviceSpecificInfo + ", matchFilterLength="
-                    + matchFilterLength + ", matchFilter=" + matchFilter);
+                    + ", serviceSpecificInfo=" + Arrays.toString(serviceSpecificInfo)
+                    + ", matchFilterLength=" + matchFilterLength + ", matchFilter="
+                    + Arrays.toString(matchFilter));
         }
 
-        WifiNanStateManager.getInstance().onMatch(pubSubId, requestorInstanceId, mac,
+        WifiNanStateManager.getInstance().onMatchNotification(pubSubId, requestorInstanceId, mac,
                 serviceSpecificInfo, serviceSpecificInfoLength, matchFilter, matchFilterLength);
     }
 
@@ -591,8 +705,8 @@
     private static void onPublishTerminated(int publishId, int status) {
         if (VDBG) Log.v(TAG, "onPublishTerminated: publishId=" + publishId + ", status=" + status);
 
-        WifiNanStateManager.getInstance().onPublishTerminated(publishId,
-                translateHalStatusToPublicStatus(status));
+        WifiNanStateManager.getInstance().onSessionTerminatedNotification(publishId,
+                translateHalStatusToNanSessionCallbackTerminate(status), true);
     }
 
     // callback from native
@@ -601,8 +715,8 @@
             Log.v(TAG, "onSubscribeTerminated: subscribeId=" + subscribeId + ", status=" + status);
         }
 
-        WifiNanStateManager.getInstance().onSubscribeTerminated(subscribeId,
-                translateHalStatusToPublicStatus(status));
+        WifiNanStateManager.getInstance().onSessionTerminatedNotification(subscribeId,
+                translateHalStatusToNanSessionCallbackTerminate(status), false);
     }
 
     // callback from native
@@ -614,14 +728,30 @@
                     + ", messageLength=" + messageLength);
         }
 
-        WifiNanStateManager.getInstance().onMessageReceived(pubSubId, requestorInstanceId, mac,
-                message, messageLength);
+        WifiNanStateManager.getInstance().onMessageReceivedNotification(pubSubId,
+                requestorInstanceId, mac, message, messageLength);
     }
 
     // callback from native
     private static void onDisabledEvent(int status) {
         if (VDBG) Log.v(TAG, "onDisabledEvent: status=" + status);
 
-        WifiNanStateManager.getInstance().onNanDown(translateHalStatusToPublicStatus(status));
+        WifiNanStateManager.getInstance()
+                .onNanDownNotification(translateHalStatusToNanEventCallbackReason(status));
+    }
+
+    // callback from native
+    private static void onTransmitFollowupEvent(short transactionId, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onTransmitFollowupEvent: transactionId=" + transactionId + ", reason="
+                    + reason);
+        }
+
+        if (reason == NAN_STATUS_SUCCESS) {
+            WifiNanStateManager.getInstance().onMessageSendSuccessNotification(transactionId);
+        } else {
+            WifiNanStateManager.getInstance().onMessageSendFailNotification(transactionId,
+                    translateHalStatusToNanSessionCallbackReason(reason));
+        }
     }
 }
diff --git a/service/java/com/android/server/wifi/nan/WifiNanRttStateManager.java b/service/java/com/android/server/wifi/nan/WifiNanRttStateManager.java
new file mode 100644
index 0000000..9d9f2d4
--- /dev/null
+++ b/service/java/com/android/server/wifi/nan/WifiNanRttStateManager.java
@@ -0,0 +1,203 @@
+/*
+ * 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.nan;
+
+import android.content.Context;
+import android.net.wifi.IRttManager;
+import android.net.wifi.RttManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.AsyncChannel;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+
+
+/**
+ * Manages interactions between the NAN and the RTT service. Duplicates some of the functionality
+ * of the RttManager.
+ */
+public class WifiNanRttStateManager {
+    private static final String TAG = "WifiNanRttStateManager";
+
+    private static final boolean DBG = true;
+    private static final boolean VDBG = true; // STOPSHIP if true
+
+    private final SparseArray<WifiNanClientState> mPendingOperations = new SparseArray<>();
+    private AsyncChannel mAsyncChannel;
+
+    /**
+     * Initializes the connection to the RTT service.
+     */
+    public void start(Context context, Looper looper) {
+        if (VDBG) Log.v(TAG, "start()");
+
+        IBinder b = ServiceManager.getService(Context.WIFI_RTT_SERVICE);
+        IRttManager service = IRttManager.Stub.asInterface(b);
+        if (service == null) {
+            Log.e(TAG, "start(): not able to get WIFI_RTT_SERVICE");
+            return;
+        }
+
+        startWithRttService(context, looper, service);
+    }
+
+    /**
+     * Initializes the connection to the RTT service.
+     */
+    @VisibleForTesting
+    public void startWithRttService(Context context, Looper looper, IRttManager service) {
+        Messenger messenger;
+        try {
+            messenger = service.getMessenger();
+        } catch (RemoteException e) {
+            Log.e(TAG, "start(): not able to getMessenger() of WIFI_RTT_SERVICE");
+            return;
+        }
+
+        mAsyncChannel = new AsyncChannel();
+        mAsyncChannel.connect(context, new NanRttHandler(looper), messenger);
+    }
+
+    private WifiNanClientState getAndRemovePendingOperationClient(int rangingId) {
+        WifiNanClientState client = mPendingOperations.get(rangingId);
+        mPendingOperations.delete(rangingId);
+        return client;
+    }
+
+    /**
+     * Start a ranging operation for the client + peer MAC.
+     */
+    public void startRanging(int rangingId, WifiNanClientState client,
+                             RttManager.RttParams[] params) {
+        if (VDBG) {
+            Log.v(TAG, "startRanging: rangingId=" + rangingId + ", parms="
+                    + Arrays.toString(params));
+        }
+
+        if (mAsyncChannel == null) {
+            Log.d(TAG, "startRanging(): AsyncChannel to RTT service not configured - failing");
+            client.onRangingFailure(rangingId, RttManager.REASON_NOT_AVAILABLE,
+                    "NAN service not able to configure connection to RTT service");
+            return;
+        }
+
+        mPendingOperations.put(rangingId, client);
+        RttManager.ParcelableRttParams pparams = new RttManager.ParcelableRttParams(params);
+        mAsyncChannel.sendMessage(RttManager.CMD_OP_START_RANGING, 0, rangingId, pparams);
+    }
+
+    private class NanRttHandler extends Handler {
+        NanRttHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (VDBG) Log.v(TAG, "handleMessage(): " + msg.what);
+
+            // channel configuration messages
+            switch (msg.what) {
+                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                        mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+                    } else {
+                        Log.e(TAG, "Failed to set up channel connection to RTT service");
+                        mAsyncChannel = null;
+                    }
+                    return;
+                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
+                    /* NOP */
+                    return;
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                    Log.e(TAG, "Channel connection to RTT service lost");
+                    mAsyncChannel = null;
+                    return;
+            }
+
+            // RTT-specific messages
+            WifiNanClientState client = getAndRemovePendingOperationClient(msg.arg2);
+            if (client == null) {
+                Log.e(TAG, "handleMessage(): RTT message (" + msg.what
+                        + ") -- cannot find registered pending operation client for ID "
+                        + msg.arg2);
+                return;
+            }
+
+            switch (msg.what) {
+                case RttManager.CMD_OP_SUCCEEDED: {
+                    int rangingId = msg.arg2;
+                    RttManager.ParcelableRttResults results = (RttManager.ParcelableRttResults)
+                            msg.obj;
+                    if (VDBG) {
+                        Log.v(TAG, "CMD_OP_SUCCEEDED: rangingId=" + rangingId + ", results="
+                                + results);
+                    }
+                    for (int i = 0; i < results.mResults.length; ++i) {
+                        /*
+                         * TODO: store peer ID rather than null in the return result.
+                         */
+                        results.mResults[i].bssid = null;
+                    }
+                    client.onRangingSuccess(rangingId, results);
+                    break;
+                }
+                case RttManager.CMD_OP_FAILED: {
+                    int rangingId = msg.arg2;
+                    int reason = msg.arg1;
+                    String description = ((Bundle) msg.obj).getString(RttManager.DESCRIPTION_KEY);
+                    if (VDBG) {
+                        Log.v(TAG, "CMD_OP_FAILED: rangingId=" + rangingId + ", reason=" + reason
+                                + ", description=" + description);
+                    }
+                    client.onRangingFailure(rangingId, reason, description);
+                    break;
+                }
+                case RttManager.CMD_OP_ABORTED: {
+                    int rangingId = msg.arg2;
+                    if (VDBG) {
+                        Log.v(TAG, "CMD_OP_ABORTED: rangingId=" + rangingId);
+                    }
+                    client.onRangingAborted(rangingId);
+                    break;
+                }
+                default:
+                    Log.e(TAG, "handleMessage(): ignoring message " + msg.what);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("WifiNanRttStateManager:");
+        pw.println("  mPendingOperations: [" + mPendingOperations + "]");
+    }
+}
diff --git a/service/java/com/android/server/wifi/nan/WifiNanService.java b/service/java/com/android/server/wifi/nan/WifiNanService.java
index b5920f2..6589067 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanService.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanService.java
@@ -21,6 +21,10 @@
 
 import com.android.server.SystemService;
 
+/**
+ * Service implementing Wi-Fi NAN functionality. Delegates actual interface
+ * implementation to WifiNanServiceImpl.
+ */
 public final class WifiNanService extends SystemService {
     private static final String TAG = "WifiNanService";
     final WifiNanServiceImpl mImpl;
@@ -40,6 +44,8 @@
     public void onBootPhase(int phase) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
             mImpl.start();
+        } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
+            mImpl.startLate();
         }
     }
 }
diff --git a/service/java/com/android/server/wifi/nan/WifiNanServiceImpl.java b/service/java/com/android/server/wifi/nan/WifiNanServiceImpl.java
index deefe94..917d815 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanServiceImpl.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanServiceImpl.java
@@ -18,14 +18,15 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.net.wifi.RttManager;
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.IWifiNanEventListener;
+import android.net.wifi.nan.IWifiNanEventCallback;
 import android.net.wifi.nan.IWifiNanManager;
-import android.net.wifi.nan.IWifiNanSessionListener;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanSession;
 import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -35,7 +36,14 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.Arrays;
 
+/**
+ * Implementation of the IWifiNanManager AIDL interface. Performs validity
+ * (permission and clientID-UID mapping) checks and delegates execution to the
+ * WifiNanStateManager singleton handler. Limited state to feedback which has to
+ * be provided instantly: client and session IDs.
+ */
 public class WifiNanServiceImpl extends IWifiNanManager.Stub {
     private static final String TAG = "WifiNanService";
     private static final boolean DBG = false;
@@ -43,180 +51,336 @@
 
     private Context mContext;
     private WifiNanStateManager mStateManager;
-    private final boolean mNanSupported;
 
     private final Object mLock = new Object();
-    private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByUid = new SparseArray<>();
-    private int mNextNetworkRequestToken = 1;
-    private int mNextSessionId = 1;
+    private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
+            new SparseArray<>();
+    private int mNextClientId = 1;
+    private int mNextRangingId = 1;
+    private final SparseArray<Integer> mUidByClientId = new SparseArray<>();
 
     public WifiNanServiceImpl(Context context) {
         mContext = context.getApplicationContext();
-
-        mNanSupported = mContext.getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_WIFI_NAN);
-        if (DBG) Log.w(TAG, "WifiNanServiceImpl: mNanSupported=" + mNanSupported);
-
         mStateManager = WifiNanStateManager.getInstance();
     }
 
+    /**
+     * Proxy for the final native call of the parent class. Enables mocking of
+     * the function.
+     */
+    public int getMockableCallingUid() {
+        return getCallingUid();
+    }
+
+    /**
+     * Start the service: allocate a new thread (for now), start the handlers of
+     * the components of the service.
+     */
     public void start() {
         Log.i(TAG, "Starting Wi-Fi NAN service");
 
-        // TODO: share worker thread with other Wi-Fi handlers
+        // TODO: share worker thread with other Wi-Fi handlers (b/27924886)
         HandlerThread wifiNanThread = new HandlerThread("wifiNanService");
         wifiNanThread.start();
 
-        mStateManager.start(wifiNanThread.getLooper());
+        mStateManager.start(mContext, wifiNanThread.getLooper());
+    }
+
+    /**
+     * Start/initialize portions of the service which require the boot stage to be complete.
+     */
+    public void startLate() {
+        Log.i(TAG, "Late initialization of Wi-Fi NAN service");
+
+        mStateManager.startLate();
     }
 
     @Override
-    public void connect(final IBinder binder, IWifiNanEventListener listener, int events) {
+    public void enableUsage() {
         enforceAccessPermission();
         enforceChangePermission();
+        /*
+         * TODO: enforce additional permissions b/27696149.
+         */
 
-        final int uid = getCallingUid();
+        mStateManager.enableUsage();
+    }
 
-        if (VDBG) Log.v(TAG, "connect: uid=" + uid);
+    @Override
+    public void disableUsage() {
+        enforceAccessPermission();
+        enforceChangePermission();
+        /*
+         * TODO: enforce additional permissions b/27696149.
+         */
 
+        mStateManager.disableUsage();
+
+        /*
+         * Potential leak (b/27796984) since we keep app information here (uid,
+         * binder-link-to-death), while clearing all state information. However:
+         * (1) can't clear all information since don't have binder, (2)
+         * information will clear once app dies, (3) allows us to do security
+         * checks in the future.
+         */
+    }
+
+    @Override
+    public boolean isUsageEnabled() {
+        enforceAccessPermission();
+
+        return mStateManager.isUsageEnabled();
+    }
+
+    @Override
+    public int connect(final IBinder binder, IWifiNanEventCallback callback,
+            ConfigRequest configRequest) {
+        enforceAccessPermission();
+        enforceChangePermission();
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        if (binder == null) {
+            throw new IllegalArgumentException("Binder must not be null");
+        }
+
+        if (configRequest != null) {
+            /*
+             * TODO: enforce additional permissions if configuration is
+             * non-standard (i.e. the system API). (b/27696149)
+             */
+        } else {
+            configRequest = new ConfigRequest.Builder().build();
+        }
+        configRequest.validate();
+
+        final int uid = getMockableCallingUid();
+
+        final int clientId;
+        synchronized (mLock) {
+            clientId = mNextClientId++;
+        }
+
+        if (VDBG) {
+            Log.v(TAG, "connect: uid=" + uid + ", clientId=" + clientId + ", configRequest"
+                    + configRequest);
+        }
 
         IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
             @Override
             public void binderDied() {
-                if (DBG) Log.d(TAG, "binderDied: uid=" + uid);
+                if (DBG) Log.d(TAG, "binderDied: clientId=" + clientId);
                 binder.unlinkToDeath(this, 0);
 
                 synchronized (mLock) {
-                    mDeathRecipientsByUid.delete(uid);
+                    mDeathRecipientsByClientId.delete(clientId);
+                    mUidByClientId.delete(clientId);
                 }
 
-                mStateManager.disconnect(uid);
+                mStateManager.disconnect(clientId);
             }
         };
-        synchronized (mLock) {
-            mDeathRecipientsByUid.put(uid, dr);
-        }
+
         try {
             binder.linkToDeath(dr, 0);
         } catch (RemoteException e) {
-            Log.w(TAG, "Error on linkToDeath - " + e);
+            Log.e(TAG, "Error on linkToDeath - " + e);
+            try {
+                callback.onConnectFail(WifiNanEventCallback.REASON_OTHER);
+            } catch (RemoteException e1) {
+                Log.e(TAG, "Error on onConnectFail()");
+            }
+            return 0;
         }
 
-        mStateManager.connect(uid, listener, events);
+        synchronized (mLock) {
+            mDeathRecipientsByClientId.put(clientId, dr);
+            mUidByClientId.put(clientId, uid);
+        }
+
+        mStateManager.connect(clientId, callback, configRequest);
+
+        return clientId;
     }
 
     @Override
-    public void disconnect(IBinder binder) {
+    public void disconnect(int clientId, IBinder binder) {
         enforceAccessPermission();
         enforceChangePermission();
 
-        int uid = getCallingUid();
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) Log.v(TAG, "disconnect: uid=" + uid + ", clientId=" + clientId);
 
-        if (VDBG) Log.v(TAG, "disconnect: uid=" + uid);
+        if (binder == null) {
+            throw new IllegalArgumentException("Binder must not be null");
+        }
 
         synchronized (mLock) {
-            IBinder.DeathRecipient dr = mDeathRecipientsByUid.get(uid);
+            IBinder.DeathRecipient dr = mDeathRecipientsByClientId.get(clientId);
             if (dr != null) {
                 binder.unlinkToDeath(dr, 0);
-                mDeathRecipientsByUid.delete(uid);
+                mDeathRecipientsByClientId.delete(clientId);
             }
+            mUidByClientId.delete(clientId);
         }
 
-        mStateManager.disconnect(uid);
+        mStateManager.disconnect(clientId);
     }
 
     @Override
-    public void requestConfig(ConfigRequest configRequest) {
+    public void terminateSession(int clientId, int sessionId) {
         enforceAccessPermission();
         enforceChangePermission();
 
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "terminateSession: sessionId=" + sessionId + ", uid=" + uid + ", clientId="
+                    + clientId);
+        }
+
+        mStateManager.terminateSession(clientId, sessionId);
+    }
+
+    @Override
+    public void publish(int clientId, PublishConfig publishConfig,
+            IWifiNanSessionCallback callback) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        if (publishConfig == null) {
+            throw new IllegalArgumentException("PublishConfig must not be null");
+        }
+        publishConfig.validate();
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "publish: uid=" + uid + ", clientId=" + clientId + ", publishConfig="
+                    + publishConfig + ", callback=" + callback);
+        }
+
+        mStateManager.publish(clientId, publishConfig, callback);
+    }
+
+    @Override
+    public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        if (publishConfig == null) {
+            throw new IllegalArgumentException("PublishConfig must not be null");
+        }
+        publishConfig.validate();
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "updatePublish: uid=" + uid + ", clientId=" + clientId + ", sessionId="
+                    + sessionId + ", config=" + publishConfig);
+        }
+
+        mStateManager.updatePublish(clientId, sessionId, publishConfig);
+    }
+
+    @Override
+    public void subscribe(int clientId, SubscribeConfig subscribeConfig,
+            IWifiNanSessionCallback callback) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        if (subscribeConfig == null) {
+            throw new IllegalArgumentException("SubscribeConfig must not be null");
+        }
+        subscribeConfig.validate();
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "subscribe: uid=" + uid + ", clientId=" + clientId + ", config="
+                    + subscribeConfig + ", callback=" + callback);
+        }
+
+        mStateManager.subscribe(clientId, subscribeConfig, callback);
+    }
+
+    @Override
+    public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        if (subscribeConfig == null) {
+            throw new IllegalArgumentException("SubscribeConfig must not be null");
+        }
+        subscribeConfig.validate();
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "updateSubscribe: uid=" + uid + ", clientId=" + clientId + ", sessionId="
+                    + sessionId + ", config=" + subscribeConfig);
+        }
+
+        mStateManager.updateSubscribe(clientId, sessionId, subscribeConfig);
+    }
+
+    @Override
+    public void sendMessage(int clientId, int sessionId, int peerId, byte[] message,
+            int messageLength, int messageId, int retryCount) {
+        enforceAccessPermission();
+        enforceChangePermission();
+
+        if (messageLength != 0 && (message == null || message.length < messageLength)) {
+            throw new IllegalArgumentException(
+                    "Non-matching combination of message and messageLength");
+        }
+        if (retryCount < 0 || retryCount > WifiNanSession.MAX_SEND_RETRY_COUNT) {
+            throw new IllegalArgumentException("Invalid 'retryCount' must be non-negative "
+                    + "and <= WifiNanSession.MAX_SEND_RETRY_COUNT");
+        }
+
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
         if (VDBG) {
             Log.v(TAG,
-                    "requestConfig: uid=" + getCallingUid() + ", configRequest=" + configRequest);
+                    "sendMessage: sessionId=" + sessionId + ", uid=" + uid + ", clientId="
+                            + clientId + ", peerId=" + peerId + ", messageLength=" + messageLength
+                            + ", messageId=" + messageId + ", retryCount=" + retryCount);
         }
 
-        mStateManager.requestConfig(getCallingUid(), configRequest);
+        mStateManager.sendMessage(clientId, sessionId, peerId, message, messageLength, messageId,
+                retryCount);
     }
 
     @Override
-    public void stopSession(int sessionId) {
+    public int startRanging(int clientId, int sessionId, RttManager.ParcelableRttParams params) {
         enforceAccessPermission();
-        enforceChangePermission();
+        enforceLocationPermission();
 
-        if (VDBG) Log.v(TAG, "stopSession: sessionId=" + sessionId + ", uid=" + getCallingUid());
+        int uid = getMockableCallingUid();
+        enforceClientValidity(uid, clientId);
+        if (VDBG) {
+            Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
+                    + ", parms=" + Arrays.toString(params.mParams));
+        }
 
-        mStateManager.stopSession(getCallingUid(), sessionId);
-    }
+        if (params.mParams.length == 0) {
+            throw new IllegalArgumentException("Empty ranging parameters");
+        }
 
-    @Override
-    public void destroySession(int sessionId) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) Log.v(TAG, "destroySession: sessionId=" + sessionId + ", uid=" + getCallingUid());
-
-        mStateManager.destroySession(getCallingUid(), sessionId);
-    }
-
-    @Override
-    public int createSession(IWifiNanSessionListener listener, int events) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) Log.v(TAG, "createSession: uid=" + getCallingUid());
-
-        int sessionId;
+        int rangingId;
         synchronized (mLock) {
-            sessionId = mNextSessionId++;
+            rangingId = mNextRangingId++;
         }
-
-        mStateManager.createSession(getCallingUid(), sessionId, listener, events);
-
-        return sessionId;
-    }
-
-    @Override
-    public void publish(int sessionId, PublishData publishData, PublishSettings publishSettings) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) {
-            Log.v(TAG, "publish: uid=" + getCallingUid() + ", sessionId=" + sessionId + ", data='"
-                    + publishData + "', settings=" + publishSettings);
-        }
-
-        mStateManager.publish(getCallingUid(), sessionId, publishData, publishSettings);
-    }
-
-    @Override
-    public void subscribe(int sessionId, SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) {
-            Log.v(TAG, "subscribe: uid=" + getCallingUid() + ", sessionId=" + sessionId + ", data='"
-                    + subscribeData + "', settings=" + subscribeSettings);
-        }
-
-        mStateManager.subscribe(getCallingUid(), sessionId, subscribeData, subscribeSettings);
-    }
-
-    @Override
-    public void sendMessage(int sessionId, int peerId, byte[] message, int messageLength,
-            int messageId) {
-        enforceAccessPermission();
-        enforceChangePermission();
-
-        if (VDBG) {
-            Log.v(TAG,
-                    "sendMessage: sessionId=" + sessionId + ", uid=" + getCallingUid() + ", peerId="
-                            + peerId + ", messageLength=" + messageLength + ", messageId="
-                            + messageId);
-        }
-
-        mStateManager.sendMessage(getCallingUid(), sessionId, peerId, message, messageLength,
-                messageId);
+        mStateManager.startRanging(clientId, sessionId, params.mParams, rangingId);
+        return rangingId;
     }
 
     @Override
@@ -228,13 +392,27 @@
             return;
         }
         pw.println("Wi-Fi NAN Service");
-        pw.println("  mNanSupported: " + mNanSupported);
-        pw.println("  mNextNetworkRequestToken: " + mNextNetworkRequestToken);
-        pw.println("  mNextSessionId: " + mNextSessionId);
-        pw.println("  mDeathRecipientsByUid: " + mDeathRecipientsByUid);
+        synchronized (mLock) {
+            pw.println("  mNextClientId: " + mNextClientId);
+            pw.println("  mDeathRecipientsByClientId: " + mDeathRecipientsByClientId);
+            pw.println("  mUidByClientId: " + mUidByClientId);
+        }
         mStateManager.dump(fd, pw, args);
     }
 
+    private void enforceClientValidity(int uid, int clientId) {
+        Integer uidLookup;
+        synchronized (mLock) {
+            uidLookup = mUidByClientId.get(clientId);
+        }
+
+        boolean valid = uidLookup != null && uidLookup == uid;
+        if (!valid) {
+            throw new SecurityException("Attempting to use invalid uid+clientId mapping: uid=" + uid
+                    + ", clientId=" + clientId);
+        }
+    }
+
     private void enforceAccessPermission() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG);
     }
@@ -242,4 +420,9 @@
     private void enforceChangePermission() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
     }
+
+    private void enforceLocationPermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
+                TAG);
+    }
 }
diff --git a/service/java/com/android/server/wifi/nan/WifiNanSessionState.java b/service/java/com/android/server/wifi/nan/WifiNanSessionState.java
index ea64403..57aab30 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanSessionState.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanSessionState.java
@@ -16,12 +16,10 @@
 
 package com.android.server.wifi.nan;
 
-import android.net.wifi.nan.IWifiNanSessionListener;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
-import android.net.wifi.nan.WifiNanSessionListener;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanSessionCallback;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.SparseArray;
@@ -31,183 +29,168 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+/**
+ * Manages the state of a single NAN discovery session (publish or subscribe).
+ * Primary state consists of a callback through which session callbacks are
+ * executed as well as state related to currently active discovery sessions:
+ * publish/subscribe ID, and MAC address caching (hiding) from clients.
+ */
 public class WifiNanSessionState {
     private static final String TAG = "WifiNanSessionState";
     private static final boolean DBG = false;
     private static final boolean VDBG = false; // STOPSHIP if true
 
+    private int mSessionId;
+    private int mPubSubId;
+    private IWifiNanSessionCallback mCallback;
+    private boolean mIsPublishSession;
+
     private final SparseArray<String> mMacByRequestorInstanceId = new SparseArray<>();
 
-    private int mSessionId;
-    private IWifiNanSessionListener mListener;
-    private int mEvents;
-
-    private boolean mPubSubIdValid = false;
-    private int mPubSubId;
-
-    private static final int SESSION_TYPE_NOT_INIT = 0;
-    private static final int SESSION_TYPE_PUBLISH = 1;
-    private static final int SESSION_TYPE_SUBSCRIBE = 2;
-    private int mSessionType = SESSION_TYPE_NOT_INIT;
-
-    public WifiNanSessionState(int sessionId, IWifiNanSessionListener listener, int events) {
+    public WifiNanSessionState(int sessionId, int pubSubId, IWifiNanSessionCallback callback,
+                               boolean isPublishSession) {
         mSessionId = sessionId;
-        mListener = listener;
-        mEvents = events;
-    }
-
-    public void destroy() {
-        stop(WifiNanStateManager.getInstance().createNextTransactionId());
-        if (mPubSubIdValid) {
-            mMacByRequestorInstanceId.clear();
-            mListener = null;
-            mPubSubIdValid = false;
-        }
+        mPubSubId = pubSubId;
+        mCallback = callback;
+        mIsPublishSession = isPublishSession;
     }
 
     public int getSessionId() {
         return mSessionId;
     }
 
+    public IWifiNanSessionCallback getCallback() {
+        return mCallback;
+    }
+
+    /**
+     * Return the MAC address (String) of the specified peer ID - or a null if no such address is
+     * registered.
+     */
+    public String getMac(int peerId, String sep) {
+        String mac = mMacByRequestorInstanceId.get(peerId);
+        if (mac != null && sep != null && !sep.isEmpty()) {
+            mac = new StringBuilder(mac).insert(10, sep).insert(8, sep).insert(6, sep)
+                    .insert(4, sep).insert(2, sep).toString();
+        }
+        return mac;
+    }
+
+    /**
+     * Destroy the current discovery session - stops publishing or subscribing
+     * if currently active.
+     */
+    public void terminate() {
+        mCallback = null;
+
+        if (mIsPublishSession) {
+            WifiNanNative.getInstance().stopPublish((short) 0, mPubSubId);
+        } else {
+            WifiNanNative.getInstance().stopSubscribe((short) 0, mPubSubId);
+        }
+    }
+
+    /**
+     * Indicates whether the publish/subscribe ID (a HAL ID) corresponds to this
+     * session.
+     *
+     * @param pubSubId The publish/subscribe HAL ID to be tested.
+     * @return true if corresponds to this session, false otherwise.
+     */
     public boolean isPubSubIdSession(int pubSubId) {
-        return mPubSubIdValid && mPubSubId == pubSubId;
+        return mPubSubId == pubSubId;
     }
 
-    public void publish(short transactionId, PublishData data, PublishSettings settings) {
-        if (mSessionType == SESSION_TYPE_SUBSCRIBE) {
-            throw new IllegalStateException("A SUBSCRIBE session is being used for publish");
+    /**
+     * Modify a publish discovery session.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the
+     *            async callback to match with the original request.
+     * @param config Configuration of the publish session.
+     */
+    public boolean updatePublish(short transactionId, PublishConfig config) {
+        if (!mIsPublishSession) {
+            Log.e(TAG, "A SUBSCRIBE session is being used to publish");
+            try {
+                mCallback.onSessionConfigFail(WifiNanSessionCallback.REASON_OTHER);
+            } catch (RemoteException e) {
+                Log.e(TAG, "updatePublish: RemoteException=" + e);
+            }
+            return false;
         }
-        mSessionType = SESSION_TYPE_PUBLISH;
 
-        WifiNanNative.getInstance().publish(transactionId, mPubSubIdValid ? mPubSubId : 0, data,
-                settings);
+        return WifiNanNative.getInstance().publish(transactionId, mPubSubId, config);
     }
 
-    public void subscribe(short transactionId, SubscribeData data, SubscribeSettings settings) {
-        if (mSessionType == SESSION_TYPE_PUBLISH) {
-            throw new IllegalStateException("A PUBLISH session is being used for publish");
+    /**
+     * Modify a subscribe discovery session.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the
+     *            async callback to match with the original request.
+     * @param config Configuration of the subscribe session.
+     */
+    public boolean updateSubscribe(short transactionId, SubscribeConfig config) {
+        if (mIsPublishSession) {
+            Log.e(TAG, "A PUBLISH session is being used to subscribe");
+            try {
+                mCallback.onSessionConfigFail(WifiNanSessionCallback.REASON_OTHER);
+            } catch (RemoteException e) {
+                Log.e(TAG, "updateSubscribe: RemoteException=" + e);
+            }
+            return false;
         }
-        mSessionType = SESSION_TYPE_SUBSCRIBE;
 
-        WifiNanNative.getInstance().subscribe(transactionId, mPubSubIdValid ? mPubSubId : 0, data,
-                settings);
+        return WifiNanNative.getInstance().subscribe(transactionId, mPubSubId, config);
     }
 
-    public void sendMessage(short transactionId, int peerId, byte[] message, int messageLength,
+    /**
+     * Send a message to a peer which is part of a discovery session.
+     *
+     * @param transactionId Transaction ID for the transaction - used in the
+     *            async callback to match with the original request.
+     * @param peerId ID of the peer. Obtained through previous communication (a
+     *            match indication).
+     * @param message Message byte array to send to the peer.
+     * @param messageLength Length of the message byte array.
+     * @param messageId A message ID provided by caller to be used in any
+     *            callbacks related to the message (success/failure).
+     */
+    public boolean sendMessage(short transactionId, int peerId, byte[] message, int messageLength,
             int messageId) {
-        if (!mPubSubIdValid) {
-            Log.e(TAG, "sendMessage: attempting to send a message on a non-live session "
-                    + "(no successful publish or subscribe");
-            onMessageSendFail(messageId, WifiNanSessionListener.FAIL_REASON_NO_MATCH_SESSION);
-            return;
-        }
-
         String peerMacStr = mMacByRequestorInstanceId.get(peerId);
         if (peerMacStr == null) {
             Log.e(TAG, "sendMessage: attempting to send a message to an address which didn't "
                     + "match/contact us");
-            onMessageSendFail(messageId, WifiNanSessionListener.FAIL_REASON_NO_MATCH_SESSION);
-            return;
+            try {
+                mCallback.onMessageSendFail(messageId,
+                        WifiNanSessionCallback.REASON_NO_MATCH_SESSION);
+            } catch (RemoteException e) {
+                Log.e(TAG, "sendMessage: RemoteException=" + e);
+            }
+            return false;
         }
         byte[] peerMac = HexEncoding.decode(peerMacStr.toCharArray(), false);
 
-        WifiNanNative.getInstance().sendMessage(transactionId, mPubSubId, peerId, peerMac, message,
-                messageLength);
+        return WifiNanNative.getInstance().sendMessage(transactionId, mPubSubId, peerId, peerMac,
+                message, messageLength);
     }
 
-    public void stop(short transactionId) {
-        if (!mPubSubIdValid || mSessionType == SESSION_TYPE_NOT_INIT) {
-            Log.e(TAG, "sendMessage: attempting to stop pub/sub on a non-live session (no "
-                    + "successful publish or subscribe");
-            return;
-        }
-
-        if (mSessionType == SESSION_TYPE_PUBLISH) {
-            WifiNanNative.getInstance().stopPublish(transactionId, mPubSubId);
-        } else if (mSessionType == SESSION_TYPE_SUBSCRIBE) {
-            WifiNanNative.getInstance().stopSubscribe(transactionId, mPubSubId);
-        }
-    }
-
-    public void onPublishSuccess(int publishId) {
-        mPubSubId = publishId;
-        mPubSubIdValid = true;
-    }
-
-    public void onPublishFail(int status) {
-        mPubSubIdValid = false;
-        try {
-            if (mListener != null && (mEvents & WifiNanSessionListener.LISTEN_PUBLISH_FAIL) != 0) {
-                mListener.onPublishFail(status);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onPublishFail: RemoteException (FYI): " + e);
-        }
-    }
-
-    public void onPublishTerminated(int status) {
-        mPubSubIdValid = false;
-        try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_PUBLISH_TERMINATED) != 0) {
-                mListener.onPublishTerminated(status);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onPublishTerminated: RemoteException (FYI): " + e);
-        }
-    }
-
-    public void onSubscribeSuccess(int subscribeId) {
-        mPubSubId = subscribeId;
-        mPubSubIdValid = true;
-    }
-
-    public void onSubscribeFail(int status) {
-        mPubSubIdValid = false;
-        try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_SUBSCRIBE_FAIL) != 0) {
-                mListener.onSubscribeFail(status);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onSubscribeFail: RemoteException (FYI): " + e);
-        }
-    }
-
-    public void onSubscribeTerminated(int status) {
-        mPubSubIdValid = false;
-        try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_SUBSCRIBE_TERMINATED) != 0) {
-                mListener.onSubscribeTerminated(status);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onSubscribeTerminated: RemoteException (FYI): " + e);
-        }
-    }
-
-    public void onMessageSendSuccess(int messageId) {
-        try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_MESSAGE_SEND_SUCCESS) != 0) {
-                mListener.onMessageSendSuccess(messageId);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onMessageSendSuccess: RemoteException (FYI): " + e);
-        }
-    }
-
-    public void onMessageSendFail(int messageId, int status) {
-        try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_MESSAGE_SEND_FAIL) != 0) {
-                mListener.onMessageSendFail(messageId, status);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "onMessageSendFail: RemoteException (FYI): " + e);
-        }
-    }
-
+    /**
+     * Callback from HAL when a discovery occurs - i.e. when a match to an
+     * active subscription request or to a solicited publish request occurs.
+     * Propagates to client if registered.
+     *
+     * @param requestorInstanceId The ID used to identify the peer in this
+     *            matched session.
+     * @param peerMac The MAC address of the peer. Never propagated to client
+     *            due to privacy concerns.
+     * @param serviceSpecificInfo Information from the discovery advertisement
+     *            (usually not used in the match decisions).
+     * @param serviceSpecificInfoLength Length of the above information field.
+     * @param matchFilter The filter from the discovery advertisement (which was
+     *            used in the match decision).
+     * @param matchFilterLength Length of the above filter field.
+     */
     public void onMatch(int requestorInstanceId, byte[] peerMac, byte[] serviceSpecificInfo,
             int serviceSpecificInfoLength, byte[] matchFilter, int matchFilterLength) {
         String prevMac = mMacByRequestorInstanceId.get(requestorInstanceId);
@@ -216,15 +199,24 @@
         if (DBG) Log.d(TAG, "onMatch: previous peer MAC replaced - " + prevMac);
 
         try {
-            if (mListener != null && (mEvents & WifiNanSessionListener.LISTEN_MATCH) != 0) {
-                mListener.onMatch(requestorInstanceId, serviceSpecificInfo,
-                        serviceSpecificInfoLength, matchFilter, matchFilterLength);
-            }
+            mCallback.onMatch(requestorInstanceId, serviceSpecificInfo, serviceSpecificInfoLength,
+                    matchFilter, matchFilterLength);
         } catch (RemoteException e) {
             Log.w(TAG, "onMatch: RemoteException (FYI): " + e);
         }
     }
 
+    /**
+     * Callback from HAL when a message is received from a peer in a discovery
+     * session. Propagated to client if registered.
+     *
+     * @param requestorInstanceId An ID used to identify the peer.
+     * @param peerMac The MAC address of the peer sending the message. This
+     *            information is never propagated to the client due to privacy
+     *            concerns.
+     * @param message The received message.
+     * @param messageLength The length of the received message.
+     */
     public void onMessageReceived(int requestorInstanceId, byte[] peerMac, byte[] message,
             int messageLength) {
         String prevMac = mMacByRequestorInstanceId.get(requestorInstanceId);
@@ -235,21 +227,20 @@
         }
 
         try {
-            if (mListener != null
-                    && (mEvents & WifiNanSessionListener.LISTEN_MESSAGE_RECEIVED) != 0) {
-                mListener.onMessageReceived(requestorInstanceId, message, messageLength);
-            }
+            mCallback.onMessageReceived(requestorInstanceId, message, messageLength);
         } catch (RemoteException e) {
             Log.w(TAG, "onMessageReceived: RemoteException (FYI): " + e);
         }
     }
 
+    /**
+     * Dump the internal state of the class.
+     */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NanSessionState:");
         pw.println("  mSessionId: " + mSessionId);
-        pw.println("  mSessionType: " + mSessionType);
-        pw.println("  mEvents: " + mEvents);
-        pw.println("  mPubSubId: " + (mPubSubIdValid ? Integer.toString(mPubSubId) : "not valid"));
+        pw.println("  mIsPublishSession: " + mIsPublishSession);
+        pw.println("  mPubSubId: " + mPubSubId);
         pw.println("  mMacByRequestorInstanceId: [" + mMacByRequestorInstanceId + "]");
     }
 }
diff --git a/service/java/com/android/server/wifi/nan/WifiNanStateManager.java b/service/java/com/android/server/wifi/nan/WifiNanStateManager.java
index f7bfa55..7655225 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanStateManager.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanStateManager.java
@@ -16,71 +16,110 @@
 
 package com.android.server.wifi.nan;
 
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.RttManager;
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.IWifiNanEventListener;
-import android.net.wifi.nan.IWifiNanSessionListener;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
+import android.net.wifi.nan.IWifiNanEventCallback;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanManager;
+import android.net.wifi.nan.WifiNanSessionCallback;
 import android.os.Bundle;
-import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.WakeupMessage;
+
 import libcore.util.HexEncoding;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
+/**
+ * Manages the state of the Wi-Fi NAN system service.
+ */
 public class WifiNanStateManager {
     private static final String TAG = "WifiNanStateManager";
     private static final boolean DBG = false;
     private static final boolean VDBG = false; // STOPSHIP if true
 
+    @VisibleForTesting
+    public static final String HAL_COMMAND_TIMEOUT_TAG = TAG + " HAL Command Timeout";
+
+    @VisibleForTesting
+    public static final String HAL_SEND_MESSAGE_TIMEOUT_TAG = TAG + " HAL Send Message Timeout";
+
     private static WifiNanStateManager sNanStateManagerSingleton;
 
-    private static final int MESSAGE_CONNECT = 0;
-    private static final int MESSAGE_DISCONNECT = 1;
-    private static final int MESSAGE_REQUEST_CONFIG = 4;
-    private static final int MESSAGE_CREATE_SESSION = 5;
-    private static final int MESSAGE_DESTROY_SESSION = 6;
-    private static final int MESSAGE_PUBLISH = 7;
-    private static final int MESSAGE_SUBSCRIBE = 8;
-    private static final int MESSAGE_SEND_MESSAGE = 9;
-    private static final int MESSAGE_STOP_SESSION = 10;
-    private static final int MESSAGE_ON_CONFIG_COMPLETED = 11;
-    private static final int MESSAGE_ON_CONFIG_FAILED = 12;
-    private static final int MESSAGE_ON_NAN_DOWN = 13;
-    private static final int MESSAGE_ON_INTERFACE_CHANGE = 14;
-    private static final int MESSAGE_ON_CLUSTER_CHANGE = 15;
-    private static final int MESSAGE_ON_PUBLISH_SUCCESS = 16;
-    private static final int MESSAGE_ON_PUBLISH_FAIL = 17;
-    private static final int MESSAGE_ON_PUBLISH_TERMINATED = 18;
-    private static final int MESSAGE_ON_SUBSCRIBE_SUCCESS = 19;
-    private static final int MESSAGE_ON_SUBSCRIBE_FAIL = 20;
-    private static final int MESSAGE_ON_SUBSCRIBE_TERMINATED = 21;
-    private static final int MESSAGE_ON_MESSAGE_SEND_SUCCESS = 22;
-    private static final int MESSAGE_ON_MESSAGE_SEND_FAIL = 23;
-    private static final int MESSAGE_ON_UNKNOWN_TRANSACTION = 24;
-    private static final int MESSAGE_ON_MATCH = 25;
-    private static final int MESSAGE_ON_MESSAGE_RECEIVED = 26;
-    private static final int MESSAGE_ON_CAPABILITIES_UPDATED = 27;
+    /*
+     * State machine message types. There are sub-types for the messages (except for TIMEOUT).
+     * Format: - Message.arg1: contains message sub-type - Message.arg2: contains transaction ID for
+     * RESPONSE & RESPONSE_TIMEOUT
+     */
+    private static final int MESSAGE_TYPE_NOTIFICATION = 1;
+    private static final int MESSAGE_TYPE_COMMAND = 2;
+    private static final int MESSAGE_TYPE_RESPONSE = 3;
+    private static final int MESSAGE_TYPE_RESPONSE_TIMEOUT = 4;
+    private static final int MESSAGE_TYPE_SEND_MESSAGE_TIMEOUT = 5;
 
+    /*
+     * Message sub-types:
+     */
+    private static final int COMMAND_TYPE_CONNECT = 0;
+    private static final int COMMAND_TYPE_DISCONNECT = 1;
+    private static final int COMMAND_TYPE_TERMINATE_SESSION = 2;
+    private static final int COMMAND_TYPE_PUBLISH = 3;
+    private static final int COMMAND_TYPE_UPDATE_PUBLISH = 4;
+    private static final int COMMAND_TYPE_SUBSCRIBE = 5;
+    private static final int COMMAND_TYPE_UPDATE_SUBSCRIBE = 6;
+    private static final int COMMAND_TYPE_SEND_MESSAGE = 7;
+    private static final int COMMAND_TYPE_ENABLE_USAGE = 8;
+    private static final int COMMAND_TYPE_DISABLE_USAGE = 9;
+    private static final int COMMAND_TYPE_START_RANGING = 10;
+
+    private static final int RESPONSE_TYPE_ON_CONFIG_SUCCESS = 0;
+    private static final int RESPONSE_TYPE_ON_CONFIG_FAIL = 1;
+    private static final int RESPONSE_TYPE_ON_SESSION_CONFIG_SUCCESS = 2;
+    private static final int RESPONSE_TYPE_ON_SESSION_CONFIG_FAIL = 3;
+    private static final int RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_SUCCESS = 4;
+    private static final int RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_FAIL = 5;
+
+    private static final int NOTIFICATION_TYPE_CAPABILITIES_UPDATED = 0;
+    private static final int NOTIFICATION_TYPE_INTERFACE_CHANGE = 1;
+    private static final int NOTIFICATION_TYPE_CLUSTER_CHANGE = 2;
+    private static final int NOTIFICATION_TYPE_MATCH = 3;
+    private static final int NOTIFICATION_TYPE_SESSION_TERMINATED = 4;
+    private static final int NOTIFICATION_TYPE_MESSAGE_RECEIVED = 5;
+    private static final int NOTIFICATION_TYPE_NAN_DOWN = 6;
+    private static final int NOTIFICATION_TYPE_ON_MESSAGE_SEND_SUCCESS = 7;
+    private static final int NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL = 8;
+
+    /*
+     * Keys used when passing (some) arguments to the Handler thread (too many
+     * arguments to pass in the short-cut Message members).
+     */
+    private static final String MESSAGE_BUNDLE_KEY_SESSION_TYPE = "session_type";
     private static final String MESSAGE_BUNDLE_KEY_SESSION_ID = "session_id";
-    private static final String MESSAGE_BUNDLE_KEY_EVENTS = "events";
-    private static final String MESSAGE_BUNDLE_KEY_PUBLISH_DATA = "publish_data";
-    private static final String MESSAGE_BUNDLE_KEY_PUBLISH_SETTINGS = "publish_settings";
-    private static final String MESSAGE_BUNDLE_KEY_SUBSCRIBE_DATA = "subscribe_data";
-    private static final String MESSAGE_BUNDLE_KEY_SUBSCRIBE_SETTINGS = "subscribe_settings";
+    private static final String MESSAGE_BUNDLE_KEY_CONFIG = "config";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE_PEER_ID = "message_peer_id";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE_ID = "message_id";
-    private static final String MESSAGE_BUNDLE_KEY_RESPONSE_TYPE = "response_type";
     private static final String MESSAGE_BUNDLE_KEY_SSI_LENGTH = "ssi_length";
     private static final String MESSAGE_BUNDLE_KEY_SSI_DATA = "ssi_data";
     private static final String MESSAGE_BUNDLE_KEY_FILTER_LENGTH = "filter_length";
@@ -88,20 +127,38 @@
     private static final String MESSAGE_BUNDLE_KEY_MAC_ADDRESS = "mac_address";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE_DATA = "message_data";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH = "message_length";
+    private static final String MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID = "req_instance_id";
+    private static final String MESSAGE_BUNDLE_KEY_RANGING_ID = "ranging_id";
+    private static final String MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME = "message_queue_time";
+    private static final String MESSAGE_BUNDLE_KEY_RETRY_COUNT = "retry_count";
 
+    /*
+     * Asynchronous access with no lock
+     */
+    private volatile boolean mUsageEnabled = false;
+
+    /*
+     * Synchronous access: state is only accessed through the state machine
+     * handler thread: no need to use a lock.
+     */
+    private Context mContext;
     private WifiNanNative.Capabilities mCapabilities;
+    private WifiNanStateMachine mSm;
+    private WifiNanRttStateManager mRtt;
 
-    private WifiNanStateHandler mHandler;
-
-    // no synchronization necessary: only access through Handler
     private final SparseArray<WifiNanClientState> mClients = new SparseArray<>();
-    private final SparseArray<TransactionInfoBase> mPendingResponses = new SparseArray<>();
-    private short mNextTransactionId = 1;
+    private ConfigRequest mCurrentNanConfiguration = null;
 
     private WifiNanStateManager() {
         // EMPTY: singleton pattern
     }
 
+    /**
+     * Access the singleton NAN state manager. Use a singleton since need to be
+     * accessed (for now) from several other child classes.
+     *
+     * @return The state manager singleton.
+     */
     public static WifiNanStateManager getInstance() {
         if (sNanStateManagerSingleton == null) {
             sNanStateManagerSingleton = new WifiNanStateManager();
@@ -110,384 +167,554 @@
         return sNanStateManagerSingleton;
     }
 
-    public void start(Looper looper) {
+    /**
+     * Initialize the handler of the state manager with the specified thread
+     * looper.
+     *
+     * @param looper Thread looper on which to run the handler.
+     */
+    public void start(Context context, Looper looper) {
         Log.i(TAG, "start()");
 
-        mHandler = new WifiNanStateHandler(looper);
+        mContext = context;
+        mSm = new WifiNanStateMachine(TAG, looper);
+        mSm.setDbg(DBG);
+        mSm.start();
     }
 
-    public void connect(int uid, IWifiNanEventListener listener, int events) {
-        Message msg = mHandler.obtainMessage(MESSAGE_CONNECT);
-        msg.arg1 = uid;
-        msg.arg2 = events;
-        msg.obj = listener;
-        mHandler.sendMessage(msg);
+    /**
+     * Initialize the RTT service (requires late initialization).
+     */
+    public void startLate() {
+        mRtt = new WifiNanRttStateManager();
+        mRtt.start(mContext, mSm.getHandler().getLooper());
     }
 
-    public void disconnect(int uid) {
-        Message msg = mHandler.obtainMessage(MESSAGE_DISCONNECT);
-        msg.arg1 = uid;
-        mHandler.sendMessage(msg);
+    /*
+     * COMMANDS
+     */
+
+    /**
+     * Place a request for a new client connection on the state machine queue.
+     */
+    public void connect(int clientId, IWifiNanEventCallback callback, ConfigRequest configRequest) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_CONNECT;
+        msg.arg2 = clientId;
+        msg.obj = callback;
+        msg.getData().putParcelable(MESSAGE_BUNDLE_KEY_CONFIG, configRequest);
+        mSm.sendMessage(msg);
     }
 
-    public void requestConfig(int uid, ConfigRequest configRequest) {
-        Message msg = mHandler.obtainMessage(MESSAGE_REQUEST_CONFIG);
-        msg.arg1 = uid;
-        msg.obj = configRequest;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to disconnect (destroy) an existing client on the state
+     * machine queue.
+     */
+    public void disconnect(int clientId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_DISCONNECT;
+        msg.arg2 = clientId;
+        mSm.sendMessage(msg);
     }
 
-    public void stopSession(int uid, int sessionId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_STOP_SESSION);
-        msg.arg1 = uid;
-        msg.arg2 = sessionId;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to stop a discovery session on the state machine queue.
+     */
+    public void terminateSession(int clientId, int sessionId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_TERMINATE_SESSION;
+        msg.arg2 = clientId;
+        msg.obj = Integer.valueOf(sessionId);
+        mSm.sendMessage(msg);
     }
 
-    public void destroySession(int uid, int sessionId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_DESTROY_SESSION);
-        msg.arg1 = uid;
-        msg.arg2 = sessionId;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to start a new publish discovery session on the state
+     * machine queue.
+     */
+    public void publish(int clientId, PublishConfig publishConfig,
+            IWifiNanSessionCallback callback) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_PUBLISH;
+        msg.arg2 = clientId;
+        msg.obj = callback;
+        msg.getData().putParcelable(MESSAGE_BUNDLE_KEY_CONFIG, publishConfig);
+        mSm.sendMessage(msg);
     }
 
-    public void createSession(int uid, int sessionId, IWifiNanSessionListener listener,
-            int events) {
-        Bundle data = new Bundle();
-        data.putInt(MESSAGE_BUNDLE_KEY_EVENTS, events);
-
-        Message msg = mHandler.obtainMessage(MESSAGE_CREATE_SESSION);
-        msg.setData(data);
-        msg.arg1 = uid;
-        msg.arg2 = sessionId;
-        msg.obj = listener;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to modify an existing publish discovery session on the
+     * state machine queue.
+     */
+    public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_UPDATE_PUBLISH;
+        msg.arg2 = clientId;
+        msg.obj = publishConfig;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
+        mSm.sendMessage(msg);
     }
 
-    public void publish(int uid, int sessionId, PublishData publishData,
-            PublishSettings publishSettings) {
-        Bundle data = new Bundle();
-        data.putParcelable(MESSAGE_BUNDLE_KEY_PUBLISH_DATA, publishData);
-        data.putParcelable(MESSAGE_BUNDLE_KEY_PUBLISH_SETTINGS, publishSettings);
-
-        Message msg = mHandler.obtainMessage(MESSAGE_PUBLISH);
-        msg.setData(data);
-        msg.arg1 = uid;
-        msg.arg2 = sessionId;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to start a new subscribe discovery session on the state
+     * machine queue.
+     */
+    public void subscribe(int clientId, SubscribeConfig subscribeConfig,
+            IWifiNanSessionCallback callback) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_SUBSCRIBE;
+        msg.arg2 = clientId;
+        msg.obj = callback;
+        msg.getData().putParcelable(MESSAGE_BUNDLE_KEY_CONFIG, subscribeConfig);
+        mSm.sendMessage(msg);
     }
 
-    public void subscribe(int uid, int sessionId, SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings) {
-        Bundle data = new Bundle();
-        data.putParcelable(MESSAGE_BUNDLE_KEY_SUBSCRIBE_DATA, subscribeData);
-        data.putParcelable(MESSAGE_BUNDLE_KEY_SUBSCRIBE_SETTINGS, subscribeSettings);
-
-        Message msg = mHandler.obtainMessage(MESSAGE_SUBSCRIBE);
-        msg.setData(data);
-        msg.arg1 = uid;
-        msg.arg2 = sessionId;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to modify an existing subscribe discovery session on the
+     * state machine queue.
+     */
+    public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_UPDATE_SUBSCRIBE;
+        msg.arg2 = clientId;
+        msg.obj = subscribeConfig;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
+        mSm.sendMessage(msg);
     }
 
-    public void sendMessage(int uid, int sessionId, int peerId, byte[] message, int messageLength,
-            int messageId) {
-        Bundle data = new Bundle();
-        data.putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
-        data.putInt(MESSAGE_BUNDLE_KEY_MESSAGE_PEER_ID, peerId);
-        data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, message);
-        data.putInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID, messageId);
-
-        Message msg = mHandler.obtainMessage(MESSAGE_SEND_MESSAGE);
-        msg.arg1 = uid;
-        msg.arg2 = messageLength;
-        msg.setData(data);
-        mHandler.sendMessage(msg);
+    /**
+     * Place a request to send a message on a discovery session on the state
+     * machine queue.
+     */
+    public void sendMessage(int clientId, int sessionId, int peerId, byte[] message,
+            int messageLength, int messageId, int retryCount) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_SEND_MESSAGE;
+        msg.arg2 = clientId;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_MESSAGE_PEER_ID, peerId);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, message);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID, messageId);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH, messageLength);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_RETRY_COUNT, retryCount);
+        mSm.sendMessage(msg);
     }
 
-    public void onCapabilitiesUpdate(short transactionId, WifiNanNative.Capabilities capabilities) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_CAPABILITIES_UPDATED);
-        msg.arg1 = transactionId;
+    /**
+     * Place a request to range a peer on the discovery session on the state machine queue.
+     */
+    public void startRanging(int clientId, int sessionId, RttManager.RttParams[] params,
+                             int rangingId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_START_RANGING;
+        msg.arg2 = clientId;
+        msg.obj = params;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_RANGING_ID, rangingId);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Enable usage of NAN. Doesn't actually turn on NAN (form clusters) - that
+     * only happens when a connection is created.
+     */
+    public void enableUsage() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_ENABLE_USAGE;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Disable usage of NAN. Terminates all existing clients with onNanDown().
+     */
+    public void disableUsage() {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
+        msg.arg1 = COMMAND_TYPE_DISABLE_USAGE;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Checks whether NAN usage is enabled (not necessarily that NAN is up right
+     * now) or disabled.
+     *
+     * @return A boolean indicating whether NAN usage is enabled (true) or
+     *         disabled (false).
+     */
+    public boolean isUsageEnabled() {
+        return mUsageEnabled;
+    }
+
+    /*
+     * RESPONSES
+     */
+
+    /**
+     * Place a callback request on the state machine queue: configuration
+     * request completed (successfully).
+     */
+    public void onConfigSuccessResponse(short transactionId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_CONFIG_SUCCESS;
+        msg.arg2 = transactionId;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: configuration
+     * request failed.
+     */
+    public void onConfigFailedResponse(short transactionId, int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_CONFIG_FAIL;
+        msg.arg2 = transactionId;
+        msg.obj = Integer.valueOf(reason);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: session
+     * configuration (new or update) request succeeded.
+     */
+    public void onSessionConfigSuccessResponse(short transactionId, boolean isPublish,
+            int pubSubId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_SESSION_CONFIG_SUCCESS;
+        msg.arg2 = transactionId;
+        msg.obj = Integer.valueOf(pubSubId);
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE, isPublish);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: session
+     * configuration (new or update) request failed.
+     */
+    public void onSessionConfigFailResponse(short transactionId, boolean isPublish, int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_SESSION_CONFIG_FAIL;
+        msg.arg2 = transactionId;
+        msg.obj = Integer.valueOf(reason);
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE, isPublish);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: message has been queued successfully.
+     */
+    public void onMessageSendQueuedSuccessResponse(short transactionId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_SUCCESS;
+        msg.arg2 = transactionId;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Place a callback request on the state machine queue: attempt to queue the message failed.
+     */
+    public void onMessageSendQueuedFailResponse(short transactionId, int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_RESPONSE);
+        msg.arg1 = RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_FAIL;
+        msg.arg2 = transactionId;
+        msg.obj = Integer.valueOf(reason);
+        mSm.sendMessage(msg);
+    }
+
+    /*
+     * NOTIFICATIONS
+     */
+
+    /**
+     * Place a callback request on the state machine queue: update vendor
+     * capabilities of the NAN stack. This is actually a RESPONSE from the HAL -
+     * but treated as a NOTIFICATION.
+     */
+    public void onCapabilitiesUpdateNotification(WifiNanNative.Capabilities capabilities) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_CAPABILITIES_UPDATED;
         msg.obj = capabilities;
-        mHandler.sendMessage(msg);
+        mSm.sendMessage(msg);
     }
 
-    public void onConfigCompleted(short transactionId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_CONFIG_COMPLETED);
-        msg.arg1 = transactionId;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onConfigFailed(short transactionId, int reason) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_CONFIG_FAILED);
-        msg.arg1 = transactionId;
-        msg.arg2 = reason;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onPublishSuccess(short transactionId, int publishId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_PUBLISH_SUCCESS);
-        msg.arg1 = transactionId;
-        msg.arg2 = publishId;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onPublishFail(short transactionId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_PUBLISH_FAIL);
-        msg.arg1 = transactionId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onMessageSendSuccess(short transactionId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_MESSAGE_SEND_SUCCESS);
-        msg.arg1 = transactionId;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onMessageSendFail(short transactionId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_MESSAGE_SEND_FAIL);
-        msg.arg1 = transactionId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onSubscribeSuccess(short transactionId, int subscribeId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_SUBSCRIBE_SUCCESS);
-        msg.arg1 = transactionId;
-        msg.arg2 = subscribeId;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onSubscribeFail(short transactionId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_SUBSCRIBE_FAIL);
-        msg.arg1 = transactionId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onUnknownTransaction(int responseType, short transactionId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_UNKNOWN_TRANSACTION);
-        Bundle data = new Bundle();
-        data.putInt(MESSAGE_BUNDLE_KEY_RESPONSE_TYPE, responseType);
-        msg.setData(data);
-        msg.arg1 = transactionId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onInterfaceAddressChange(byte[] mac) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_INTERFACE_CHANGE);
+    /**
+     * Place a callback request on the state machine queue: the discovery
+     * interface has changed.
+     */
+    public void onInterfaceAddressChangeNotification(byte[] mac) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_INTERFACE_CHANGE;
         msg.obj = mac;
-        mHandler.sendMessage(msg);
+        mSm.sendMessage(msg);
     }
 
-    public void onClusterChange(int flag, byte[] clusterId) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_CLUSTER_CHANGE);
-        msg.arg1 = flag;
+    /**
+     * Place a callback request on the state machine queue: the cluster
+     * membership has changed (e.g. due to starting a new cluster or joining
+     * another cluster).
+     */
+    public void onClusterChangeNotification(int flag, byte[] clusterId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_CLUSTER_CHANGE;
+        msg.arg2 = flag;
         msg.obj = clusterId;
-        mHandler.sendMessage(msg);
+        mSm.sendMessage(msg);
     }
 
-    public void onMatch(int pubSubId, int requestorInstanceId, byte[] peerMac,
+    /**
+     * Place a callback request on the state machine queue: a discovery match
+     * has occurred - e.g. our subscription discovered someone else publishing a
+     * matching service (to the one we were looking for).
+     */
+    public void onMatchNotification(int pubSubId, int requestorInstanceId, byte[] peerMac,
             byte[] serviceSpecificInfo, int serviceSpecificInfoLength, byte[] matchFilter,
             int matchFilterLength) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_MATCH);
-        msg.arg1 = pubSubId;
-        msg.arg2 = requestorInstanceId;
-        Bundle data = new Bundle();
-        data.putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
-        data.putByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA, serviceSpecificInfo);
-        data.putInt(MESSAGE_BUNDLE_KEY_SSI_LENGTH, serviceSpecificInfoLength);
-        data.putByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA, matchFilter);
-        data.putInt(MESSAGE_BUNDLE_KEY_FILTER_LENGTH, matchFilterLength);
-        msg.setData(data);
-        mHandler.sendMessage(msg);
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_MATCH;
+        msg.arg2 = pubSubId;
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID, requestorInstanceId);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA, serviceSpecificInfo);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SSI_LENGTH, serviceSpecificInfoLength);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA, matchFilter);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_FILTER_LENGTH, matchFilterLength);
+        mSm.sendMessage(msg);
     }
 
-    public void onPublishTerminated(int publishId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_PUBLISH_TERMINATED);
-        msg.arg1 = publishId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a callback request on the state machine queue: a session (publish
+     * or subscribe) has terminated (per plan or due to an error).
+     */
+    public void onSessionTerminatedNotification(int pubSubId, int reason, boolean isPublish) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_SESSION_TERMINATED;
+        msg.arg2 = pubSubId;
+        msg.obj = Integer.valueOf(reason);
+        msg.getData().putBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE, isPublish);
+        mSm.sendMessage(msg);
     }
 
-    public void onSubscribeTerminated(int subscribeId, int status) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_SUBSCRIBE_TERMINATED);
-        msg.arg1 = subscribeId;
-        msg.arg2 = status;
-        mHandler.sendMessage(msg);
-    }
-
-    public void onMessageReceived(int pubSubId, int requestorInstanceId, byte[] peerMac,
+    /**
+     * Place a callback request on the state machine queue: a message has been
+     * received as part of a discovery session.
+     */
+    public void onMessageReceivedNotification(int pubSubId, int requestorInstanceId, byte[] peerMac,
             byte[] message, int messageLength) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_MESSAGE_RECEIVED);
-        msg.arg1 = pubSubId;
-        msg.arg2 = requestorInstanceId;
-        Bundle data = new Bundle();
-        data.putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
-        data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE_DATA, message);
-        data.putInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH, messageLength);
-        msg.setData(data);
-        mHandler.sendMessage(msg);
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_MESSAGE_RECEIVED;
+        msg.arg2 = pubSubId;
+        msg.obj = Integer.valueOf(requestorInstanceId);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
+        msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE_DATA, message);
+        msg.getData().putInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH, messageLength);
+        mSm.sendMessage(msg);
     }
 
-    public void onNanDown(int reason) {
-        Message msg = mHandler.obtainMessage(MESSAGE_ON_NAN_DOWN);
-        msg.arg1 = reason;
-        mHandler.sendMessage(msg);
+    /**
+     * Place a callback request on the state machine queue: NAN is going down.
+     */
+    public void onNanDownNotification(int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_NAN_DOWN;
+        msg.arg2 = reason;
+        mSm.sendMessage(msg);
     }
 
-    private class WifiNanStateHandler extends Handler {
-        WifiNanStateHandler(android.os.Looper looper) {
-            super(looper);
+    /**
+     * Notification that a message has been sent successfully (i.e. an ACK has been received).
+     */
+    public void onMessageSendSuccessNotification(short transactionId) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_ON_MESSAGE_SEND_SUCCESS;
+        msg.arg2 = transactionId;
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * Notification that a message transmission has failed due to the indicated reason - e.g. no ACK
+     * was received.
+     */
+    public void onMessageSendFailNotification(short transactionId, int reason) {
+        Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
+        msg.arg1 = NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL;
+        msg.arg2 = transactionId;
+        msg.obj = Integer.valueOf(reason);
+        mSm.sendMessage(msg);
+    }
+
+    /**
+     * State machine.
+     */
+    private class WifiNanStateMachine extends StateMachine {
+        private static final int TRANSACTION_ID_IGNORE = 0;
+
+        private DefaultState mDefaultState = new DefaultState();
+        private WaitState mWaitState = new WaitState();
+        private WaitForResponseState mWaitForResponseState = new WaitForResponseState();
+
+        private short mNextTransactionId = 1;
+        public int mNextSessionId = 1;
+
+        private Message mCurrentCommand;
+        private short mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+
+        private static final long NAN_SEND_MESSAGE_TIMEOUT = 5_000;
+        private final Map<Short, Message> mQueuedSendMessages = new LinkedHashMap<>();
+        private WakeupMessage mSendMessageTimeoutMessage = new WakeupMessage(mContext, getHandler(),
+                HAL_SEND_MESSAGE_TIMEOUT_TAG, MESSAGE_TYPE_SEND_MESSAGE_TIMEOUT);
+
+        WifiNanStateMachine(String name, Looper looper) {
+            super(name, looper);
+
+            addState(mDefaultState);
+            /* --> */ addState(mWaitState, mDefaultState);
+            /* --> */ addState(mWaitForResponseState, mDefaultState);
+
+            setInitialState(mWaitState);
         }
 
-        @Override
-        public void handleMessage(Message msg) {
-            if (DBG) {
-                Log.d(TAG, "Message: " + msg.what);
+        private class DefaultState extends State {
+            @Override
+            public boolean processMessage(Message msg) {
+                if (VDBG) {
+                    Log.v(TAG, getName() + msg.toString());
+                }
+
+                switch (msg.what) {
+                    case MESSAGE_TYPE_NOTIFICATION:
+                        processNotification(msg);
+                        return HANDLED;
+                    case MESSAGE_TYPE_SEND_MESSAGE_TIMEOUT:
+                        processSendMessageTimeout();
+                        return HANDLED;
+                    default:
+                        /* fall-through */
+                }
+
+                Log.wtf(TAG,
+                        "DefaultState: should not get non-NOTIFICATION in this state: msg=" + msg);
+                return NOT_HANDLED;
             }
-            switch (msg.what) {
-                case MESSAGE_CONNECT: {
-                    if (VDBG) {
-                        Log.d(TAG, "NAN connection request received");
-                    }
-                    connectLocal(msg.arg1, (IWifiNanEventListener) msg.obj, msg.arg2);
-                    break;
-                }
-                case MESSAGE_DISCONNECT: {
-                    if (VDBG) {
-                        Log.d(TAG, "NAN disconnection request received");
-                    }
-                    disconnectLocal(msg.arg1);
-                    break;
-                }
-                case MESSAGE_REQUEST_CONFIG: {
-                    if (VDBG) {
-                        Log.d(TAG, "NAN configuration request received");
-                    }
-                    requestConfigLocal(msg.arg1, (ConfigRequest) msg.obj);
-                    break;
-                }
-                case MESSAGE_CREATE_SESSION: {
-                    if (VDBG) {
-                        Log.d(TAG, "Create session");
-                    }
-                    int events = msg.getData().getInt(MESSAGE_BUNDLE_KEY_EVENTS);
-                    createSessionLocal(msg.arg1, msg.arg2, (IWifiNanSessionListener) msg.obj,
-                            events);
-                    break;
-                }
-                case MESSAGE_DESTROY_SESSION: {
-                    if (VDBG) {
-                        Log.d(TAG, "Destroy session");
-                    }
-                    destroySessionLocal(msg.arg1, msg.arg2);
-                    break;
-                }
-                case MESSAGE_PUBLISH: {
-                    Bundle data = msg.getData();
-                    PublishData publishData = (PublishData) data
-                            .getParcelable(MESSAGE_BUNDLE_KEY_PUBLISH_DATA);
-                    PublishSettings publishSettings = (PublishSettings) data
-                            .getParcelable(MESSAGE_BUNDLE_KEY_PUBLISH_SETTINGS);
-                    if (VDBG) {
-                        Log.d(TAG,
-                                "Publish: data='" + publishData + "', settings=" + publishSettings);
-                    }
+        }
 
-                    publishLocal(msg.arg1, msg.arg2, publishData, publishSettings);
-                    break;
+        private class WaitState extends State {
+            @Override
+            public boolean processMessage(Message msg) {
+                if (VDBG) {
+                    Log.v(TAG, getName() + msg.toString());
                 }
-                case MESSAGE_SUBSCRIBE: {
-                    Bundle data = msg.getData();
-                    SubscribeData subscribeData = (SubscribeData) data
-                            .getParcelable(MESSAGE_BUNDLE_KEY_SUBSCRIBE_DATA);
-                    SubscribeSettings subscribeSettings = (SubscribeSettings) data
-                            .getParcelable(MESSAGE_BUNDLE_KEY_SUBSCRIBE_SETTINGS);
-                    if (VDBG) {
-                        Log.d(TAG, "Subscribe: data='" + subscribeData + "', settings="
-                                + subscribeSettings);
-                    }
 
-                    subscribeLocal(msg.arg1, msg.arg2, subscribeData, subscribeSettings);
-                    break;
+                switch (msg.what) {
+                    case MESSAGE_TYPE_COMMAND:
+                        if (processCommand(msg)) {
+                            transitionTo(mWaitForResponseState);
+                        }
+                        return HANDLED;
+                    case MESSAGE_TYPE_RESPONSE:
+                        /* fall-through */
+                    case MESSAGE_TYPE_RESPONSE_TIMEOUT:
+                        /*
+                         * remnants/delayed/out-of-sync messages - but let
+                         * WaitForResponseState deal with them (identified as
+                         * out-of-date by transaction ID).
+                         */
+                        deferMessage(msg);
+                        return HANDLED;
+                    default:
+                        /* fall-through */
                 }
-                case MESSAGE_SEND_MESSAGE: {
-                    Bundle data = msg.getData();
-                    int sessionId = msg.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
-                    int peerId = data.getInt(MESSAGE_BUNDLE_KEY_MESSAGE_PEER_ID);
-                    byte[] message = data.getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE);
-                    int messageId = data.getInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID);
 
-                    if (VDBG) {
-                        Log.d(TAG, "Send Message: message='" + message + "' (ID=" + messageId
-                                + ") to peerId=" + peerId);
-                    }
+                return NOT_HANDLED;
+            }
+        }
 
-                    sendFollowonMessageLocal(msg.arg1, sessionId, peerId, message, msg.arg2,
-                            messageId);
+        private class WaitForResponseState extends State {
+            private static final long NAN_COMMAND_TIMEOUT = 5_000;
+            private WakeupMessage mTimeoutMessage;
+
+            @Override
+            public void enter() {
+                mTimeoutMessage = new WakeupMessage(mContext, getHandler(), HAL_COMMAND_TIMEOUT_TAG,
+                        MESSAGE_TYPE_RESPONSE_TIMEOUT, mCurrentCommand.arg1, mCurrentTransactionId);
+                mTimeoutMessage.schedule(SystemClock.elapsedRealtime() + NAN_COMMAND_TIMEOUT);
+            }
+
+            @Override
+            public void exit() {
+                mTimeoutMessage.cancel();
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+                if (VDBG) {
+                    Log.v(TAG, getName() + msg.toString());
+                }
+
+                switch (msg.what) {
+                    case MESSAGE_TYPE_COMMAND:
+                        /*
+                         * don't want COMMANDs in this state - defer until back
+                         * in WaitState
+                         */
+                        deferMessage(msg);
+                        return HANDLED;
+                    case MESSAGE_TYPE_RESPONSE:
+                        if (msg.arg2 == mCurrentTransactionId) {
+                            processResponse(msg);
+                            transitionTo(mWaitState);
+                        } else {
+                            Log.w(TAG,
+                                    "WaitForResponseState: processMessage: non-matching "
+                                            + "transaction ID on RESPONSE (a very late "
+                                            + "response) -- msg=" + msg);
+                            /* no transition */
+                        }
+                        return HANDLED;
+                    case MESSAGE_TYPE_RESPONSE_TIMEOUT:
+                        if (msg.arg2 == mCurrentTransactionId) {
+                            processTimeout(msg);
+                            transitionTo(mWaitState);
+                        } else {
+                            Log.w(TAG, "WaitForResponseState: processMessage: non-matching "
+                                    + "transaction ID on RESPONSE_TIMEOUT (either a non-cancelled "
+                                    + "timeout or a race condition with cancel) -- msg=" + msg);
+                            /* no transition */
+                        }
+                        return HANDLED;
+                    default:
+                        /* fall-through */
+                }
+
+                return NOT_HANDLED;
+            }
+        }
+
+        private void processNotification(Message msg) {
+            if (VDBG) {
+                Log.v(TAG, "processNotification: msg=" + msg);
+            }
+
+            switch (msg.arg1) {
+                case NOTIFICATION_TYPE_CAPABILITIES_UPDATED: {
+                    WifiNanNative.Capabilities capabilities = (WifiNanNative.Capabilities) msg.obj;
+                    onCapabilitiesUpdatedLocal(capabilities);
                     break;
                 }
-                case MESSAGE_STOP_SESSION: {
-                    if (VDBG) {
-                        Log.d(TAG, "Stop session");
-                    }
-                    stopSessionLocal(msg.arg1, msg.arg2);
+                case NOTIFICATION_TYPE_INTERFACE_CHANGE: {
+                    byte[] mac = (byte[]) msg.obj;
+
+                    onInterfaceAddressChangeLocal(mac);
                     break;
                 }
-                case MESSAGE_ON_CAPABILITIES_UPDATED:
-                    onCapabilitiesUpdatedLocal((short) msg.arg1,
-                            (WifiNanNative.Capabilities) msg.obj);
+                case NOTIFICATION_TYPE_CLUSTER_CHANGE: {
+                    int flag = msg.arg2;
+                    byte[] clusterId = (byte[]) msg.obj;
+
+                    onClusterChangeLocal(flag, clusterId);
                     break;
-                case MESSAGE_ON_CONFIG_COMPLETED:
-                    onConfigCompletedLocal((short) msg.arg1);
-                    break;
-                case MESSAGE_ON_CONFIG_FAILED:
-                    onConfigFailedLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_NAN_DOWN:
-                    onNanDownLocal(msg.arg1);
-                    break;
-                case MESSAGE_ON_INTERFACE_CHANGE:
-                    onInterfaceAddressChangeLocal((byte[]) msg.obj);
-                    break;
-                case MESSAGE_ON_CLUSTER_CHANGE:
-                    onClusterChangeLocal(msg.arg1, (byte[]) msg.obj);
-                    break;
-                case MESSAGE_ON_PUBLISH_SUCCESS:
-                    onPublishSuccessLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_PUBLISH_FAIL:
-                    onPublishFailLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_PUBLISH_TERMINATED:
-                    onPublishTerminatedLocal(msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_SUBSCRIBE_SUCCESS:
-                    onSubscribeSuccessLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_SUBSCRIBE_FAIL:
-                    onSubscribeFailLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_SUBSCRIBE_TERMINATED:
-                    onSubscribeTerminatedLocal(msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_MESSAGE_SEND_SUCCESS:
-                    onMessageSendSuccessLocal((short) msg.arg1);
-                    break;
-                case MESSAGE_ON_MESSAGE_SEND_FAIL:
-                    onMessageSendFailLocal((short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_UNKNOWN_TRANSACTION:
-                    onUnknownTransactionLocal(
-                            msg.getData().getInt(MESSAGE_BUNDLE_KEY_RESPONSE_TYPE),
-                            (short) msg.arg1, msg.arg2);
-                    break;
-                case MESSAGE_ON_MATCH: {
-                    int pubSubId = msg.arg1;
-                    int requestorInstanceId = msg.arg2;
+                }
+                case NOTIFICATION_TYPE_MATCH: {
+                    int pubSubId = msg.arg2;
+                    int requestorInstanceId = msg.getData()
+                            .getInt(MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID);
                     byte[] peerMac = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS);
                     byte[] serviceSpecificInfo = msg.getData()
                             .getByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA);
@@ -495,380 +722,941 @@
                             .getInt(MESSAGE_BUNDLE_KEY_SSI_LENGTH);
                     byte[] matchFilter = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA);
                     int matchFilterLength = msg.getData().getInt(MESSAGE_BUNDLE_KEY_FILTER_LENGTH);
+
                     onMatchLocal(pubSubId, requestorInstanceId, peerMac, serviceSpecificInfo,
                             serviceSpecificInfoLength, matchFilter, matchFilterLength);
                     break;
                 }
-                case MESSAGE_ON_MESSAGE_RECEIVED: {
-                    int pubSubId = msg.arg1;
-                    int requestorInstanceId = msg.arg2;
+                case NOTIFICATION_TYPE_SESSION_TERMINATED: {
+                    int pubSubId = msg.arg2;
+                    int reason = (Integer) msg.obj;
+                    boolean isPublish = msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE);
+
+                    onSessionTerminatedLocal(pubSubId, isPublish, reason);
+                    break;
+                }
+                case NOTIFICATION_TYPE_MESSAGE_RECEIVED: {
+                    int pubSubId = msg.arg2;
+                    int requestorInstanceId = (Integer) msg.obj;
                     byte[] peerMac = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS);
                     byte[] message = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE_DATA);
                     int messageLength = msg.getData().getInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH);
+
                     onMessageReceivedLocal(pubSubId, requestorInstanceId, peerMac, message,
                             messageLength);
                     break;
                 }
+                case NOTIFICATION_TYPE_NAN_DOWN: {
+                    int reason = msg.arg2;
+
+                    /*
+                     * TODO: b/28615938. Use reason code to determine whether or not need clean-up
+                     * local state (only needed if NAN_DOWN is due to internal firmware reason, e.g.
+                     * concurrency, rather than due to a requested shutdown).
+                     */
+
+                    onNanDownLocal();
+
+                    break;
+                }
+                case NOTIFICATION_TYPE_ON_MESSAGE_SEND_SUCCESS: {
+                    short transactionId = (short) msg.arg2;
+                    Message queuedSendCommand = mQueuedSendMessages.get(transactionId);
+                    if (queuedSendCommand == null) {
+                        Log.w(TAG,
+                                "processNotification: NOTIFICATION_TYPE_ON_MESSAGE_SEND_SUCCESS:"
+                                        + " transactionId=" + transactionId
+                                        + " - no such queued send command");
+                    } else {
+                        removeSendMessageFromQueue(transactionId);
+                        onMessageSendSuccessLocal(queuedSendCommand);
+                    }
+
+                    break;
+                }
+                case NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL: {
+                    short transactionId = (short) msg.arg2;
+                    int reason = (Integer) msg.obj;
+                    Message queuedSendCommand = mQueuedSendMessages.get(transactionId);
+                    if (queuedSendCommand == null) {
+                        Log.w(TAG,
+                                "processNotification: NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL:"
+                                        + " transactionId=" + transactionId
+                                        + " - no such queued send command");
+                    } else {
+                        removeSendMessageFromQueue(transactionId);
+
+                        int retryCount = queuedSendCommand.getData()
+                                .getInt(MESSAGE_BUNDLE_KEY_RETRY_COUNT);
+                        if (retryCount > 0 && reason == WifiNanSessionCallback.REASON_TX_FAIL) {
+                            if (DBG) {
+                                Log.d(TAG,
+                                        "NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL: transactionId="
+                                                + transactionId + ", reason=" + reason
+                                                + ": retransmitting - retryCount=" + retryCount);
+                            }
+                            queuedSendCommand.getData().putInt(MESSAGE_BUNDLE_KEY_RETRY_COUNT,
+                                    retryCount - 1);
+                            sendMessageAtFrontOfQueue(queuedSendCommand);
+                        } else {
+                            onMessageSendFailLocal(queuedSendCommand, reason);
+                        }
+                    }
+                    break;
+                }
                 default:
-                    Log.e(TAG, "Unknown message code: " + msg.what);
+                    Log.wtf(TAG, "processNotification: this isn't a NOTIFICATION -- msg=" + msg);
+                    return;
             }
         }
+
+        /**
+         * Execute the command specified by the input Message. Returns a true if
+         * need to wait for a RESPONSE, otherwise a false. We may not have to
+         * wait for a RESPONSE if there was an error in the state (so no command
+         * is sent to HAL) OR if we choose not to wait for response - e.g. for
+         * disconnected/terminate commands failure is not possible.
+         */
+        private boolean processCommand(Message msg) {
+            if (VDBG) {
+                Log.v(TAG, "processCommand: msg=" + msg);
+            }
+
+            if (mCurrentCommand != null) {
+                Log.wtf(TAG,
+                        "processCommand: receiving a command (msg=" + msg
+                                + ") but current (previous) command isn't null (prev_msg="
+                                + mCurrentCommand + ")");
+                mCurrentCommand = null;
+            }
+
+            mCurrentTransactionId = mNextTransactionId++;
+
+            boolean waitForResponse = true;
+
+            switch (msg.arg1) {
+                case COMMAND_TYPE_CONNECT: {
+                    int clientId = msg.arg2;
+                    IWifiNanEventCallback callback = (IWifiNanEventCallback) msg.obj;
+                    ConfigRequest configRequest = (ConfigRequest) msg.getData()
+                            .getParcelable(MESSAGE_BUNDLE_KEY_CONFIG);
+
+                    waitForResponse = connectLocal(mCurrentTransactionId, clientId, callback,
+                            configRequest);
+                    break;
+                }
+                case COMMAND_TYPE_DISCONNECT: {
+                    int clientId = msg.arg2;
+
+                    waitForResponse = disconnectLocal(mCurrentTransactionId, clientId);
+                    break;
+                }
+                case COMMAND_TYPE_TERMINATE_SESSION: {
+                    int clientId = msg.arg2;
+                    int sessionId = (Integer) msg.obj;
+
+                    terminateSessionLocal(clientId, sessionId);
+                    waitForResponse = false;
+                    break;
+                }
+                case COMMAND_TYPE_PUBLISH: {
+                    int clientId = msg.arg2;
+                    IWifiNanSessionCallback callback = (IWifiNanSessionCallback) msg.obj;
+                    PublishConfig publishConfig = (PublishConfig) msg.getData()
+                            .getParcelable(MESSAGE_BUNDLE_KEY_CONFIG);
+
+                    waitForResponse = publishLocal(mCurrentTransactionId, clientId, publishConfig,
+                            callback);
+                    break;
+                }
+                case COMMAND_TYPE_UPDATE_PUBLISH: {
+                    int clientId = msg.arg2;
+                    int sessionId = msg.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+                    PublishConfig publishConfig = (PublishConfig) msg.obj;
+
+                    waitForResponse = updatePublishLocal(mCurrentTransactionId, clientId, sessionId,
+                            publishConfig);
+                    break;
+                }
+                case COMMAND_TYPE_SUBSCRIBE: {
+                    int clientId = msg.arg2;
+                    IWifiNanSessionCallback callback = (IWifiNanSessionCallback) msg.obj;
+                    SubscribeConfig subscribeConfig = (SubscribeConfig) msg.getData()
+                            .getParcelable(MESSAGE_BUNDLE_KEY_CONFIG);
+
+                    waitForResponse = subscribeLocal(mCurrentTransactionId, clientId,
+                            subscribeConfig, callback);
+                    break;
+                }
+                case COMMAND_TYPE_UPDATE_SUBSCRIBE: {
+                    int clientId = msg.arg2;
+                    int sessionId = msg.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+                    SubscribeConfig subscribeConfig = (SubscribeConfig) msg.obj;
+
+                    waitForResponse = updateSubscribeLocal(mCurrentTransactionId, clientId,
+                            sessionId, subscribeConfig);
+                    break;
+                }
+                case COMMAND_TYPE_SEND_MESSAGE: {
+                    Bundle data = msg.getData();
+
+                    int clientId = msg.arg2;
+                    int sessionId = msg.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+                    int peerId = data.getInt(MESSAGE_BUNDLE_KEY_MESSAGE_PEER_ID);
+                    byte[] message = data.getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE);
+                    int messageId = data.getInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID);
+                    int messageLength = data.getInt(MESSAGE_BUNDLE_KEY_MESSAGE_LENGTH);
+
+                    waitForResponse = sendFollowonMessageLocal(mCurrentTransactionId, clientId,
+                            sessionId, peerId, message, messageLength, messageId);
+                    break;
+                }
+                case COMMAND_TYPE_ENABLE_USAGE:
+                    enableUsageLocal();
+                    waitForResponse = false;
+                    break;
+                case COMMAND_TYPE_DISABLE_USAGE:
+                    disableUsageLocal();
+                    waitForResponse = false;
+                    break;
+                case COMMAND_TYPE_START_RANGING: {
+                    Bundle data = msg.getData();
+
+                    int clientId = msg.arg2;
+                    RttManager.RttParams[] params = (RttManager.RttParams[]) msg.obj;
+                    int sessionId = data.getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+                    int rangingId = data.getInt(MESSAGE_BUNDLE_KEY_RANGING_ID);
+
+                    startRangingLocal(clientId, sessionId, params, rangingId);
+                    waitForResponse = false;
+                    break;
+                }
+                default:
+                    waitForResponse = false;
+                    Log.wtf(TAG, "processCommand: this isn't a COMMAND -- msg=" + msg);
+                    /* fall-through */
+            }
+
+            if (!waitForResponse) {
+                mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+            } else {
+                mCurrentCommand = obtainMessage(msg.what);
+                mCurrentCommand.copyFrom(msg);
+            }
+
+            return waitForResponse;
+        }
+
+        private void processResponse(Message msg) {
+            if (VDBG) {
+                Log.v(TAG, "processResponse: msg=" + msg);
+            }
+
+            if (mCurrentCommand == null) {
+                Log.wtf(TAG, "processResponse: no existing command stored!? msg=" + msg);
+                mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+                return;
+            }
+
+            switch (msg.arg1) {
+                case RESPONSE_TYPE_ON_CONFIG_SUCCESS:
+                    onConfigCompletedLocal(mCurrentCommand);
+                    break;
+                case RESPONSE_TYPE_ON_CONFIG_FAIL: {
+                    int reason = (Integer) msg.obj;
+
+                    onConfigFailedLocal(mCurrentCommand, reason);
+                    break;
+                }
+                case RESPONSE_TYPE_ON_SESSION_CONFIG_SUCCESS: {
+                    int pubSubId = (Integer) msg.obj;
+                    boolean isPublish = msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE);
+
+                    onSessionConfigSuccessLocal(mCurrentCommand, pubSubId, isPublish);
+                    break;
+                }
+                case RESPONSE_TYPE_ON_SESSION_CONFIG_FAIL: {
+                    int reason = (Integer) msg.obj;
+                    boolean isPublish = msg.getData().getBoolean(MESSAGE_BUNDLE_KEY_SESSION_TYPE);
+
+                    onSessionConfigFailLocal(mCurrentCommand, isPublish, reason);
+                    break;
+                }
+                case RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_SUCCESS:
+                    addSendMessageToQueue((short) msg.arg2, mCurrentCommand);
+                    break;
+                case RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_FAIL: {
+                    int reason = (Integer) msg.obj;
+
+                    onMessageSendFailLocal(mCurrentCommand, reason);
+                    break;
+                }
+                default:
+                    Log.wtf(TAG, "processResponse: this isn't a RESPONSE -- msg=" + msg);
+                    mCurrentCommand = null;
+                    mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+                    return;
+            }
+
+            mCurrentCommand = null;
+            mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+        }
+
+        private void processTimeout(Message msg) {
+            if (VDBG) {
+                Log.v(TAG, "processTimeout: msg=" + msg);
+            }
+
+            if (mCurrentCommand == null) {
+                Log.wtf(TAG, "processTimeout: no existing command stored!? msg=" + msg);
+                mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+                return;
+            }
+
+            /*
+             * Only have to handle those COMMANDs which wait for a response.
+             */
+            switch (msg.arg1) {
+                case COMMAND_TYPE_CONNECT: {
+                    onConfigFailedLocal(mCurrentCommand, WifiNanEventCallback.REASON_OTHER);
+                    break;
+                }
+                case COMMAND_TYPE_DISCONNECT: {
+                    /*
+                     * Will only get here on DISCONNECT if was downgrading. The
+                     * callback will do a NOP - but should still call it.
+                     */
+                    onConfigFailedLocal(mCurrentCommand, WifiNanEventCallback.REASON_OTHER);
+                    break;
+                }
+                case COMMAND_TYPE_TERMINATE_SESSION: {
+                    Log.wtf(TAG, "processTimeout: TERMINATE_SESSION - shouldn't be waiting!");
+                    break;
+                }
+                case COMMAND_TYPE_PUBLISH: {
+                    onSessionConfigFailLocal(mCurrentCommand, true,
+                            WifiNanSessionCallback.REASON_OTHER);
+                    break;
+                }
+                case COMMAND_TYPE_UPDATE_PUBLISH: {
+                    onSessionConfigFailLocal(mCurrentCommand, true,
+                            WifiNanSessionCallback.REASON_OTHER);
+                    break;
+                }
+                case COMMAND_TYPE_SUBSCRIBE: {
+                    onSessionConfigFailLocal(mCurrentCommand, false,
+                            WifiNanSessionCallback.REASON_OTHER);
+                    break;
+                }
+                case COMMAND_TYPE_UPDATE_SUBSCRIBE: {
+                    onSessionConfigFailLocal(mCurrentCommand, false,
+                            WifiNanSessionCallback.REASON_OTHER);
+                    break;
+                }
+                case COMMAND_TYPE_SEND_MESSAGE: {
+                    onMessageSendFailLocal(mCurrentCommand, WifiNanSessionCallback.REASON_TX_FAIL);
+                    break;
+                }
+                case COMMAND_TYPE_ENABLE_USAGE:
+                    Log.wtf(TAG, "processTimeout: ENABLE_USAGE - shouldn't be waiting!");
+                    break;
+                case COMMAND_TYPE_DISABLE_USAGE:
+                    Log.wtf(TAG, "processTimeout: DISABLE_USAGE - shouldn't be waiting!");
+                    break;
+                case COMMAND_TYPE_START_RANGING:
+                    Log.wtf(TAG, "processTimeout: START_RANGING - shouldn't be waiting!");
+                    break;
+                default:
+                    Log.wtf(TAG, "processTimeout: this isn't a COMMAND -- msg=" + msg);
+                    /* fall-through */
+            }
+
+            mCurrentCommand = null;
+            mCurrentTransactionId = TRANSACTION_ID_IGNORE;
+        }
+
+        private void updateSendMessageTimeout() {
+            Iterator<Message> it = mQueuedSendMessages.values().iterator();
+            if (it.hasNext()) {
+                /*
+                 * Schedule timeout based on the first message in the queue (which is the earliest
+                 * submitted message). Timeout = queuing time + timeout constant.
+                 */
+                Message msg = it.next();
+                mSendMessageTimeoutMessage.schedule(
+                        msg.getData().getLong(MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME)
+                        + NAN_SEND_MESSAGE_TIMEOUT);
+            } else {
+                mSendMessageTimeoutMessage.cancel();
+            }
+        }
+
+        private void processSendMessageTimeout() {
+            /*
+             * Note: using 'first' to always time-out (remove) at least 1 notification (partially)
+             * due to test code needs: there's no way to mock elapsedRealtime(). TODO: replace with
+             * injected getClock() once moved off of mmwd.
+             */
+            boolean first = true;
+            Iterator<Message> it = mQueuedSendMessages.values().iterator();
+            while (it.hasNext()) {
+                Message message = it.next();
+                if (first || message.getData().getLong(MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME)
+                        + NAN_SEND_MESSAGE_TIMEOUT >= SystemClock.elapsedRealtime()) {
+                    onMessageSendFailLocal(message, WifiNanSessionCallback.REASON_TX_FAIL);
+                    it.remove();
+                    first = false;
+                } else {
+                    break;
+                }
+            }
+            updateSendMessageTimeout();
+        }
+
+        private void addSendMessageToQueue(short transactionId, Message sendCommand) {
+            sendCommand.getData().putLong(MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME,
+                    SystemClock.elapsedRealtime());
+            mQueuedSendMessages.put(transactionId, sendCommand);
+            updateSendMessageTimeout();
+        }
+
+        private void removeSendMessageFromQueue(short transactionId) {
+            mQueuedSendMessages.remove(transactionId);
+            updateSendMessageTimeout();
+        }
+
+        @Override
+        protected String getLogRecString(Message msg) {
+            StringBuffer sb = new StringBuffer(WifiNanStateManager.messageToString(msg));
+
+            if (msg.what == MESSAGE_TYPE_COMMAND
+                    && mCurrentTransactionId != TRANSACTION_ID_IGNORE) {
+                sb.append(" (Transaction ID=" + mCurrentTransactionId + ")");
+            }
+
+            return sb.toString();
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("WifiNanStateMachine:");
+            pw.println("  mNextTransactionId: " + mNextTransactionId);
+            pw.println("  mNextSessionId: " + mNextSessionId);
+            pw.println("  mCurrentCommand: " + mCurrentCommand);
+            pw.println("  mCurrentTransaction: " + mCurrentTransactionId);
+            pw.println("  mQueuedSendMessages: [" + mQueuedSendMessages + "]");
+            super.dump(fd, pw, args);
+        }
+    }
+
+    private void sendNanStateChangedBroadcast(boolean enabled) {
+        if (VDBG) {
+            Log.v(TAG, "sendNanStateChangedBroadcast: enabled=" + enabled);
+        }
+        final Intent intent = new Intent(WifiNanManager.WIFI_NAN_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        if (enabled) {
+            intent.putExtra(WifiNanManager.EXTRA_WIFI_STATE, WifiNanManager.WIFI_NAN_STATE_ENABLED);
+        } else {
+            intent.putExtra(WifiNanManager.EXTRA_WIFI_STATE,
+                    WifiNanManager.WIFI_NAN_STATE_DISABLED);
+        }
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
     }
 
     /*
-     * Transaction management classes & operations
+     * COMMANDS
      */
 
-    // non-synchronized (should be ok as long as only used from NanStateManager,
-    // NanClientState, and NanSessionState)
-    /* package */ short createNextTransactionId() {
-        return mNextTransactionId++;
-    }
-
-    private static class TransactionInfoBase {
-        short mTransactionId;
-    }
-
-    private static class TransactionInfoSession extends TransactionInfoBase {
-        public WifiNanClientState mClient;
-        public WifiNanSessionState mSession;
-    }
-
-    private static class TransactionInfoMessage extends TransactionInfoSession {
-        public int mMessageId;
-    }
-
-    private static class TransactionInfoConfig extends TransactionInfoBase {
-        public ConfigRequest mConfig;
-    }
-
-    private void allocateAndRegisterTransactionId(TransactionInfoBase info) {
-        info.mTransactionId = createNextTransactionId();
-
-        mPendingResponses.put(info.mTransactionId, info);
-    }
-
-    private void fillInTransactionInfoSession(TransactionInfoSession info, int uid,
-            int sessionId) {
-        WifiNanClientState client = mClients.get(uid);
-        if (client == null) {
-            throw new IllegalStateException(
-                    "getAndRegisterTransactionId: no client exists for uid=" + uid);
-        }
-        info.mClient = client;
-
-        WifiNanSessionState session = info.mClient.getSession(sessionId);
-        if (session == null) {
-            throw new IllegalStateException(
-                    "getAndRegisterSessionTransactionId: no session exists for uid=" + uid
-                            + ", sessionId=" + sessionId);
-        }
-        info.mSession = session;
-    }
-
-    private TransactionInfoBase createTransactionInfo() {
-        TransactionInfoBase info = new TransactionInfoBase();
-        allocateAndRegisterTransactionId(info);
-        return info;
-    }
-
-    private TransactionInfoSession createTransactionInfoSession(int uid, int sessionId) {
-        TransactionInfoSession info = new TransactionInfoSession();
-        fillInTransactionInfoSession(info, uid, sessionId);
-        allocateAndRegisterTransactionId(info);
-        return info;
-    }
-
-    private TransactionInfoMessage createTransactionInfoMessage(int uid, int sessionId,
-            int messageId) {
-        TransactionInfoMessage info = new TransactionInfoMessage();
-        fillInTransactionInfoSession(info, uid, sessionId);
-        info.mMessageId = messageId;
-        allocateAndRegisterTransactionId(info);
-        return info;
-    }
-
-    private TransactionInfoConfig createTransactionInfoConfig(ConfigRequest configRequest) {
-        TransactionInfoConfig info = new TransactionInfoConfig();
-        info.mConfig = configRequest;
-        allocateAndRegisterTransactionId(info);
-        return info;
-    }
-
-    private TransactionInfoBase getAndRemovePendingResponseTransactionInfo(short transactionId) {
-        TransactionInfoBase transInfo = mPendingResponses.get(transactionId);
-        if (transInfo != null) {
-            mPendingResponses.remove(transactionId);
-        }
-
-        return transInfo;
-    }
-
-    private WifiNanSessionState getNanSessionStateForPubSubId(int pubSubId) {
-        for (int i = 0; i < mClients.size(); ++i) {
-            WifiNanSessionState session = mClients.valueAt(i)
-                    .getNanSessionStateForPubSubId(pubSubId);
-            if (session != null) {
-                return session;
-            }
-        }
-
-        return null;
-    }
-
-    /*
-     * Actions (calls from API to service)
-     */
-    private void connectLocal(int uid, IWifiNanEventListener listener, int events) {
+    private boolean connectLocal(short transactionId, int clientId, IWifiNanEventCallback callback,
+            ConfigRequest configRequest) {
         if (VDBG) {
-            Log.v(TAG, "connect(): uid=" + uid + ", listener=" + listener + ", events=" + events);
+            Log.v(TAG, "connectLocal(): transactionId=" + transactionId + ", clientId=" + clientId
+                    + ", callback=" + callback + ", configRequest=" + configRequest);
         }
 
-        if (mClients.get(uid) != null) {
-            Log.e(TAG, "connect: entry already exists for uid=" + uid);
-            return;
+        if (!mUsageEnabled) {
+            Log.w(TAG, "connect(): called with mUsageEnabled=false");
+            return false;
         }
 
-        WifiNanClientState client = new WifiNanClientState(uid, listener, events);
-        mClients.put(uid, client);
+        if (mClients.get(clientId) != null) {
+            Log.e(TAG, "connectLocal: entry already exists for clientId=" + clientId);
+        }
+
+        if (mCurrentNanConfiguration != null
+                && !mCurrentNanConfiguration.equalsOnTheAir(configRequest)) {
+            try {
+                callback.onConnectFail(
+                        WifiNanEventCallback.REASON_ALREADY_CONNECTED_INCOMPAT_CONFIG);
+            } catch (RemoteException e) {
+                Log.w(TAG, "connectLocal onConnectFail(): RemoteException (FYI): " + e);
+            }
+            return false;
+        }
+
+        ConfigRequest merged = mergeConfigRequests(configRequest);
+        if (mCurrentNanConfiguration != null && mCurrentNanConfiguration.equals(merged)) {
+            try {
+                callback.onConnectSuccess();
+                mClients.append(clientId,
+                        new WifiNanClientState(clientId, callback, configRequest));
+            } catch (RemoteException e) {
+                Log.w(TAG, "connectLocal onConnectSuccess(): RemoteException (FYI): " + e);
+            }
+            return false;
+        }
+
+        return WifiNanNative.getInstance().enableAndConfigure(transactionId, merged,
+                mCurrentNanConfiguration == null);
     }
 
-    private void disconnectLocal(int uid) {
+    private boolean disconnectLocal(short transactionId, int clientId) {
         if (VDBG) {
-            Log.v(TAG, "disconnect(): uid=" + uid);
+            Log.v(TAG,
+                    "disconnectLocal(): transactionId=" + transactionId + ", clientId=" + clientId);
         }
 
-        WifiNanClientState client = mClients.get(uid);
-        mClients.delete(uid);
-
+        WifiNanClientState client = mClients.get(clientId);
         if (client == null) {
-            Log.e(TAG, "disconnect: no entry for uid=" + uid);
-            return;
+            Log.e(TAG, "disconnectLocal: no entry for clientId=" + clientId);
+            return false;
         }
-
-        List<Integer> toRemove = new ArrayList<>();
-        for (int i = 0; i < mPendingResponses.size(); ++i) {
-            TransactionInfoBase info = mPendingResponses.valueAt(i);
-            if (!(info instanceof TransactionInfoSession)) {
-                continue;
-            }
-            if (((TransactionInfoSession) info).mClient.getUid() == uid) {
-                toRemove.add(i);
-            }
-        }
-        for (Integer id : toRemove) {
-            mPendingResponses.removeAt(id);
-        }
-
+        mClients.delete(clientId);
         client.destroy();
 
         if (mClients.size() == 0) {
-            WifiNanNative.getInstance().disable(createTransactionInfo().mTransactionId);
-            return;
+            mCurrentNanConfiguration = null;
+            WifiNanNative.getInstance().disable((short) 0);
+            return false;
         }
 
-        ConfigRequest merged = mergeConfigRequests();
+        ConfigRequest merged = mergeConfigRequests(null);
+        if (merged.equals(mCurrentNanConfiguration)) {
+            return false;
+        }
 
-        WifiNanNative.getInstance()
-                .enableAndConfigure(createTransactionInfoConfig(merged).mTransactionId, merged);
+        return WifiNanNative.getInstance().enableAndConfigure(transactionId, merged, false);
     }
 
-    private void requestConfigLocal(int uid, ConfigRequest configRequest) {
+    private void terminateSessionLocal(int clientId, int sessionId) {
         if (VDBG) {
-            Log.v(TAG, "requestConfig(): uid=" + uid + ", configRequest=" + configRequest);
+            Log.v(TAG,
+                    "terminateSessionLocal(): clientId=" + clientId + ", sessionId=" + sessionId);
         }
 
-        WifiNanClientState client = mClients.get(uid);
+        WifiNanClientState client = mClients.get(clientId);
         if (client == null) {
-            Log.e(TAG, "requestConfig: no client exists for uid=" + uid);
+            Log.e(TAG, "terminateSession: no client exists for clientId=" + clientId);
             return;
         }
 
-        client.setConfigRequest(configRequest);
-
-        ConfigRequest merged = mergeConfigRequests();
-
-        WifiNanNative.getInstance()
-                .enableAndConfigure(createTransactionInfoConfig(merged).mTransactionId, merged);
+        client.terminateSession(sessionId);
     }
 
-    private void createSessionLocal(int uid, int sessionId, IWifiNanSessionListener listener,
-            int events) {
+    private boolean publishLocal(short transactionId, int clientId, PublishConfig publishConfig,
+            IWifiNanSessionCallback callback) {
         if (VDBG) {
-            Log.v(TAG, "createSession(): uid=" + uid + ", sessionId=" + sessionId + ", listener="
-                    + listener + ", events=" + events);
+            Log.v(TAG, "publishLocal(): transactionId=" + transactionId + ", clientId=" + clientId
+                    + ", publishConfig=" + publishConfig + ", callback=" + callback);
         }
 
-        WifiNanClientState client = mClients.get(uid);
+        WifiNanClientState client = mClients.get(clientId);
         if (client == null) {
-            Log.e(TAG, "createSession: no client exists for uid=" + uid);
-            return;
+            Log.e(TAG, "publishLocal: no client exists for clientId=" + clientId);
+            return false;
         }
 
-        client.createSession(sessionId, listener, events);
+        return WifiNanNative.getInstance().publish(transactionId, 0, publishConfig);
     }
 
-    private void destroySessionLocal(int uid, int sessionId) {
+    private boolean updatePublishLocal(short transactionId, int clientId, int sessionId,
+            PublishConfig publishConfig) {
         if (VDBG) {
-            Log.v(TAG, "destroySession(): uid=" + uid + ", sessionId=" + sessionId);
+            Log.v(TAG, "updatePublishLocal(): transactionId=" + transactionId + ", clientId="
+                    + clientId + ", sessionId=" + sessionId + ", publishConfig=" + publishConfig);
         }
 
-        WifiNanClientState client = mClients.get(uid);
+        WifiNanClientState client = mClients.get(clientId);
         if (client == null) {
-            Log.e(TAG, "destroySession: no client exists for uid=" + uid);
+            Log.e(TAG, "updatePublishLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "updatePublishLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            return false;
+        }
+
+        return session.updatePublish(transactionId, publishConfig);
+    }
+
+    private boolean subscribeLocal(short transactionId, int clientId,
+            SubscribeConfig subscribeConfig, IWifiNanSessionCallback callback) {
+        if (VDBG) {
+            Log.v(TAG, "subscribeLocal(): transactionId=" + transactionId + ", clientId=" + clientId
+                    + ", subscribeConfig=" + subscribeConfig + ", callback=" + callback);
+        }
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "subscribeLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        return WifiNanNative.getInstance().subscribe(transactionId, 0, subscribeConfig);
+    }
+
+    private boolean updateSubscribeLocal(short transactionId, int clientId, int sessionId,
+            SubscribeConfig subscribeConfig) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "updateSubscribeLocal(): transactionId=" + transactionId + ", clientId="
+                            + clientId + ", sessionId=" + sessionId + ", subscribeConfig="
+                            + subscribeConfig);
+        }
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "updateSubscribeLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "updateSubscribeLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            return false;
+        }
+
+        return session.updateSubscribe(transactionId, subscribeConfig);
+    }
+
+    private boolean sendFollowonMessageLocal(short transactionId, int clientId, int sessionId,
+            int peerId, byte[] message, int messageLength, int messageId) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "sendFollowonMessageLocal(): transactionId=" + transactionId + ", clientId="
+                            + clientId + ", sessionId=" + sessionId + ", peerId=" + peerId
+                            + ", messageLength=" + messageLength + ", messageId=" + messageId);
+        }
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "sendFollowonMessageLocal: no client exists for clientId=" + clientId);
+            return false;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "sendFollowonMessageLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            return false;
+        }
+
+        return session.sendMessage(transactionId, peerId, message, messageLength, messageId);
+    }
+
+    private void enableUsageLocal() {
+        if (mUsageEnabled) {
             return;
         }
 
-        List<Integer> toRemove = new ArrayList<>();
-        for (int i = 0; i < mPendingResponses.size(); ++i) {
-            TransactionInfoBase info = mPendingResponses.valueAt(i);
-            if (!(info instanceof TransactionInfoSession)) {
-                continue;
-            }
-            TransactionInfoSession infoSession = (TransactionInfoSession) info;
-            if (infoSession.mClient.getUid() == uid
-                    && infoSession.mSession.getSessionId() == sessionId) {
-                toRemove.add(i);
+        mUsageEnabled = true;
+        sendNanStateChangedBroadcast(true);
+    }
+
+    private void disableUsageLocal() {
+        if (!mUsageEnabled) {
+            return;
+        }
+
+        mUsageEnabled = false;
+        WifiNanNative.getInstance().disable((short) 0);
+        onNanDownLocal();
+
+        sendNanStateChangedBroadcast(false);
+    }
+
+    private void startRangingLocal(int clientId, int sessionId, RttManager.RttParams[] params,
+                                   int rangingId) {
+        if (VDBG) {
+            Log.v(TAG, "startRangingLocal: clientId=" + clientId + ", sessionId=" + sessionId
+                    + ", parms=" + Arrays.toString(params) + ", rangingId=" + rangingId);
+        }
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "startRangingLocal: no client exists for clientId=" + clientId);
+            return;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "startRangingLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            client.onRangingFailure(rangingId, RttManager.REASON_INVALID_REQUEST,
+                    "Invalid session ID");
+            return;
+        }
+
+        for (int i = 0; i < params.length; ++i) {
+            String peerIdStr = params[i].bssid;
+            try {
+                params[i].bssid = session.getMac(Integer.parseInt(peerIdStr), ":");
+                if (params[i].bssid == null) {
+                    Log.d(TAG, "startRangingLocal: no MAC address for peer ID=" + peerIdStr);
+                    params[i].bssid = "";
+                }
+            } catch (NumberFormatException e) {
+                Log.e(TAG, "startRangingLocal: invalid peer ID specification (in bssid field): '"
+                        + peerIdStr + "'");
+                params[i].bssid = "";
             }
         }
-        for (Integer id : toRemove) {
-            mPendingResponses.removeAt(id);
-        }
 
-        client.destroySession(sessionId);
-    }
-
-    private void publishLocal(int uid, int sessionId, PublishData publishData,
-            PublishSettings publishSettings) {
-        if (VDBG) {
-            Log.v(TAG, "publish(): uid=" + uid + ", sessionId=" + sessionId + ", data="
-                    + publishData + ", settings=" + publishSettings);
-        }
-
-        TransactionInfoSession info = createTransactionInfoSession(uid, sessionId);
-
-        info.mSession.publish(info.mTransactionId, publishData, publishSettings);
-    }
-
-    private void subscribeLocal(int uid, int sessionId, SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings) {
-        if (VDBG) {
-            Log.v(TAG, "subscribe(): uid=" + uid + ", sessionId=" + sessionId + ", data="
-                    + subscribeData + ", settings=" + subscribeSettings);
-        }
-
-        TransactionInfoSession info = createTransactionInfoSession(uid, sessionId);
-
-        info.mSession.subscribe(info.mTransactionId, subscribeData, subscribeSettings);
-    }
-
-    private void sendFollowonMessageLocal(int uid, int sessionId, int peerId, byte[] message,
-            int messageLength, int messageId) {
-        if (VDBG) {
-            Log.v(TAG, "sendMessage(): uid=" + uid + ", sessionId=" + sessionId + ", peerId="
-                    + peerId + ", messageLength=" + messageLength + ", messageId=" + messageId);
-        }
-
-        TransactionInfoMessage info = createTransactionInfoMessage(uid, sessionId, messageId);
-
-        info.mSession.sendMessage(info.mTransactionId, peerId, message, messageLength, messageId);
-    }
-
-    private void stopSessionLocal(int uid, int sessionId) {
-        if (VDBG) {
-            Log.v(TAG, "stopSession(): uid=" + uid + ", sessionId=" + sessionId);
-        }
-
-        TransactionInfoSession info = createTransactionInfoSession(uid, sessionId);
-
-        info.mSession.stop(info.mTransactionId);
+        mRtt.startRanging(rangingId, client, params);
     }
 
     /*
-     * Callbacks (calls from HAL/Native to service)
+     * RESPONSES
      */
 
-    private void onCapabilitiesUpdatedLocal(short transactionId,
-            WifiNanNative.Capabilities capabilities) {
+    private void onConfigCompletedLocal(Message completedCommand) {
         if (VDBG) {
-            Log.v(TAG, "onCapabilitiesUpdatedLocal: transactionId=" + transactionId
-                    + ", capabilites=" + capabilities);
+            Log.v(TAG, "onConfigCompleted: completedCommand=" + completedCommand);
+        }
+
+        if (completedCommand.arg1 == COMMAND_TYPE_CONNECT) {
+            Bundle data = completedCommand.getData();
+
+            int clientId = completedCommand.arg2;
+            IWifiNanEventCallback callback = (IWifiNanEventCallback) completedCommand.obj;
+            ConfigRequest configRequest = (ConfigRequest) data
+                    .getParcelable(MESSAGE_BUNDLE_KEY_CONFIG);
+
+            mClients.put(clientId, new WifiNanClientState(clientId, callback, configRequest));
+            try {
+                callback.onConnectSuccess();
+            } catch (RemoteException e) {
+                Log.w(TAG,
+                        "onConfigCompletedLocal onConnectSuccess(): RemoteException (FYI): " + e);
+            }
+        } else if (completedCommand.arg1 == COMMAND_TYPE_DISCONNECT) {
+            /*
+             * NOP (i.e. updated configuration after disconnecting a client)
+             */
+        } else {
+            Log.wtf(TAG, "onConfigCompletedLocal: unexpected completedCommand=" + completedCommand);
+            return;
+        }
+
+        mCurrentNanConfiguration = mergeConfigRequests(null);
+    }
+
+    private void onConfigFailedLocal(Message failedCommand, int reason) {
+        if (VDBG) {
+            Log.v(TAG,
+                    "onConfigFailedLocal: failedCommand=" + failedCommand + ", reason=" + reason);
+        }
+
+        if (failedCommand.arg1 == COMMAND_TYPE_CONNECT) {
+            IWifiNanEventCallback callback = (IWifiNanEventCallback) failedCommand.obj;
+
+            try {
+                callback.onConnectFail(reason);
+            } catch (RemoteException e) {
+                Log.w(TAG, "onConfigFailedLocal onConnectFail(): RemoteException (FYI): " + e);
+            }
+        } else if (failedCommand.arg1 == COMMAND_TYPE_DISCONNECT) {
+            /*
+             * NOP (tried updating configuration after disconnecting a client -
+             * shouldn't fail but there's nothing to do - the old configuration
+             * is still up-and-running).
+             */
+        } else {
+            Log.wtf(TAG, "onConfigFailedLocal: unexpected failedCommand=" + failedCommand);
+            return;
+        }
+
+    }
+
+    private void onSessionConfigSuccessLocal(Message completedCommand, int pubSubId,
+            boolean isPublish) {
+        if (VDBG) {
+            Log.v(TAG, "onSessionConfigSuccessLocal: completedCommand=" + completedCommand
+                    + ", pubSubId=" + pubSubId + ", isPublish=" + isPublish);
+        }
+
+        if (completedCommand.arg1 == COMMAND_TYPE_PUBLISH
+                || completedCommand.arg1 == COMMAND_TYPE_SUBSCRIBE) {
+            int clientId = completedCommand.arg2;
+            IWifiNanSessionCallback callback = (IWifiNanSessionCallback) completedCommand.obj;
+
+            WifiNanClientState client = mClients.get(clientId);
+            if (client == null) {
+                Log.e(TAG,
+                        "onSessionConfigSuccessLocal: no client exists for clientId=" + clientId);
+                return;
+            }
+
+            int sessionId = mSm.mNextSessionId++;
+            try {
+                callback.onSessionStarted(sessionId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onSessionConfigSuccessLocal: onSessionStarted() RemoteException=" + e);
+                return;
+            }
+
+            WifiNanSessionState session = new WifiNanSessionState(sessionId, pubSubId, callback,
+                    isPublish);
+            client.addSession(session);
+        } else if (completedCommand.arg1 == COMMAND_TYPE_UPDATE_PUBLISH
+                || completedCommand.arg1 == COMMAND_TYPE_UPDATE_SUBSCRIBE) {
+            int clientId = completedCommand.arg2;
+            int sessionId = completedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+
+            WifiNanClientState client = mClients.get(clientId);
+            if (client == null) {
+                Log.e(TAG,
+                        "onSessionConfigSuccessLocal: no client exists for clientId=" + clientId);
+                return;
+            }
+
+            WifiNanSessionState session = client.getSession(sessionId);
+            if (session == null) {
+                Log.e(TAG, "onSessionConfigSuccessLocal: no session exists for clientId=" + clientId
+                        + ", sessionId=" + sessionId);
+                return;
+            }
+
+            try {
+                session.getCallback().onSessionConfigSuccess();
+            } catch (RemoteException e) {
+                Log.e(TAG, "onSessionConfigSuccessLocal: onSessionConfigSuccess() RemoteException="
+                        + e);
+            }
+        } else {
+            Log.wtf(TAG,
+                    "onSessionConfigSuccessLocal: unexpected completedCommand=" + completedCommand);
+        }
+    }
+
+    private void onSessionConfigFailLocal(Message failedCommand, boolean isPublish, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onSessionConfigFailLocal: failedCommand=" + failedCommand + ", isPublish="
+                    + isPublish + ", reason=" + reason);
+        }
+
+        if (failedCommand.arg1 == COMMAND_TYPE_PUBLISH
+                || failedCommand.arg1 == COMMAND_TYPE_SUBSCRIBE) {
+            IWifiNanSessionCallback callback = (IWifiNanSessionCallback) failedCommand.obj;
+            try {
+                callback.onSessionConfigFail(reason);
+            } catch (RemoteException e) {
+                Log.w(TAG, "onSessionConfigFailLocal onSessionConfigFail(): RemoteException (FYI): "
+                        + e);
+            }
+        } else if (failedCommand.arg1 == COMMAND_TYPE_UPDATE_PUBLISH
+                || failedCommand.arg1 == COMMAND_TYPE_UPDATE_SUBSCRIBE) {
+            int clientId = failedCommand.arg2;
+            int sessionId = failedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+
+            WifiNanClientState client = mClients.get(clientId);
+            if (client == null) {
+                Log.e(TAG, "onSessionConfigFailLocal: no client exists for clientId=" + clientId);
+                return;
+            }
+
+            WifiNanSessionState session = client.getSession(sessionId);
+            if (session == null) {
+                Log.e(TAG, "onSessionConfigFailLocal: no session exists for clientId=" + clientId
+                        + ", sessionId=" + sessionId);
+                return;
+            }
+
+            try {
+                session.getCallback().onSessionConfigFail(reason);
+            } catch (RemoteException e) {
+                Log.e(TAG, "onSessionConfigFailLocal: onSessionConfigFail() RemoteException=" + e);
+            }
+        } else {
+            Log.wtf(TAG, "onSessionConfigFailLocal: unexpected failedCommand=" + failedCommand);
+        }
+    }
+
+    private void onMessageSendSuccessLocal(Message completedCommand) {
+        if (VDBG) {
+            Log.v(TAG, "onMessageSendSuccess: completedCommand=" + completedCommand);
+        }
+
+        int clientId = completedCommand.arg2;
+        int sessionId = completedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+        int messageId = completedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID);
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "onMessageSendSuccessLocal: no client exists for clientId=" + clientId);
+            return;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "onMessageSendSuccessLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            return;
+        }
+
+        try {
+            session.getCallback().onMessageSendSuccess(messageId);
+        } catch (RemoteException e) {
+            Log.w(TAG, "onMessageSendSuccessLocal: RemoteException (FYI): " + e);
+        }
+    }
+
+    private void onMessageSendFailLocal(Message failedCommand, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onMessageSendFail: failedCommand=" + failedCommand + ", reason=" + reason);
+        }
+
+        int clientId = failedCommand.arg2;
+        int sessionId = failedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
+        int messageId = failedCommand.getData().getInt(MESSAGE_BUNDLE_KEY_MESSAGE_ID);
+
+        WifiNanClientState client = mClients.get(clientId);
+        if (client == null) {
+            Log.e(TAG, "onMessageSendFailLocal: no client exists for clientId=" + clientId);
+            return;
+        }
+
+        WifiNanSessionState session = client.getSession(sessionId);
+        if (session == null) {
+            Log.e(TAG, "onMessageSendFailLocal: no session exists for clientId=" + clientId
+                    + ", sessionId=" + sessionId);
+            return;
+        }
+
+        try {
+            session.getCallback().onMessageSendFail(messageId, reason);
+        } catch (RemoteException e) {
+            Log.e(TAG, "onMessageSendFailLocal: onMessageSendFail RemoteException=" + e);
+        }
+    }
+
+    /*
+     * NOTIFICATIONS
+     */
+
+    private void onCapabilitiesUpdatedLocal(WifiNanNative.Capabilities capabilities) {
+        if (VDBG) {
+            Log.v(TAG, "onCapabilitiesUpdatedLocal: capabilites=" + capabilities);
         }
 
         mCapabilities = capabilities;
     }
 
-    private void onConfigCompletedLocal(short transactionId) {
-        if (VDBG) {
-            Log.v(TAG, "onConfigCompleted: transactionId=" + transactionId);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onConfigCompleted: no transaction info for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoConfig)) {
-            Log.e(TAG, "onConfigCompleted: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoConfig infoConfig = (TransactionInfoConfig) info;
-
-        if (DBG) {
-            Log.d(TAG, "onConfigCompleted: request=" + infoConfig.mConfig);
-        }
-
-        for (int i = 0; i < mClients.size(); ++i) {
-            WifiNanClientState client = mClients.valueAt(i);
-            client.onConfigCompleted(infoConfig.mConfig);
-        }
-    }
-
-    private void onConfigFailedLocal(short transactionId, int reason) {
-        if (VDBG) {
-            Log.v(TAG, "onEnableFailed: transactionId=" + transactionId + ", reason=" + reason);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onConfigFailed: no transaction info for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoConfig)) {
-            Log.e(TAG, "onConfigCompleted: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoConfig infoConfig = (TransactionInfoConfig) info;
-
-        if (DBG) {
-            Log.d(TAG, "onConfigFailed: request=" + infoConfig.mConfig);
-        }
-
-        for (int i = 0; i < mClients.size(); ++i) {
-            WifiNanClientState client = mClients.valueAt(i);
-            client.onConfigFailed(infoConfig.mConfig, reason);
-        }
-    }
-
-    private void onNanDownLocal(int reason) {
-        if (VDBG) {
-            Log.v(TAG, "onNanDown: reason=" + reason);
-        }
-
-        int interested = 0;
-        for (int i = 0; i < mClients.size(); ++i) {
-            WifiNanClientState client = mClients.valueAt(i);
-            interested += client.onNanDown(reason);
-        }
-
-        if (interested == 0) {
-            Log.e(TAG, "onNanDown: event received but no listeners registered for this event "
-                    + "- should be disabled from fw!");
-        }
-    }
-
     private void onInterfaceAddressChangeLocal(byte[] mac) {
         if (VDBG) {
             Log.v(TAG, "onInterfaceAddressChange: mac=" + String.valueOf(HexEncoding.encode(mac)));
@@ -881,7 +1669,7 @@
         }
 
         if (interested == 0) {
-            Log.e(TAG, "onInterfaceAddressChange: event received but no listeners registered "
+            Log.e(TAG, "onInterfaceAddressChange: event received but no callbacks registered "
                     + "for this event - should be disabled from fw!");
         }
     }
@@ -899,175 +1687,11 @@
         }
 
         if (interested == 0) {
-            Log.e(TAG, "onClusterChange: event received but no listeners registered for this "
+            Log.e(TAG, "onClusterChange: event received but no callbacks registered for this "
                     + "event - should be disabled from fw!");
         }
     }
 
-    private void onPublishSuccessLocal(short transactionId, int publishId) {
-        if (VDBG) {
-            Log.v(TAG, "onPublishSuccess: transactionId=" + transactionId + ", publishId="
-                    + publishId);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onPublishSuccess(): no info registered for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoSession)) {
-            Log.e(TAG, "onPublishSuccess: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoSession infoSession = (TransactionInfoSession) info;
-
-        infoSession.mSession.onPublishSuccess(publishId);
-    }
-
-    private void onPublishFailLocal(short transactionId, int status) {
-        if (VDBG) {
-            Log.v(TAG, "onPublishFail: transactionId=" + transactionId + ", status=" + status);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onPublishFail(): no info registered for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoSession)) {
-            Log.e(TAG, "onPublishFail: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoSession infoSession = (TransactionInfoSession) info;
-
-        infoSession.mSession.onPublishFail(status);
-    }
-
-    private void onPublishTerminatedLocal(int publishId, int status) {
-        if (VDBG) {
-            Log.v(TAG, "onPublishTerminated: publishId=" + publishId + ", status=" + status);
-        }
-
-        WifiNanSessionState session = getNanSessionStateForPubSubId(publishId);
-        if (session == null) {
-            Log.e(TAG, "onPublishTerminated: no session found for publishId=" + publishId);
-            return;
-        }
-
-        session.onPublishTerminated(status);
-    }
-
-    private void onSubscribeSuccessLocal(short transactionId, int subscribeId) {
-        if (VDBG) {
-            Log.v(TAG, "onSubscribeSuccess: transactionId=" + transactionId + ", subscribeId="
-                    + subscribeId);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG,
-                    "onSubscribeSuccess(): no info registered for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoSession)) {
-            Log.e(TAG, "onSubscribeSuccess: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoSession infoSession = (TransactionInfoSession) info;
-
-        infoSession.mSession.onSubscribeSuccess(subscribeId);
-    }
-
-    private void onSubscribeFailLocal(short transactionId, int status) {
-        if (VDBG) {
-            Log.v(TAG, "onSubscribeFail: transactionId=" + transactionId + ", status=" + status);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onSubscribeFail(): no info registered for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoSession)) {
-            Log.e(TAG, "onSubscribeFail: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoSession infoSession = (TransactionInfoSession) info;
-
-        infoSession.mSession.onSubscribeFail(status);
-    }
-
-    private void onSubscribeTerminatedLocal(int subscribeId, int status) {
-        if (VDBG) {
-            Log.v(TAG, "onPublishTerminated: subscribeId=" + subscribeId + ", status=" + status);
-        }
-
-        WifiNanSessionState session = getNanSessionStateForPubSubId(subscribeId);
-        if (session == null) {
-            Log.e(TAG, "onSubscribeTerminated: no session found for subscribeId=" + subscribeId);
-            return;
-        }
-
-        session.onSubscribeTerminated(status);
-    }
-
-    private void onMessageSendSuccessLocal(short transactionId) {
-        if (VDBG) {
-            Log.v(TAG, "onMessageSendSuccess: transactionId=" + transactionId);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onMessageSendSuccess(): no info registered for transactionId="
-                    + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoMessage)) {
-            Log.e(TAG, "onMessageSendSuccess: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoMessage infoMessage = (TransactionInfoMessage) info;
-
-        infoMessage.mSession.onMessageSendSuccess(infoMessage.mMessageId);
-    }
-
-    private void onMessageSendFailLocal(short transactionId, int status) {
-        if (VDBG) {
-            Log.v(TAG, "onMessageSendFail: transactionId=" + transactionId + ", status=" + status);
-        }
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG,
-                    "onMessageSendFail(): no info registered for transactionId=" + transactionId);
-            return;
-        }
-        if (!(info instanceof TransactionInfoMessage)) {
-            Log.e(TAG, "onMessageSendFail: invalid info structure stored for transactionId="
-                    + transactionId);
-            return;
-        }
-        TransactionInfoMessage infoMessage = (TransactionInfoMessage) info;
-
-        infoMessage.mSession.onMessageSendFail(infoMessage.mMessageId, status);
-    }
-
-    private void onUnknownTransactionLocal(int responseType, short transactionId, int status) {
-        Log.e(TAG, "onUnknownTransaction: responseType=" + responseType + ", transactionId="
-                + transactionId + ", status=" + status);
-
-        TransactionInfoBase info = getAndRemovePendingResponseTransactionInfo(transactionId);
-        if (info == null) {
-            Log.e(TAG, "onUnknownTransaction(): no info registered for transactionId="
-                    + transactionId);
-        }
-    }
-
     private void onMatchLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
             byte[] serviceSpecificInfo, int serviceSpecificInfoLength, byte[] matchFilter,
             int matchFilterLength) {
@@ -1075,63 +1699,118 @@
             Log.v(TAG, "onMatch: pubSubId=" + pubSubId + ", requestorInstanceId="
                     + requestorInstanceId + ", peerMac="
                     + String.valueOf(HexEncoding.encode(peerMac)) + ", serviceSpecificInfoLength="
-                    + serviceSpecificInfoLength + ", serviceSpecificInfo=" + serviceSpecificInfo
-                    + ", matchFilterLength=" + matchFilterLength + ", matchFilter=" + matchFilter);
+                    + serviceSpecificInfoLength + ", serviceSpecificInfo="
+                    + Arrays.toString(serviceSpecificInfo) + ", matchFilterLength="
+                    + matchFilterLength + ", matchFilter=" + Arrays.toString(matchFilter));
         }
 
-        WifiNanSessionState session = getNanSessionStateForPubSubId(pubSubId);
-        if (session == null) {
+        Pair<WifiNanClientState, WifiNanSessionState> data = getClientSessionForPubSubId(pubSubId);
+        if (data == null) {
             Log.e(TAG, "onMatch: no session found for pubSubId=" + pubSubId);
             return;
         }
 
-        session.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo,
+        data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo,
                 serviceSpecificInfoLength, matchFilter, matchFilterLength);
     }
 
+    private void onSessionTerminatedLocal(int pubSubId, boolean isPublish, int reason) {
+        if (VDBG) {
+            Log.v(TAG, "onSessionTerminatedLocal: pubSubId=" + pubSubId + ", isPublish=" + isPublish
+                    + ", reason=" + reason);
+        }
+
+        Pair<WifiNanClientState, WifiNanSessionState> data = getClientSessionForPubSubId(pubSubId);
+        if (data == null) {
+            Log.e(TAG, "onSessionTerminatedLocal: no session found for pubSubId=" + pubSubId);
+            return;
+        }
+
+        try {
+            data.second.getCallback().onSessionTerminated(reason);
+        } catch (RemoteException e) {
+            Log.w(TAG,
+                    "onSessionTerminatedLocal onSessionTerminated(): RemoteException (FYI): " + e);
+        }
+        data.first.removeSession(data.second.getSessionId());
+    }
+
     private void onMessageReceivedLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
             byte[] message, int messageLength) {
         if (VDBG) {
             Log.v(TAG,
-                    "onMessageReceived: pubSubId=" + pubSubId + ", requestorInstanceId="
+                    "onMessageReceivedLocal: pubSubId=" + pubSubId + ", requestorInstanceId="
                             + requestorInstanceId + ", peerMac="
                             + String.valueOf(HexEncoding.encode(peerMac)) + ", messageLength="
                             + messageLength);
         }
 
-        WifiNanSessionState session = getNanSessionStateForPubSubId(pubSubId);
-        if (session == null) {
-            Log.e(TAG, "onMessageReceived: no session found for pubSubId=" + pubSubId);
+        Pair<WifiNanClientState, WifiNanSessionState> data = getClientSessionForPubSubId(pubSubId);
+        if (data == null) {
+            Log.e(TAG, "onMessageReceivedLocal: no session found for pubSubId=" + pubSubId);
             return;
         }
 
-        session.onMessageReceived(requestorInstanceId, peerMac, message, messageLength);
+        data.second.onMessageReceived(requestorInstanceId, peerMac, message, messageLength);
     }
 
-    private ConfigRequest mergeConfigRequests() {
+    private void onNanDownLocal() {
         if (VDBG) {
-            Log.v(TAG, "mergeConfigRequests(): mClients=[" + mClients + "]");
+            Log.v(TAG, "onNanDown");
         }
 
-        if (mClients.size() == 0) {
+        mClients.clear();
+        mCurrentNanConfiguration = null;
+    }
+
+    /*
+     * Utilities
+     */
+
+    private Pair<WifiNanClientState, WifiNanSessionState> getClientSessionForPubSubId(
+            int pubSubId) {
+        for (int i = 0; i < mClients.size(); ++i) {
+            WifiNanClientState client = mClients.valueAt(i);
+            WifiNanSessionState session = client.getNanSessionStateForPubSubId(pubSubId);
+            if (session != null) {
+                return new Pair<>(client, session);
+            }
+        }
+
+        return null;
+    }
+
+    private ConfigRequest mergeConfigRequests(ConfigRequest configRequest) {
+        if (VDBG) {
+            Log.v(TAG, "mergeConfigRequests(): mClients=[" + mClients + "], configRequest="
+                    + configRequest);
+        }
+
+        if (mClients.size() == 0 && configRequest == null) {
             Log.e(TAG, "mergeConfigRequests: invalid state - called with 0 clients registered!");
             return null;
         }
 
-        if (mClients.size() == 1) {
-            return mClients.valueAt(0).getConfigRequest();
-        }
-
         // TODO: continue working on merge algorithm:
         // - if any request 5g: enable
         // - maximal master preference
         // - cluster range covering all requests: assume that [0,max] is a
         // non-request
+        // - if any request identity change: enable
         boolean support5gBand = false;
         int masterPreference = 0;
         boolean clusterIdValid = false;
         int clusterLow = 0;
         int clusterHigh = ConfigRequest.CLUSTER_ID_MAX;
+        boolean identityChange = false;
+        if (configRequest != null) {
+            support5gBand = configRequest.mSupport5gBand;
+            masterPreference = configRequest.mMasterPreference;
+            clusterIdValid = true;
+            clusterLow = configRequest.mClusterLow;
+            clusterHigh = configRequest.mClusterHigh;
+            identityChange = configRequest.mEnableIdentityChangeCallback;
+        }
         for (int i = 0; i < mClients.size(); ++i) {
             ConfigRequest cr = mClients.valueAt(i).getConfigRequest();
 
@@ -1151,22 +1830,154 @@
                 }
                 clusterIdValid = true;
             }
-        }
-        ConfigRequest.Builder builder = new ConfigRequest.Builder();
-        builder.setSupport5gBand(support5gBand).setMasterPreference(masterPreference)
-                .setClusterLow(clusterLow).setClusterHigh(clusterHigh);
 
-        return builder.build();
+            if (cr.mEnableIdentityChangeCallback) {
+                identityChange = true;
+            }
+        }
+        return new ConfigRequest.Builder().setSupport5gBand(support5gBand)
+                .setMasterPreference(masterPreference).setClusterLow(clusterLow)
+                .setClusterHigh(clusterHigh).setEnableIdentityChangeCallback(identityChange)
+                .build();
     }
 
+    private static String messageToString(Message msg) {
+        StringBuilder sb = new StringBuilder();
+
+        switch (msg.what) {
+            case MESSAGE_TYPE_NOTIFICATION:
+                sb.append("NOTIFICATION/");
+                switch (msg.arg1) {
+                    case NOTIFICATION_TYPE_CAPABILITIES_UPDATED:
+                        sb.append("CAPABILITIES_UPDATED");
+                        break;
+                    case NOTIFICATION_TYPE_INTERFACE_CHANGE:
+                        sb.append("INTERFACE_CHANGE");
+                        break;
+                    case NOTIFICATION_TYPE_CLUSTER_CHANGE:
+                        sb.append("CLUSTER_CHANGE");
+                        break;
+                    case NOTIFICATION_TYPE_MATCH:
+                        sb.append("MATCH");
+                        break;
+                    case NOTIFICATION_TYPE_SESSION_TERMINATED:
+                        sb.append("SESSION_TERMINATED");
+                        break;
+                    case NOTIFICATION_TYPE_MESSAGE_RECEIVED:
+                        sb.append("MESSAGE_RECEIVED");
+                        break;
+                    case NOTIFICATION_TYPE_NAN_DOWN:
+                        sb.append("NAN_DOWN");
+                        break;
+                    case NOTIFICATION_TYPE_ON_MESSAGE_SEND_SUCCESS:
+                        sb.append("ON_MESSAGE_SEND_SUCCESS");
+                        break;
+                    case NOTIFICATION_TYPE_ON_MESSAGE_SEND_FAIL:
+                        sb.append("ON_MESSAGE_SEND_FAIL");
+                        break;
+                    default:
+                        sb.append("<unknown>");
+                        break;
+                }
+                break;
+            case MESSAGE_TYPE_COMMAND:
+                sb.append("COMMAND/");
+                switch (msg.arg1) {
+                    case COMMAND_TYPE_CONNECT:
+                        sb.append("CONNECT");
+                        break;
+                    case COMMAND_TYPE_DISCONNECT:
+                        sb.append("DISCONNECT");
+                        break;
+                    case COMMAND_TYPE_TERMINATE_SESSION:
+                        sb.append("TERMINATE_SESSION");
+                        break;
+                    case COMMAND_TYPE_PUBLISH:
+                        sb.append("PUBLISH");
+                        break;
+                    case COMMAND_TYPE_UPDATE_PUBLISH:
+                        sb.append("UPDATE_PUBLISH");
+                        break;
+                    case COMMAND_TYPE_SUBSCRIBE:
+                        sb.append("SUBSCRIBE");
+                        break;
+                    case COMMAND_TYPE_UPDATE_SUBSCRIBE:
+                        sb.append("UPDATE_SUBSCRIBE");
+                        break;
+                    case COMMAND_TYPE_SEND_MESSAGE:
+                        sb.append("SEND_MESSAGE");
+                        break;
+                    case COMMAND_TYPE_ENABLE_USAGE:
+                        sb.append("ENABLE_USAGE");
+                        break;
+                    case COMMAND_TYPE_DISABLE_USAGE:
+                        sb.append("DISABLE_USAGE");
+                        break;
+                    case COMMAND_TYPE_START_RANGING:
+                        sb.append("START_RANGING");
+                        break;
+                    default:
+                        sb.append("<unknown>");
+                        break;
+                }
+                break;
+            case MESSAGE_TYPE_RESPONSE:
+                sb.append("RESPONSE/");
+                switch (msg.arg1) {
+                    case RESPONSE_TYPE_ON_CONFIG_SUCCESS:
+                        sb.append("ON_CONFIG_SUCCESS");
+                        break;
+                    case RESPONSE_TYPE_ON_CONFIG_FAIL:
+                        sb.append("ON_CONFIG_FAIL");
+                        break;
+                    case RESPONSE_TYPE_ON_SESSION_CONFIG_SUCCESS:
+                        sb.append("ON_SESSION_CONFIG_SUCCESS");
+                        break;
+                    case RESPONSE_TYPE_ON_SESSION_CONFIG_FAIL:
+                        sb.append("ON_SESSION_CONFIG_FAIL");
+                        break;
+                    case RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_SUCCESS:
+                        sb.append("ON_MESSAGE_SEND_QUEUED_SUCCESS");
+                        break;
+                    case RESPONSE_TYPE_ON_MESSAGE_SEND_QUEUED_FAIL:
+                        sb.append("ON_MESSAGE_SEND_QUEUED_FAIL");
+                        break;
+                    default:
+                        sb.append("<unknown>");
+                        break;
+
+                }
+                break;
+            case MESSAGE_TYPE_RESPONSE_TIMEOUT:
+                sb.append("RESPONSE_TIMEOUT");
+                break;
+            case MESSAGE_TYPE_SEND_MESSAGE_TIMEOUT:
+                sb.append("SEND_MESSAGE_TIMEOUT");
+                break;
+            default:
+                sb.append("<unknown>");
+        }
+
+        if (msg.what == MESSAGE_TYPE_RESPONSE || msg.what == MESSAGE_TYPE_RESPONSE_TIMEOUT) {
+            sb.append(" (Transaction ID=" + msg.arg2 + ")");
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NanStateManager:");
         pw.println("  mClients: [" + mClients + "]");
-        pw.println("  mPendingResponses: [" + mPendingResponses + "]");
+        pw.println("  mUsageEnabled: " + mUsageEnabled);
         pw.println("  mCapabilities: [" + mCapabilities + "]");
-        pw.println("  mNextTransactionId: " + mNextTransactionId);
+        pw.println("  mCurrentNanConfiguration: " + mCurrentNanConfiguration);
         for (int i = 0; i < mClients.size(); ++i) {
             mClients.valueAt(i).dump(fd, pw, args);
         }
+        mSm.dump(fd, pw, args);
+        mRtt.dump(fd, pw, args);
     }
 }
diff --git a/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java b/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java
index 61c87f3..87ddb9c 100644
--- a/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java
+++ b/service/java/com/android/server/wifi/scanner/SupplicantWifiScannerImpl.java
@@ -68,7 +68,7 @@
     private final ChannelHelper mChannelHelper;
     private final Clock mClock;
 
-    private Object mSettingsLock = new Object();
+    private final Object mSettingsLock = new Object();
 
     // Next scan settings to apply when the previous scan completes
     private WifiNative.ScanSettings mPendingBackgroundScanSettings = null;
diff --git a/service/jni/com_android_server_wifi_nan_WifiNanNative.cpp b/service/jni/com_android_server_wifi_nan_WifiNanNative.cpp
index cae441a..f44d6b7 100644
--- a/service/jni/com_android_server_wifi_nan_WifiNanNative.cpp
+++ b/service/jni/com_android_server_wifi_nan_WifiNanNative.cpp
@@ -98,6 +98,8 @@
                          (int) msg->body.nan_capabilities.max_ndp_sessions);
       helper.setIntField(data, "maxAppInfoLen",
                          (int) msg->body.nan_capabilities.max_app_info_len);
+      helper.setIntField(data, "maxQueuedTransmitMessages",
+                         (int) msg->body.nan_capabilities.max_queued_transmit_followup_msgs);
 
       helper.reportEvent(
           mCls, "onNanNotifyResponseCapabilities",
@@ -208,6 +210,28 @@
     ALOGD("OnNanEventSdfPayload");
 }
 
+static void OnNanEventDataRequest(NanDataPathRequestInd* event) {
+  ALOGD("OnNanEventDataRequest");
+}
+
+static void OnNanEventDataConfirm(NanDataPathConfirmInd* event) {
+  ALOGD("OnNanEventDataConfirm");
+}
+
+static void OnNanEventDataEnd(NanDataPathEndInd* event) {
+  ALOGD("OnNanEventDataEnd");
+}
+
+static void OnNanEventTransmitFollowup(NanTransmitFollowupInd* event) {
+  ALOGD("OnNanEventTransmitFollowup: transaction_id=%d, reason=%d", event->id,
+        event->reason);
+
+  JNIHelper helper(mVM);
+
+  helper.reportEvent(mCls, "onTransmitFollowupEvent", "(SI)V",
+                     (short) event->id, (int) event->reason);
+}
+
 static jint android_net_wifi_nan_register_handler(JNIEnv *env, jclass cls,
                                                   jclass wifi_native_cls,
                                                   jint iface) {
@@ -217,6 +241,7 @@
     ALOGD("android_net_wifi_nan_register_handler handle=%p", handle);
 
     NanCallbackHandler handlers;
+    memset(&handlers, 0, sizeof(NanCallbackHandler));
     handlers.NotifyResponse = OnNanNotifyResponse;
     handlers.EventPublishTerminated = OnNanEventPublishTerminated;
     handlers.EventMatch = OnNanEventMatch;
@@ -227,6 +252,10 @@
     handlers.EventDisabled = OnNanEventDisabled;
     handlers.EventTca = OnNanEventTca;
     handlers.EventBeaconSdfPayload = OnNanEventBeaconSdfPayload;
+    handlers.EventDataRequest = OnNanEventDataRequest;
+    handlers.EventDataConfirm = OnNanEventDataConfirm;
+    handlers.EventDataEnd = OnNanEventDataEnd;
+    handlers.EventTransmitFollowup = OnNanEventTransmitFollowup;
 
     if (mVM == NULL) {
         env->GetJavaVM(&mVM);
@@ -260,6 +289,27 @@
     return hal_fn.wifi_nan_enable_request(transaction_id, handle, &msg);
 }
 
+static jint android_net_wifi_nan_config_request(JNIEnv *env, jclass cls,
+                                                jshort transaction_id,
+                                                jclass wifi_native_cls,
+                                                jint iface,
+                                                jobject config_request) {
+    JNIHelper helper(env);
+    wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
+
+    ALOGD("android_net_wifi_nan_config_request handle=%p, id=%d",
+          handle, transaction_id);
+
+    NanConfigRequest msg;
+    memset(&msg, 0, sizeof(NanConfigRequest));
+
+    /* configurable settings */
+    msg.config_master_pref = 1;
+    msg.master_pref = helper.getIntField(config_request, "mMasterPreference");
+
+    return hal_fn.wifi_nan_config_request(transaction_id, handle, &msg);
+}
+
 static jint android_net_wifi_nan_get_capabilities(JNIEnv *env, jclass cls,
                                                   jshort transaction_id,
                                                   jclass wifi_native_cls,
@@ -291,8 +341,7 @@
                                          jint publish_id,
                                          jclass wifi_native_cls,
                                          jint iface,
-                                         jobject publish_data,
-                                         jobject publish_settings) {
+                                         jobject publish_config) {
     JNIHelper helper(env);
     wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
 
@@ -310,7 +359,7 @@
     /* configurable settings */
     msg.publish_id = publish_id;
 
-    JNIObject<jstring> objStr1 = helper.getStringField(publish_data, "mServiceName");
+    JNIObject<jstring> objStr1 = helper.getStringField(publish_config, "mServiceName");
     if (objStr1 == NULL) {
         ALOGE("Error accessing mServiceName field");
         return 0;
@@ -324,33 +373,38 @@
     msg.service_name_len = strlen(serviceName);
     strcpy((char*)msg.service_name, serviceName);
 
-    msg.service_specific_info_len = helper.getIntField(publish_data, "mServiceSpecificInfoLength");
+    msg.service_specific_info_len = helper.getIntField(publish_config, "mServiceSpecificInfoLength");
     if (msg.service_specific_info_len != 0) {
-        helper.getByteArrayField(publish_data, "mServiceSpecificInfo",
+        helper.getByteArrayField(publish_config, "mServiceSpecificInfo",
                              msg.service_specific_info, msg.service_specific_info_len);
     }
 
 
-    msg.tx_match_filter_len = helper.getIntField(publish_data, "mTxFilterLength");
+    msg.tx_match_filter_len = helper.getIntField(publish_config, "mTxFilterLength");
     if (msg.tx_match_filter_len != 0) {
-        helper.getByteArrayField(publish_data, "mTxFilter",
+        helper.getByteArrayField(publish_config, "mTxFilter",
                              msg.tx_match_filter, msg.tx_match_filter_len);
     }
 
-    msg.rx_match_filter_len = helper.getIntField(publish_data, "mRxFilterLength");
+    msg.rx_match_filter_len = helper.getIntField(publish_config, "mRxFilterLength");
     if (msg.rx_match_filter_len != 0) {
-        helper.getByteArrayField(publish_data, "mRxFilter",
+        helper.getByteArrayField(publish_config, "mRxFilter",
                              msg.rx_match_filter, msg.rx_match_filter_len);
     }
 
-    msg.publish_type = (NanPublishType)helper.getIntField(publish_settings, "mPublishType");
-    msg.publish_count = helper.getIntField(publish_settings, "mPublishCount");
-    msg.ttl = helper.getIntField(publish_settings, "mTtlSec");
+    msg.publish_type = (NanPublishType)helper.getIntField(publish_config, "mPublishType");
+    msg.publish_count = helper.getIntField(publish_config, "mPublishCount");
+    msg.ttl = helper.getIntField(publish_config, "mTtlSec");
 
     msg.tx_type = NAN_TX_TYPE_BROADCAST;
     if (msg.publish_type != NAN_PUBLISH_TYPE_UNSOLICITED)
       msg.tx_type = NAN_TX_TYPE_UNICAST;
 
+    msg.recv_indication_cfg = 0;
+    if (!helper.getBoolField(publish_config, "mEnableTerminateNotification")) {
+      msg.recv_indication_cfg |= 0x1;
+    }
+
     return hal_fn.wifi_nan_publish_request(transaction_id, handle, &msg);
 }
 
@@ -359,8 +413,7 @@
                                            jint subscribe_id,
                                            jclass wifi_native_cls,
                                            jint iface,
-                                           jobject subscribe_data,
-                                           jobject subscribe_settings) {
+                                           jobject subscribe_config) {
     JNIHelper helper(env);
     wifi_interface_handle handle = getIfaceHandle(helper, wifi_native_cls, iface);
 
@@ -375,7 +428,6 @@
     msg.serviceResponseInclude = NAN_SRF_INCLUDE_RESPOND;
     msg.useServiceResponseFilter = NAN_DO_NOT_USE_SRF;
     msg.ssiRequiredForMatchIndication = NAN_SSI_NOT_REQUIRED_IN_MATCH_IND;
-    msg.subscribe_match_indicator = NAN_MATCH_ALG_MATCH_ONCE;
     msg.rssi_threshold_flag = 0;
     msg.connmap = 0;
     msg.num_intf_addr_present = 0;
@@ -383,7 +435,7 @@
     /* configurable settings */
     msg.subscribe_id = subscribe_id;
 
-    JNIObject<jstring> objStr1 = helper.getStringField(subscribe_data, "mServiceName");
+    JNIObject<jstring> objStr1 = helper.getStringField(subscribe_config, "mServiceName");
     if (objStr1 == NULL) {
         ALOGE("Error accessing mServiceName field");
         return 0;
@@ -397,27 +449,34 @@
     msg.service_name_len = strlen(serviceName);
     strcpy((char*)msg.service_name, serviceName);
 
-    msg.service_specific_info_len = helper.getIntField(subscribe_data, "mServiceSpecificInfoLength");
+    msg.service_specific_info_len = helper.getIntField(subscribe_config, "mServiceSpecificInfoLength");
     if (msg.service_specific_info_len != 0) {
-        helper.getByteArrayField(subscribe_data, "mServiceSpecificInfo",
+        helper.getByteArrayField(subscribe_config, "mServiceSpecificInfo",
                              msg.service_specific_info, msg.service_specific_info_len);
     }
 
-    msg.tx_match_filter_len = helper.getIntField(subscribe_data, "mTxFilterLength");
+    msg.tx_match_filter_len = helper.getIntField(subscribe_config, "mTxFilterLength");
     if (msg.tx_match_filter_len != 0) {
-        helper.getByteArrayField(subscribe_data, "mTxFilter",
+        helper.getByteArrayField(subscribe_config, "mTxFilter",
                              msg.tx_match_filter, msg.tx_match_filter_len);
     }
 
-    msg.rx_match_filter_len = helper.getIntField(subscribe_data, "mRxFilterLength");
+    msg.rx_match_filter_len = helper.getIntField(subscribe_config, "mRxFilterLength");
     if (msg.rx_match_filter_len != 0) {
-        helper.getByteArrayField(subscribe_data, "mRxFilter",
+        helper.getByteArrayField(subscribe_config, "mRxFilter",
                              msg.rx_match_filter, msg.rx_match_filter_len);
     }
 
-    msg.subscribe_type = (NanSubscribeType)helper.getIntField(subscribe_settings, "mSubscribeType");
-    msg.subscribe_count = helper.getIntField(subscribe_settings, "mSubscribeCount");
-    msg.ttl = helper.getIntField(subscribe_settings, "mTtlSec");
+    msg.subscribe_type = (NanSubscribeType)helper.getIntField(subscribe_config, "mSubscribeType");
+    msg.subscribe_count = helper.getIntField(subscribe_config, "mSubscribeCount");
+    msg.ttl = helper.getIntField(subscribe_config, "mTtlSec");
+    msg.subscribe_match_indicator = (NanMatchAlg) helper.getIntField(
+      subscribe_config, "mMatchStyle");
+
+    msg.recv_indication_cfg = 0;
+    if (!helper.getBoolField(subscribe_config, "mEnableTerminateNotification")) {
+      msg.recv_indication_cfg |= 0x1;
+    }
 
     return hal_fn.wifi_nan_subscribe_request(transaction_id, handle, &msg);
 }
@@ -501,16 +560,16 @@
 
 static JNINativeMethod gWifiNanMethods[] = {
     /* name, signature, funcPtr */
-
-    {"initNanHandlersNative", "(Ljava/lang/Object;I)I", (void*)android_net_wifi_nan_register_handler },
-    {"getCapabilitiesNative", "(SLjava/lang/Object;I)I", (void*)android_net_wifi_nan_get_capabilities },
-    {"enableAndConfigureNative", "(SLjava/lang/Object;ILandroid/net/wifi/nan/ConfigRequest;)I", (void*)android_net_wifi_nan_enable_request },
-    {"disableNative", "(SLjava/lang/Object;I)I", (void*)android_net_wifi_nan_disable_request },
-    {"publishNative", "(SILjava/lang/Object;ILandroid/net/wifi/nan/PublishData;Landroid/net/wifi/nan/PublishSettings;)I", (void*)android_net_wifi_nan_publish },
-    {"subscribeNative", "(SILjava/lang/Object;ILandroid/net/wifi/nan/SubscribeData;Landroid/net/wifi/nan/SubscribeSettings;)I", (void*)android_net_wifi_nan_subscribe },
-    {"sendMessageNative", "(SLjava/lang/Object;III[B[BI)I", (void*)android_net_wifi_nan_send_message },
-    {"stopPublishNative", "(SLjava/lang/Object;II)I", (void*)android_net_wifi_nan_stop_publish },
-    {"stopSubscribeNative", "(SLjava/lang/Object;II)I", (void*)android_net_wifi_nan_stop_subscribe },
+    {"initNanHandlersNative", "(Ljava/lang/Class;I)I", (void*)android_net_wifi_nan_register_handler },
+    {"getCapabilitiesNative", "(SLjava/lang/Class;I)I", (void*)android_net_wifi_nan_get_capabilities },
+    {"enableAndConfigureNative", "(SLjava/lang/Class;ILandroid/net/wifi/nan/ConfigRequest;)I", (void*)android_net_wifi_nan_enable_request },
+    {"updateConfigurationNative", "(SLjava/lang/Class;ILandroid/net/wifi/nan/ConfigRequest;)I", (void*)android_net_wifi_nan_config_request },
+    {"disableNative", "(SLjava/lang/Class;I)I", (void*)android_net_wifi_nan_disable_request },
+    {"publishNative", "(SILjava/lang/Class;ILandroid/net/wifi/nan/PublishConfig;)I", (void*)android_net_wifi_nan_publish },
+    {"subscribeNative", "(SILjava/lang/Class;ILandroid/net/wifi/nan/SubscribeConfig;)I", (void*)android_net_wifi_nan_subscribe },
+    {"sendMessageNative", "(SLjava/lang/Class;III[B[BI)I", (void*)android_net_wifi_nan_send_message },
+    {"stopPublishNative", "(SLjava/lang/Class;II)I", (void*)android_net_wifi_nan_stop_publish },
+    {"stopSubscribeNative", "(SLjava/lang/Class;II)I", (void*)android_net_wifi_nan_stop_subscribe },
 };
 
 /* User to register native functions */
diff --git a/service/jni/jni_helper.cpp b/service/jni/jni_helper.cpp
index c9b4edd..40768c6 100644
--- a/service/jni/jni_helper.cpp
+++ b/service/jni/jni_helper.cpp
@@ -514,6 +514,7 @@
     jmethodID methodID = mEnv->GetStaticMethodID(cls, method, signature);
     if (methodID == 0) {
         ALOGE("Error in getting method ID");
+        va_end(params);
         return;
     }
 
@@ -535,6 +536,7 @@
     jmethodID methodID = mEnv->GetMethodID(cls, method, signature);
     if (methodID == 0) {
         ALOGE("Error in getting method ID");
+        va_end(params);
         return;
     }
 
@@ -555,6 +557,7 @@
     jmethodID methodID = mEnv->GetStaticMethodID(cls, method, signature);
     if (methodID == 0) {
         ALOGE("Error in getting method ID");
+        va_end(params);
         return false;
     }
 
@@ -562,6 +565,7 @@
     if (mEnv->ExceptionCheck()) {
         mEnv->ExceptionDescribe();
         mEnv->ExceptionClear();
+        va_end(params);
         return false;
     }
 
@@ -582,18 +586,21 @@
     JNIObject<jclass> cls(*this, mEnv->FindClass(className));
     if (cls == NULL) {
         ALOGE("Error in finding class %s", className);
+        va_end(params);
         return JNIObject<jobject>(*this, NULL);
     }
 
     jmethodID constructor = mEnv->GetMethodID(cls, "<init>", signature);
     if (constructor == 0) {
         ALOGE("Error in constructor ID for %s", className);
+        va_end(params);
         return JNIObject<jobject>(*this, NULL);
     }
 
     JNIObject<jobject> obj(*this, mEnv->NewObjectV(cls, constructor, params));
     if (obj == NULL) {
         ALOGE("Could not create new object of %s", className);
+        va_end(params);
         return JNIObject<jobject>(*this, NULL);
     }
 
diff --git a/tests/wifitests/Android.mk b/tests/wifitests/Android.mk
index 4904bda..a25b62e 100644
--- a/tests/wifitests/Android.mk
+++ b/tests/wifitests/Android.mk
@@ -35,7 +35,7 @@
 LOCAL_SRC_FILES := \
 	jni/wifi_hal_mock.cpp
 
-ifdef INCLUDE_NAN_FEATURE
+ifeq ($(BOARD_HAS_NAN), true)
 LOCAL_SRC_FILES += \
 	jni/wifi_nan_hal_mock.cpp
 endif
@@ -61,12 +61,9 @@
 
 LOCAL_MODULE_TAGS := tests
 
-RESOURCE_FILES := $(call all-named-files-under, R.java, $(intermediates.COMMON))
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files) \
-	$RESOURCE_FILES
-
-ifndef INCLUDE_NAN_FEATURE
+ifneq ($(BOARD_HAS_NAN), true)
 LOCAL_SRC_FILES := $(filter-out $(call all-java-files-under, \
           src/com/android/server/wifi/nan),$(LOCAL_SRC_FILES))
 endif
@@ -106,6 +103,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
 	android-support-test \
 	mockito-target \
+	frameworks-base-testutils \
 	services \
 	wifi-service \
 
@@ -118,19 +116,37 @@
 # from apps.
 LOCAL_JNI_SHARED_LIBRARIES := \
 	libwifi-service \
-	libc++ \
-	libLLVM \
-	libutils \
-	libunwind \
-	libhardware_legacy \
-	libbase \
-	libhardware \
-	libnl \
-	libcutils \
-	libnetutils \
+	libEGL \
+	libGLESv2 \
+	libaudioutils \
 	libbacktrace \
-	libnativehelper \
+	libbase \
+	libbinder \
+	libc++ \
+	libcamera_client \
+	libcamera_metadata \
+	libcutils \
+	libexpat \
+	libgui \
+	libhardware \
+	libhardware_legacy \
+	libicui18n \
+	libicuuc \
 	liblzma \
+	libmedia \
+	libnativehelper \
+	libnbaio \
+	libnetutils \
+	libnl \
+	libpowermanager \
+	libsonivox \
+	libspeexresampler \
+	libstagefright_foundation \
+	libstdc++ \
+	libsync \
+	libui \
+	libunwind \
+	libutils \
 
 ifdef WPA_SUPPLICANT_VERSION
 LOCAL_JNI_SHARED_LIBRARIES += libwpa_client
diff --git a/tests/wifitests/jni/wifi_nan_hal_mock.cpp b/tests/wifitests/jni/wifi_nan_hal_mock.cpp
index 022fadd..a45410b 100644
--- a/tests/wifitests/jni/wifi_nan_hal_mock.cpp
+++ b/tests/wifitests/jni/wifi_nan_hal_mock.cpp
@@ -161,6 +161,7 @@
                        msg->tx_match_filter_len);
   jsonW.put_int("rssi_threshold_flag", msg->rssi_threshold_flag);
   jsonW.put_int("connmap", msg->connmap);
+  jsonW.put_int("recv_indication_cfg", msg->recv_indication_cfg);
   std::string str = jsonW.to_string();
 
   JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
@@ -222,6 +223,7 @@
   jsonW.put_int("connmap", msg->connmap);
   jsonW.put_int("num_intf_addr_present", msg->num_intf_addr_present);
   // TODO: jsonW.put_byte_array("intf_addr", msg->intf_addr, NAN_MAX_SUBSCRIBE_MAX_ADDRESS * NAN_MAC_ADDR_LEN);
+  jsonW.put_int("recv_indication_cfg", msg->recv_indication_cfg);
   std::string str = jsonW.to_string();
 
   JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
@@ -286,8 +288,41 @@
 wifi_error wifi_nan_config_request_mock(transaction_id id,
                                         wifi_interface_handle iface,
                                         NanConfigRequest* msg) {
+  JNIHelper helper(mock_mVM);
+
   ALOGD("wifi_nan_config_request_mock");
-  return WIFI_ERROR_UNINITIALIZED;
+  HalMockJsonWriter jsonW;
+  jsonW.put_int("config_sid_beacon", msg->config_sid_beacon);
+  jsonW.put_int("sid_beacon", msg->sid_beacon);
+  jsonW.put_int("config_rssi_proximity", msg->config_rssi_proximity);
+  jsonW.put_int("rssi_proximity", msg->rssi_proximity);
+  jsonW.put_int("config_master_pref", msg->config_master_pref);
+  jsonW.put_int("master_pref", msg->master_pref);
+  jsonW.put_int("config_5g_rssi_close_proximity", msg->config_5g_rssi_close_proximity);
+  jsonW.put_int("rssi_close_proximity_5g_val", msg->rssi_close_proximity_5g_val);
+  jsonW.put_int("config_rssi_window_size", msg->config_rssi_window_size);
+  jsonW.put_int("rssi_window_size_val", msg->rssi_window_size_val);
+  jsonW.put_int("config_cluster_attribute_val", msg->config_cluster_attribute_val);
+  jsonW.put_int("config_scan_params", msg->config_scan_params);
+  // TODO: NanSocialChannelScanParams scan_params_val
+  jsonW.put_int("config_random_factor_force", msg->config_random_factor_force);
+  jsonW.put_int("random_factor_force_val", msg->random_factor_force_val);
+  jsonW.put_int("config_hop_count_force", msg->config_hop_count_force);
+  jsonW.put_int("hop_count_force_val", msg->hop_count_force_val);
+  jsonW.put_int("config_conn_capability", msg->config_conn_capability);
+  // TODO: NanTransmitPostConnectivityCapability conn_capability_val
+  jsonW.put_int("num_config_discovery_attr", msg->num_config_discovery_attr);
+  // TODO: NanTransmitPostDiscovery discovery_attr_val[NAN_MAX_POSTDISCOVERY_LEN]
+  jsonW.put_int("config_fam", msg->config_fam);
+  // TODO: NanFurtherAvailabilityMap fam_val
+  std::string str = jsonW.to_string();
+
+  JNIObject < jstring > json_write_string = helper.newStringUTF(str.c_str());
+
+  helper.callMethod(mock_mObj, "configHalMockNative", "(SLjava/lang/String;)V",
+                    (short) id, json_write_string.get());
+
+  return WIFI_SUCCESS;
 }
 
 wifi_error wifi_nan_tca_request_mock(transaction_id id,
@@ -374,6 +409,8 @@
         "body.nan_capabilities.max_ndp_sessions", &error);
     msg.body.nan_capabilities.max_app_info_len = jsonR.get_int(
         "body.nan_capabilities.max_app_info_len", &error);
+    msg.body.nan_capabilities.max_queued_transmit_followup_msgs = jsonR.get_int(
+        "body.nan_capabilities.max_queued_transmit_followup_msgs", &error);
   }
 
   if (error) {
@@ -541,10 +578,35 @@
   mCallbackHandlers.EventDisabled(&msg);
 }
 
+extern "C" void Java_com_android_server_wifi_nan_WifiNanHalMock_callTransmitFollowup(
+    JNIEnv* env, jclass clazz, jstring json_args_jstring) {
+  ScopedUtfChars chars(env, json_args_jstring);
+  HalMockJsonReader jsonR(chars.c_str());
+  bool error = false;
+
+  ALOGD("Java_com_android_server_wifi_nan_WifiNanHalMock_callTransmitFollowup: '%s'",
+        chars.c_str());
+
+  NanTransmitFollowupInd msg;
+  msg.id = (transaction_id) jsonR.get_int("id", &error);
+  msg.reason = (NanStatusType) jsonR.get_int("reason", &error);
+
+  if (error) {
+    ALOGE("Java_com_android_server_wifi_nan_WifiNanHalMock_callTransmitFollowup: "
+          "error parsing args");
+    return;
+  }
+
+  mCallbackHandlers.EventTransmitFollowup(&msg);
+}
+
 // TODO: Not currently used: add as needed
-//void (*EventUnMatch) (NanUnmatchInd* event);
+//void (*EventMatchExpired) (NanUnmatchInd* event);
 //void (*EventTca) (NanTCAInd* event);
 //void (*EventBeaconSdfPayload) (NanBeaconSdfPayloadInd* event);
+//void (*EventDataRequest)(NanDataPathRequestInd* event);
+//void (*EventDataConfirm)(NanDataPathConfirmInd* event);
+//void (*EventDataEnd)(NanDataPathEndInd* event);
 
 int init_wifi_nan_hal_func_table_mock(wifi_hal_fn *fn) {
   if (fn == NULL) {
diff --git a/tests/wifitests/src/android/net/wifi/WifiEnterpriseConfigTest.java b/tests/wifitests/src/android/net/wifi/WifiEnterpriseConfigTest.java
index 75480b5..814594d 100644
--- a/tests/wifitests/src/android/net/wifi/WifiEnterpriseConfigTest.java
+++ b/tests/wifitests/src/android/net/wifi/WifiEnterpriseConfigTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
 
 import android.net.wifi.WifiEnterpriseConfig.Eap;
 import android.net.wifi.WifiEnterpriseConfig.Phase2;
@@ -50,6 +51,22 @@
     }
 
     @Test
+    public void testGetEmptyCaCertificate() {
+        // A newly-constructed WifiEnterpriseConfig object should have no CA certificate.
+        assertNull(mEnterpriseConfig.getCaCertificate());
+        assertNull(mEnterpriseConfig.getCaCertificates());
+        // Setting CA certificate to null explicitly.
+        mEnterpriseConfig.setCaCertificate(null);
+        assertNull(mEnterpriseConfig.getCaCertificate());
+        // Setting CA certificate to null using setCaCertificates().
+        mEnterpriseConfig.setCaCertificates(null);
+        assertNull(mEnterpriseConfig.getCaCertificates());
+        // Setting CA certificate to zero-length array.
+        mEnterpriseConfig.setCaCertificates(new X509Certificate[0]);
+        assertNull(mEnterpriseConfig.getCaCertificates());
+    }
+
+    @Test
     public void testSetGetSingleCaCertificate() {
         X509Certificate cert0 = FakeKeys.CA_CERT0;
         mEnterpriseConfig.setCaCertificate(cert0);
diff --git a/tests/wifitests/src/android/net/wifi/WifiScannerTest.java b/tests/wifitests/src/android/net/wifi/WifiScannerTest.java
index 5034c2a..a829eb9 100644
--- a/tests/wifitests/src/android/net/wifi/WifiScannerTest.java
+++ b/tests/wifitests/src/android/net/wifi/WifiScannerTest.java
@@ -30,10 +30,10 @@
 import android.net.wifi.WifiScanner.BssidListener;
 import android.os.Handler;
 import android.os.Message;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.server.wifi.BidirectionalAsyncChannelServer;
-import com.android.server.wifi.MockLooper;
+import com.android.internal.util.test.BidirectionalAsyncChannelServer;
 
 import org.junit.After;
 import org.junit.Before;
@@ -55,7 +55,7 @@
     private BssidListener mBssidListener;
 
     private WifiScanner mWifiScanner;
-    private MockLooper mLooper;
+    private TestLooper mLooper;
     private Handler mHandler;
 
     /**
@@ -64,7 +64,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
         mHandler = mock(Handler.class);
         BidirectionalAsyncChannelServer server = new BidirectionalAsyncChannelServer(
                 mContext, mLooper.getLooper(), mHandler);
diff --git a/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannel.java b/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannel.java
deleted file mode 100644
index 75c0f87..0000000
--- a/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannel.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wifi;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.util.Log;
-
-import com.android.internal.util.AsyncChannel;
-
-
-/**
- * Provides an AsyncChannel interface that implements the connection initiating half of a
- * bidirectional channel as described in {@link com.android.internal.util.AsyncChannel}.
- */
-public class BidirectionalAsyncChannel {
-    private static final String TAG = "BidirectionalAsyncChannel";
-
-    private AsyncChannel mChannel;
-    public enum ChannelState { DISCONNECTED, HALF_CONNECTED, CONNECTED, FAILURE };
-    private ChannelState mState = ChannelState.DISCONNECTED;
-
-    public void assertConnected() {
-        assertEquals("AsyncChannel was not fully connected", ChannelState.CONNECTED, mState);
-    }
-
-    public void connect(final Looper looper, final Messenger messenger,
-            final Handler incomingMessageHandler) {
-        assertEquals("AsyncChannel must be disconnected to connect",
-                ChannelState.DISCONNECTED, mState);
-        mChannel = new AsyncChannel();
-        Handler rawMessageHandler = new Handler(looper) {
-                @Override
-                public void handleMessage(Message msg) {
-                    switch (msg.what) {
-                    case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                            Log.d(TAG, "Successfully half connected " + this);
-                            mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
-                            mState = ChannelState.HALF_CONNECTED;
-                        } else {
-                            Log.d(TAG, "Failed to connect channel " + this);
-                            mState = ChannelState.FAILURE;
-                            mChannel = null;
-                        }
-                        break;
-                    case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
-                        mState = ChannelState.CONNECTED;
-                        Log.d(TAG, "Channel fully connected" + this);
-                        break;
-                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                        mState = ChannelState.DISCONNECTED;
-                        mChannel = null;
-                        Log.d(TAG, "Channel disconnected" + this);
-                        break;
-                    default:
-                        incomingMessageHandler.handleMessage(msg);
-                        break;
-                    }
-                }
-            };
-        mChannel.connect(null, rawMessageHandler, messenger);
-    }
-
-    public void disconnect() {
-        assertEquals("AsyncChannel must be connected to disconnect",
-                ChannelState.CONNECTED, mState);
-        mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_DISCONNECT);
-        mState = ChannelState.DISCONNECTED;
-        mChannel = null;
-    }
-
-    public void sendMessage(Message msg) {
-        assertEquals("AsyncChannel must be connected to send messages",
-                ChannelState.CONNECTED, mState);
-        mChannel.sendMessage(msg);
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannelServer.java b/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannelServer.java
deleted file mode 100644
index 6cc0e90..0000000
--- a/tests/wifitests/src/com/android/server/wifi/BidirectionalAsyncChannelServer.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.util.Log;
-
-import com.android.internal.util.AsyncChannel;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Provides an interface for the server side implementation of a bidirectional channel as described
- * in {@link com.android.internal.util.AsyncChannel}.
- */
-public class BidirectionalAsyncChannelServer {
-
-    private static final String TAG = "BidirectionalAsyncChannelServer";
-
-    // Keeps track of incoming clients, which are identifiable by their messengers.
-    private final Map<Messenger, AsyncChannel> mClients = new HashMap<>();
-
-    private Messenger mMessenger;
-
-    public BidirectionalAsyncChannelServer(final Context context, final Looper looper,
-            final Handler messageHandler) {
-        Handler handler = new Handler(looper) {
-            @Override
-            public void handleMessage(Message msg) {
-                AsyncChannel channel = mClients.get(msg.replyTo);
-                switch (msg.what) {
-                    case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
-                        if (channel != null) {
-                            Log.d(TAG, "duplicate client connection: " + msg.sendingUid);
-                            channel.replyToMessage(msg,
-                                    AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
-                                    AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
-                        } else {
-                            channel = new AsyncChannel();
-                            mClients.put(msg.replyTo, channel);
-                            channel.connected(context, this, msg.replyTo);
-                            channel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
-                                    AsyncChannel.STATUS_SUCCESSFUL);
-                        }
-                        break;
-                    case AsyncChannel.CMD_CHANNEL_DISCONNECT:
-                        channel.disconnect();
-                        break;
-
-                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                        mClients.remove(msg.replyTo);
-                        break;
-
-                    default:
-                        messageHandler.handleMessage(msg);
-                        break;
-                }
-            }
-        };
-        mMessenger = new Messenger(handler);
-    }
-
-    public Messenger getMessenger() {
-        return mMessenger;
-    }
-
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java b/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
index 328feaf..c1e333b 100644
--- a/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/ConfigurationMapTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.when;
 
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.pm.UserInfo;
 import android.net.wifi.WifiConfiguration;
 import android.os.UserHandle;
@@ -28,8 +29,6 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.SparseArray;
 
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
diff --git a/tests/wifitests/src/com/android/server/wifi/HalMockUtils.java b/tests/wifitests/src/com/android/server/wifi/HalMockUtils.java
index 6e485f6..3235ff7 100644
--- a/tests/wifitests/src/com/android/server/wifi/HalMockUtils.java
+++ b/tests/wifitests/src/com/android/server/wifi/HalMockUtils.java
@@ -19,12 +19,11 @@
 import android.os.Bundle;
 import android.util.Log;
 
-import com.android.server.wifi.WifiNative;
-
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.util.Iterator;
 
@@ -43,14 +42,24 @@
 
     public static void initHalMockLibrary() throws Exception {
         /*
-         * Setting the Wi-Fi HAL handle and interface (array) to dummy
-         * values. Required to fake the init checking code to think that
-         * the HAL actually started.
-         *
-         * Note that values are not important since they are only used by
-         * the real HAL - which is mocked-out in this use-case.
+         * Setting the Wi-Fi HAL handle and interface (array) to dummy values.
+         * Need to install a default WifiNative (since another mock may have
+         * installed something else). Required to fake the init checking code to
+         * think that the HAL actually started. Note that values are not
+         * important since they are only used by the real HAL - which is
+         * mocked-out in this use-case.
          */
-        Field field = WifiNative.class.getDeclaredField("sWifiHalHandle");
+
+        Constructor<WifiNative> ctr = WifiNative.class.getDeclaredConstructor(String.class,
+                                                                              Boolean.TYPE);
+        ctr.setAccessible(true);
+        WifiNative wifiNativeInstance = ctr.newInstance("wlan0", true);
+
+        Field field = WifiNative.class.getDeclaredField("wlanNativeInterface");
+        field.setAccessible(true);
+        field.set(null, wifiNativeInstance);
+
+        field = WifiNative.class.getDeclaredField("sWifiHalHandle");
         field.setAccessible(true);
         long currentWifiHalHandle = field.getLong(null);
         if (DBG) Log.d(TAG, "currentWifiHalHandle=" + currentWifiHalHandle);
diff --git a/tests/wifitests/src/com/android/server/wifi/MockAlarmManager.java b/tests/wifitests/src/com/android/server/wifi/MockAlarmManager.java
deleted file mode 100644
index 02af281..0000000
--- a/tests/wifitests/src/com/android/server/wifi/MockAlarmManager.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.anyLong;
-import static org.mockito.Mockito.anyString;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-
-import android.app.AlarmManager;
-import android.os.Handler;
-
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Creates an AlarmManager whose alarm dispatch can be controlled
- * Currently only supports alarm listeners
- *
- * Alarm listeners will be dispatched to the handler provided or will
- * be dispatched imediatly if they would have been sent to the main
- * looper (handler was null).
- */
-public class MockAlarmManager {
-    private final AlarmManager mAlarmManager;
-    private final List<PendingAlarm> mPendingAlarms;
-
-    public MockAlarmManager() throws Exception {
-        mPendingAlarms = new ArrayList<>();
-
-        mAlarmManager = mock(AlarmManager.class);
-        doAnswer(new SetListenerAnswer()).when(mAlarmManager).set(anyInt(), anyLong(), anyString(),
-                any(AlarmManager.OnAlarmListener.class), any(Handler.class));
-        doAnswer(new SetListenerAnswer()).when(mAlarmManager).setExact(anyInt(), anyLong(),
-                anyString(), any(AlarmManager.OnAlarmListener.class), any(Handler.class));
-        doAnswer(new CancelListenerAnswer())
-                .when(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
-    }
-
-    public AlarmManager getAlarmManager() {
-        return mAlarmManager;
-    }
-
-    /**
-     * Dispatch a pending alarm with the given tag
-     * @return if any alarm was dispatched
-     */
-    public boolean dispatch(String tag) {
-        for (int i = 0; i < mPendingAlarms.size(); ++i) {
-            PendingAlarm alarm = mPendingAlarms.get(i);
-            if (Objects.equals(tag, alarm.getTag())) {
-                mPendingAlarms.remove(i);
-                alarm.dispatch();
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @return if an alarm with the given tag is pending
-     */
-    public boolean isPending(String tag) {
-        for (int i = 0; i < mPendingAlarms.size(); ++i) {
-            PendingAlarm alarm = mPendingAlarms.get(i);
-            if (Objects.equals(tag, alarm.getTag())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @return trigger time of an pending alarm with the given tag
-     *         -1 if no pending alram with the given tag
-     */
-    public long getTriggerTimeMillis(String tag) {
-        for (int i = 0; i < mPendingAlarms.size(); ++i) {
-            PendingAlarm alarm = mPendingAlarms.get(i);
-            if (Objects.equals(tag, alarm.getTag())) {
-                return alarm.getTriggerTimeMillis();
-            }
-        }
-        return -1;
-    }
-
-    private static class PendingAlarm {
-        private final int mType;
-        private final long mTriggerAtMillis;
-        private final String mTag;
-        private final Runnable mCallback;
-
-        public PendingAlarm(int type, long triggerAtMillis, String tag, Runnable callback) {
-            mType = type;
-            mTriggerAtMillis = triggerAtMillis;
-            mTag = tag;
-            mCallback = callback;
-        }
-
-        public void dispatch() {
-            if (mCallback != null) {
-                mCallback.run();
-            }
-        }
-
-        public Runnable getCallback() {
-            return mCallback;
-        }
-
-        public String getTag() {
-            return mTag;
-        }
-
-        public long getTriggerTimeMillis() {
-            return mTriggerAtMillis;
-        }
-    }
-
-    private class SetListenerAnswer extends AnswerWithArguments {
-        public void answer(int type, long triggerAtMillis, String tag,
-                AlarmManager.OnAlarmListener listener, Handler handler) {
-            mPendingAlarms.add(new PendingAlarm(type, triggerAtMillis, tag,
-                            new AlarmListenerRunnable(listener, handler)));
-        }
-    }
-
-    private class CancelListenerAnswer extends AnswerWithArguments {
-        public void answer(AlarmManager.OnAlarmListener listener) {
-            Iterator<PendingAlarm> alarmItr = mPendingAlarms.iterator();
-            while (alarmItr.hasNext()) {
-                PendingAlarm alarm = alarmItr.next();
-                if (alarm.getCallback() instanceof AlarmListenerRunnable) {
-                    AlarmListenerRunnable alarmCallback =
-                            (AlarmListenerRunnable) alarm.getCallback();
-                    if (alarmCallback.getListener() == listener) {
-                        alarmItr.remove();
-                    }
-                }
-            }
-        }
-    }
-
-    private static class AlarmListenerRunnable implements Runnable {
-        private final AlarmManager.OnAlarmListener mListener;
-        private final Handler mHandler;
-        public AlarmListenerRunnable(AlarmManager.OnAlarmListener listener, Handler handler) {
-            mListener = listener;
-            mHandler = handler;
-        }
-
-        public Handler getHandler() {
-            return mHandler;
-        }
-
-        public AlarmManager.OnAlarmListener getListener() {
-            return mListener;
-        }
-
-        public void run() {
-            if (mHandler != null) {
-                mHandler.post(new Runnable() {
-                        public void run() {
-                            mListener.onAlarm();
-                        }
-                    });
-            } else { // normally gets dispatched in main looper
-                mListener.onAlarm();
-            }
-        }
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/MockAnswerUtil.java b/tests/wifitests/src/com/android/server/wifi/MockAnswerUtil.java
deleted file mode 100644
index 1a3dfa1..0000000
--- a/tests/wifitests/src/com/android/server/wifi/MockAnswerUtil.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-/**
- * Utilities for creating Answers for mock objects
- */
-public class MockAnswerUtil {
-
-    /**
-     * Answer that calls the method in the Answer called "answer" that matches the type signature of
-     * the method being answered. An error will be thrown at runtime if the signature does not match
-     * exactly.
-     */
-    public static class AnswerWithArguments implements Answer<Object> {
-        @Override
-        public final Object answer(InvocationOnMock invocation) throws Throwable {
-            Method method = invocation.getMethod();
-            try {
-                Method implementation = getClass().getMethod("answer", method.getParameterTypes());
-                if (!implementation.getReturnType().equals(method.getReturnType())) {
-                    throw new RuntimeException("Found answer method does not have expected return "
-                            + "type. Expected: " + method.getReturnType() + ", got "
-                            + implementation.getReturnType());
-                }
-                Object[] args = invocation.getArguments();
-                try {
-                    return implementation.invoke(this, args);
-                } catch (IllegalAccessException e) {
-                    throw new RuntimeException("Error invoking answer method", e);
-                } catch (InvocationTargetException e) {
-                    throw e.getCause();
-                }
-            } catch (NoSuchMethodException e) {
-                throw new RuntimeException("Could not find answer method with the expected args "
-                        + Arrays.toString(method.getParameterTypes()), e);
-            }
-        }
-    }
-
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/MockLooper.java b/tests/wifitests/src/com/android/server/wifi/MockLooper.java
deleted file mode 100644
index 34d80a4..0000000
--- a/tests/wifitests/src/com/android/server/wifi/MockLooper.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import static org.junit.Assert.assertTrue;
-
-import android.os.Looper;
-import android.os.Message;
-import android.os.MessageQueue;
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * Creates a looper whose message queue can be manipulated
- * This allows testing code that uses a looper to dispatch messages in a deterministic manner
- * Creating a MockLooper will also install it as the looper for the current thread
- */
-public class MockLooper {
-    protected final Looper mLooper;
-
-    private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
-    private static final Field THREAD_LOCAL_LOOPER_FIELD;
-    private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
-    private static final Field MESSAGE_NEXT_FIELD;
-    private static final Field MESSAGE_WHEN_FIELD;
-    private static final Method MESSAGE_MARK_IN_USE_METHOD;
-    private static final String TAG = "MockLooper";
-
-    private AutoDispatchThread mAutoDispatchThread;
-
-    static {
-        try {
-            LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
-            LOOPER_CONSTRUCTOR.setAccessible(true);
-            THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
-            THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
-            MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
-            MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
-            MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
-            MESSAGE_NEXT_FIELD.setAccessible(true);
-            MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
-            MESSAGE_WHEN_FIELD.setAccessible(true);
-            MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
-            MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
-        } catch (NoSuchFieldException | NoSuchMethodException e) {
-            throw new RuntimeException("Failed to initialize MockLooper", e);
-        }
-    }
-
-
-    public MockLooper() {
-        try {
-            mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
-
-            ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD
-                    .get(null);
-            threadLocalLooper.set(mLooper);
-        } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
-            throw new RuntimeException("Reflection error constructing or accessing looper", e);
-        }
-    }
-
-    public Looper getLooper() {
-        return mLooper;
-    }
-
-    private Message getMessageLinkedList() {
-        try {
-            MessageQueue queue = mLooper.getQueue();
-            return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException("Access failed in MockLooper: get - MessageQueue.mMessages",
-                    e);
-        }
-    }
-
-    public void moveTimeForward(long milliSeconds) {
-        try {
-            Message msg = getMessageLinkedList();
-            while (msg != null) {
-                long updatedWhen = msg.getWhen() - milliSeconds;
-                if (updatedWhen < 0) {
-                    updatedWhen = 0;
-                }
-                MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
-                msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
-            }
-        } catch (IllegalAccessException e) {
-            throw new RuntimeException("Access failed in MockLooper: set - Message.when", e);
-        }
-    }
-
-    private Message messageQueueNext() {
-        try {
-            long now = SystemClock.uptimeMillis();
-
-            Message prevMsg = null;
-            Message msg = getMessageLinkedList();
-            if (msg != null && msg.getTarget() == null) {
-                // Stalled by a barrier. Find the next asynchronous message in
-                // the queue.
-                do {
-                    prevMsg = msg;
-                    msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
-                } while (msg != null && !msg.isAsynchronous());
-            }
-            if (msg != null) {
-                if (now >= msg.getWhen()) {
-                    // Got a message.
-                    if (prevMsg != null) {
-                        MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg));
-                    } else {
-                        MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(),
-                                MESSAGE_NEXT_FIELD.get(msg));
-                    }
-                    MESSAGE_NEXT_FIELD.set(msg, null);
-                    MESSAGE_MARK_IN_USE_METHOD.invoke(msg);
-                    return msg;
-                }
-            }
-        } catch (IllegalAccessException | InvocationTargetException e) {
-            throw new RuntimeException("Access failed in MockLooper", e);
-        }
-
-        return null;
-    }
-
-    /**
-     * @return true if there are pending messages in the message queue
-     */
-    public synchronized boolean isIdle() {
-        Message messageList = getMessageLinkedList();
-
-        return messageList != null && SystemClock.uptimeMillis() >= messageList.getWhen();
-    }
-
-    /**
-     * @return the next message in the Looper's message queue or null if there is none
-     */
-    public synchronized Message nextMessage() {
-        if (isIdle()) {
-            return messageQueueNext();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Dispatch the next message in the queue
-     * Asserts that there is a message in the queue
-     */
-    public synchronized void dispatchNext() {
-        assertTrue(isIdle());
-        Message msg = messageQueueNext();
-        if (msg == null) {
-            return;
-        }
-        msg.getTarget().dispatchMessage(msg);
-    }
-
-    /**
-     * Dispatch all messages currently in the queue
-     * Will not fail if there are no messages pending
-     * @return the number of messages dispatched
-     */
-    public synchronized int dispatchAll() {
-        int count = 0;
-        while (isIdle()) {
-            dispatchNext();
-            ++count;
-        }
-        return count;
-    }
-
-    /**
-     * Thread used to dispatch messages when the main thread is blocked waiting for a response.
-     */
-    private class AutoDispatchThread extends Thread {
-        private static final int MAX_LOOPS = 100;
-        private static final int LOOP_SLEEP_TIME_MS = 10;
-
-        private RuntimeException mAutoDispatchException = null;
-
-        /**
-         * Run method for the auto dispatch thread.
-         * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops.
-         * The thread continues looping and attempting to dispatch all messages until at
-         * least one message has been dispatched.
-         */
-        @Override
-        public void run() {
-            int dispatchCount = 0;
-            for (int i = 0; i < MAX_LOOPS; i++) {
-                try {
-                    dispatchCount = dispatchAll();
-                } catch (RuntimeException e) {
-                    mAutoDispatchException = e;
-                }
-                Log.d(TAG, "dispatched " + dispatchCount + " messages");
-                if (dispatchCount > 0) {
-                    return;
-                }
-                try {
-                    Thread.sleep(LOOP_SLEEP_TIME_MS);
-                } catch (InterruptedException e) {
-                    mAutoDispatchException = new IllegalStateException(
-                            "stopAutoDispatch called before any messages were dispatched.");
-                    return;
-                }
-            }
-            Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
-            mAutoDispatchException = new IllegalStateException(
-                    "MockLooper did not dispatch any messages before exiting.");
-        }
-
-        /**
-         * Method allowing the MockLooper to pass any exceptions thrown by the thread to be passed
-         * to the main thread.
-         *
-         * @return RuntimeException Exception created by stopping without dispatching a message
-         */
-        public RuntimeException getException() {
-            return mAutoDispatchException;
-        }
-    }
-
-    /**
-     * Create and start a new AutoDispatchThread if one is not already running.
-     */
-    public void startAutoDispatch() {
-        if (mAutoDispatchThread != null) {
-            throw new IllegalStateException(
-                    "startAutoDispatch called with the AutoDispatchThread already running.");
-        }
-        mAutoDispatchThread = new AutoDispatchThread();
-        mAutoDispatchThread.start();
-    }
-
-    /**
-     * If an AutoDispatchThread is currently running, stop and clean up.
-     */
-    public void stopAutoDispatch() {
-        if (mAutoDispatchThread != null) {
-            if (mAutoDispatchThread.isAlive()) {
-                mAutoDispatchThread.interrupt();
-            }
-            try {
-                mAutoDispatchThread.join();
-            } catch (InterruptedException e) {
-                // Catch exception from join.
-            }
-
-            RuntimeException e = mAutoDispatchThread.getException();
-            mAutoDispatchThread = null;
-            if (e != null) {
-                throw e;
-            }
-        } else {
-            // stopAutoDispatch was called when startAutoDispatch has not created a new thread.
-            throw new IllegalStateException(
-                    "stopAutoDispatch called without startAutoDispatch.");
-        }
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/MockLooperTest.java b/tests/wifitests/src/com/android/server/wifi/MockLooperTest.java
deleted file mode 100644
index 74a73d6..0000000
--- a/tests/wifitests/src/com/android/server/wifi/MockLooperTest.java
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi;
-
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ErrorCollector;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Test MockLooperAbstractTime which provides control over "time". Note that
- * real-time is being used as well. Therefore small time increments are NOT
- * reliable. All tests are in "K" units (i.e. *1000).
- */
-
-@SmallTest
-public class MockLooperTest {
-    private MockLooper mMockLooper;
-    private Handler mHandler;
-    private Handler mHandlerSpy;
-
-    @Rule
-    public ErrorCollector collector = new ErrorCollector();
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mMockLooper = new MockLooper();
-        mHandler = new Handler(mMockLooper.getLooper());
-        mHandlerSpy = spy(mHandler);
-    }
-
-    /**
-     * Basic test with no time stamps: dispatch 4 messages, check that all 4
-     * delivered (in correct order).
-     */
-    @Test
-    public void testNoTimeMovement() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageC));
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("3: messageB", messageB, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("4: messageC", messageC, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test message sequence: A, B, C@5K, A@10K. Don't move time.
-     * <p>
-     * Expected: only get A, B
-     */
-    @Test
-    public void testDelayedDispatchNoTimeMove() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test message sequence: A, B, C@5K, A@10K, Advance time by 5K.
-     * <p>
-     * Expected: only get A, B, C
-     */
-    @Test
-    public void testDelayedDispatchAdvanceTimeOnce() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
-        mMockLooper.moveTimeForward(5000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test message sequence: A, B, C@5K, Advance time by 4K, A@1K, B@2K Advance
-     * time by 1K.
-     * <p>
-     * Expected: get A, B, C, A
-     */
-    @Test
-    public void testDelayedDispatchAdvanceTimeTwice() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
-        mMockLooper.moveTimeForward(4000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 1000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
-        mMockLooper.moveTimeForward(1000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("4: messageA", messageA, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test message sequence: A, B, C@5K, Advance time by 4K, A@5K, B@2K Advance
-     * time by 3K.
-     * <p>
-     * Expected: get A, B, C, B
-     */
-    @Test
-    public void testDelayedDispatchReverseOrder() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
-        mMockLooper.moveTimeForward(4000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
-        mMockLooper.moveTimeForward(3000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test message sequence: A, B, C@5K, Advance time by 4K, dispatch all,
-     * A@5K, B@2K Advance time by 3K, dispatch all.
-     * <p>
-     * Expected: get A, B after first dispatch; then C, B after second dispatch
-     */
-    @Test
-    public void testDelayedDispatchAllMultipleTimes() {
-        final int messageA = 1;
-        final int messageB = 2;
-        final int messageC = 3;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
-        mMockLooper.moveTimeForward(4000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
-
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
-        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
-        mMockLooper.moveTimeForward(3000);
-        mMockLooper.dispatchAll();
-
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
-        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
-        collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
-
-        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
-    }
-
-    /**
-     * Test AutoDispatch for a single message.
-     * This test would ideally use the Channel sendMessageSynchronously.  At this time, the setup to
-     * get a working test channel is cumbersome.  Until this is fixed, we substitute with a
-     * sendMessage followed by a blocking call.  The main test thread blocks until the test handler
-     * receives the test message (messageA) and sets a boolean true.  Once the boolean is true, the
-     * main thread will exit the busy wait loop, stop autoDispatch and check the assert.
-     *
-     * Enable AutoDispatch, add message, block on message being handled and stop AutoDispatch.
-     * <p>
-     * Expected: handleMessage is called for messageA and stopAutoDispatch is called.
-     */
-    @Test
-    public void testAutoDispatchWithSingleMessage() {
-        final int mLoopSleepTimeMs = 5;
-
-        final int messageA = 1;
-
-        MockLooper mockLooper = new MockLooper();
-        class TestHandler extends Handler {
-            public volatile boolean handledMessage = false;
-            TestHandler(Looper looper) {
-                super(looper);
-            }
-
-            @Override
-            public void handleMessage(Message msg) {
-                if (msg.what == messageA) {
-                    handledMessage = true;
-                }
-            }
-        }
-
-        TestHandler testHandler = new TestHandler(mockLooper.getLooper());
-        mockLooper.startAutoDispatch();
-        testHandler.sendMessage(testHandler.obtainMessage(messageA));
-        while (!testHandler.handledMessage) {
-            // Block until message is handled
-            try {
-                Thread.sleep(mLoopSleepTimeMs);
-            } catch (InterruptedException e) {
-                // Interrupted while sleeping.
-            }
-        }
-        mockLooper.stopAutoDispatch();
-        assertTrue("TestHandler should have received messageA", testHandler.handledMessage);
-    }
-
-    /**
-     * Test starting AutoDispatch while already running throws IllegalStateException
-     * Enable AutoDispatch two times in a row.
-     * <p>
-     * Expected: catch IllegalStateException on second call.
-     */
-    @Test(expected = IllegalStateException.class)
-    public void testRepeatedStartAutoDispatchThrowsException() {
-        mMockLooper.startAutoDispatch();
-        mMockLooper.startAutoDispatch();
-    }
-
-    /**
-     * Test stopping AutoDispatch without previously starting throws IllegalStateException
-     * Stop AutoDispatch
-     * <p>
-     * Expected: catch IllegalStateException on second call.
-     */
-    @Test(expected = IllegalStateException.class)
-    public void testStopAutoDispatchWithoutStartThrowsException() {
-        mMockLooper.stopAutoDispatch();
-    }
-
-    /**
-     * Test AutoDispatch exits and does not dispatch a later message.
-     * Start and stop AutoDispatch then add a message.
-     * <p>
-     * Expected: After AutoDispatch is stopped, dispatchAll will return 1.
-     */
-    @Test
-    public void testAutoDispatchStopsCleanlyWithoutDispatchingAMessage() {
-        final int messageA = 1;
-
-        InOrder inOrder = inOrder(mHandlerSpy);
-        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
-        mMockLooper.startAutoDispatch();
-        try {
-            mMockLooper.stopAutoDispatch();
-        } catch (IllegalStateException e) {
-            //  Stopping without a dispatch will throw an exception.
-        }
-
-        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
-        assertEquals("One message should be dispatched", 1, mMockLooper.dispatchAll());
-    }
-
-    /**
-     * Test AutoDispatch throws an exception when no messages are dispatched.
-     * Start and stop AutoDispatch
-     * <p>
-     * Expected: Exception is thrown with the stopAutoDispatch call.
-     */
-    @Test(expected = IllegalStateException.class)
-    public void testAutoDispatchThrowsExceptionWhenNoMessagesDispatched() {
-        mMockLooper.startAutoDispatch();
-        mMockLooper.stopAutoDispatch();
-    }
-}
diff --git a/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java b/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java
index 8b68a11..6786504 100644
--- a/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java
+++ b/tests/wifitests/src/com/android/server/wifi/MockWifiMonitor.java
@@ -23,12 +23,11 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.os.Handler;
 import android.os.Message;
 import android.util.SparseArray;
 
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
-
 import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.util.Map;
diff --git a/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java b/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
index 177705c..157164a 100644
--- a/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/RttServiceTest.java
@@ -36,8 +36,11 @@
 import android.net.wifi.WifiManager;
 import android.os.Handler;
 import android.os.Message;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.internal.util.test.BidirectionalAsyncChannel;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -60,7 +63,7 @@
     Context mContext;
     @Mock
     WifiNative mWifiNative;
-    MockLooper mLooper;
+    TestLooper mLooper;
 
     RttService.RttServiceImpl mRttServiceImpl;
     ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor = ArgumentCaptor
@@ -70,7 +73,7 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         TestUtil.installWlanWifiNative(mWifiNative);
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
         mRttServiceImpl = new RttService.RttServiceImpl(mContext, mLooper.getLooper());
         mRttServiceImpl.startService();
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
index c091517..cd63d3f 100644
--- a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -34,6 +34,7 @@
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.os.INetworkManagementService;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.Before;
@@ -61,7 +62,7 @@
     private final ArrayList<Integer> mAllowed2GChannels =
             new ArrayList<Integer>(Arrays.asList(ALLOWED_2G_CHANNELS));
 
-    MockLooper mLooper;
+    TestLooper mLooper;
     @Mock Context mContext;
     @Mock WifiNative mWifiNative;
     @Mock INetworkManagementService mNmService;
@@ -81,7 +82,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
 
         when(mWifiNative.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
         when(mNmService.getInterfaceConfig(TEST_INTERFACE_NAME))
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
index a585000..d41a0b0 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.net.wifi.FakeKeys;
@@ -45,7 +46,6 @@
 import android.util.SparseArray;
 
 import com.android.server.net.DelayedDiskWrite;
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
 import com.android.server.wifi.hotspot2.omadm.PasspointManagementObjectManager;
 import com.android.server.wifi.hotspot2.pps.Credential;
 import com.android.server.wifi.hotspot2.pps.HomeSP;
@@ -420,23 +420,15 @@
                         assertEquals(WifiConfiguration.Status.ENABLED, config2.status);
                     }
                 } else {
-                    // If the network configuration is visible to the current user, verify that it
-                    // was enabled and all other network configurations visible to the user were
-                    // disabled.
+                    // If the network configuration is visible to the current user, verify that
+                    // a connection attempt was made to it. This does not modify the status of
+                    // other networks.
                     assertTrue(success);
                     verify(mWifiNative).selectNetwork(config.networkId);
                     verify(mWifiNative, never()).selectNetwork(intThat(not(config.networkId)));
                     verify(mWifiNative, never()).enableNetwork(config.networkId);
                     verify(mWifiNative, never()).enableNetwork(intThat(not(config.networkId)));
-                    for (WifiConfiguration config2 : mConfiguredNetworks.valuesForAllUsers()) {
-                        if (WifiConfigurationUtil.isVisibleToAnyProfile(config2,
-                                USER_PROFILES.get(userId))
-                                && config2.networkId != config.networkId) {
-                            assertEquals(WifiConfiguration.Status.DISABLED, config2.status);
-                        } else {
-                            assertEquals(WifiConfiguration.Status.ENABLED, config2.status);
-                        }
-                    }
+                    assertEquals(WifiConfiguration.Status.ENABLED, config.status);
                 }
             }
         }
@@ -741,16 +733,20 @@
         for (WifiConfiguration config : newConfigs) {
             if (oldUserOnlyConfigs.contains(config)) {
                 verify(mWifiNative).disableNetwork(config.networkId);
-                assertEquals(WifiConfiguration.Status.DISABLED, config.status);
+                assertNetworkStatus(
+                        config,
+                        WifiConfiguration.NetworkSelectionStatus.DISABLED_DUE_TO_USER_SWITCH);
             } else {
                 verify(mWifiNative, never()).disableNetwork(config.networkId);
                 if (neitherUserConfigs.contains(config)) {
-                    assertEquals(WifiConfiguration.Status.DISABLED, config.status);
+                    assertNetworkStatus(
+                            config,
+                            WifiConfiguration.NetworkSelectionStatus.DISABLED_DUE_TO_USER_SWITCH);
                 } else {
-                    // Only enabled in networkSelection.
-                    assertTrue(config.getNetworkSelectionStatus().isNetworkEnabled());
+                    assertNetworkStatus(
+                            config,
+                            WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
                 }
-
             }
         }
     }
@@ -1591,7 +1587,6 @@
                 eapConfigSame.enterpriseConfig));
     }
 
-
     private void checkHasEverConnectedTrue(int networkId) {
         WifiConfiguration checkConfig = mWifiConfigManager.getWifiConfiguration(networkId);
         assertTrue("hasEverConnected expected to be true.",
@@ -1627,5 +1622,80 @@
         return buf.toString();
     }
 
+    /**
+     * Test whether enableNetwork with the disableOthers flag set to false enables the
+     * input network, but does not attempt a connection.
+     */
+    @Test
+    public void testEnableNetworkWithoutDisableOthers() throws Exception {
+        addNetworks();
 
+        for (Map.Entry<Integer, List<WifiConfiguration>> entry : VISIBLE_CONFIGS.entrySet()) {
+            switchUser(entry.getKey());
+            // Iterate through all the configs for the current user and invoke |enableNetwork|
+            // on the corresponding config retrieved from WifiConfigManager.
+            for (WifiConfiguration config : entry.getValue()) {
+                WifiConfiguration retrievedConfig =
+                        mWifiConfigManager.getWifiConfiguration(config.networkId);
+                assertTrue(mWifiConfigManager.enableNetwork(retrievedConfig, false, 0));
+                assertNetworkStatus(retrievedConfig,
+                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+                verify(mWifiNative, never()).selectNetwork(anyInt());
+            }
+        }
+    }
+
+    /**
+     * Test whether enableNetwork without the disableOthers flag set to true enables the input
+     * network and attempts a connection to it immediately. It also checks if all the other
+     * networks are disabled.
+     */
+    @Test
+    public void testEnableNetworkWithDisableOthers() throws Exception {
+        addNetworks();
+
+        for (Map.Entry<Integer, List<WifiConfiguration>> entry : VISIBLE_CONFIGS.entrySet()) {
+            switchUser(entry.getKey());
+            // Iterate through all the configs for the current user and invoke |enableNetwork|
+            // on the corresponding config retrieved from WifiConfigManager.
+            for (WifiConfiguration config : entry.getValue()) {
+                reset(mWifiNative);
+                when(mWifiNative.selectNetwork(anyInt())).thenReturn(true);
+                WifiConfiguration retrievedConfig =
+                        mWifiConfigManager.getWifiConfiguration(config.networkId);
+                assertTrue(mWifiConfigManager.enableNetwork(retrievedConfig, true, 0));
+                assertNetworkStatus(retrievedConfig,
+                        WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
+                assertAllNetworksDisabledExcept(retrievedConfig.networkId,
+                        WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WIFI_MANAGER);
+                verify(mWifiNative).selectNetwork(retrievedConfig.networkId);
+                verify(mWifiNative, never()).selectNetwork(intThat(not(retrievedConfig.networkId)));
+            }
+        }
+    }
+
+    private void assertNetworkStatus(WifiConfiguration config, int disableReason) {
+        final WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();
+        assertEquals(disableReason, status.getNetworkSelectionDisableReason());
+        if (disableReason
+                == WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE) {
+            assertEquals(WifiConfiguration.Status.ENABLED, config.status);
+            assertTrue(config.getNetworkSelectionStatus().isNetworkEnabled());
+        } else if (disableReason
+                < WifiConfiguration.NetworkSelectionStatus.DISABLED_TLS_VERSION_MISMATCH) {
+            assertEquals(WifiConfiguration.Status.ENABLED, config.status);
+            assertTrue(config.getNetworkSelectionStatus().isNetworkTemporaryDisabled());
+        } else {
+            assertEquals(WifiConfiguration.Status.DISABLED, config.status);
+            assertTrue(config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled());
+        }
+    }
+
+    private void assertAllNetworksDisabledExcept(int netId, int disableReason) {
+        for (WifiConfiguration config : mWifiConfigManager.getSavedNetworks()) {
+            if (config.networkId != netId) {
+                assertNetworkStatus(config, disableReason);
+            }
+        }
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
index 145c840..1c650b0 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
@@ -104,7 +104,8 @@
         NETWORK_VARS.add(NETWORK_3_VARS);
     }
 
-    // Taken from wpa_supplicant.conf actual test device Some fields modified for privacy.
+    // Taken from wpa_supplicant.conf of an actual test device. Some fields modified for privacy
+    // reasons.
     private static final String TEST_WPA_SUPPLICANT_CONF = ""
             + "ctrl_interface=/data/misc/wifi/sockets\n"
             + "disable_scan_offload=1\n"
@@ -163,8 +164,7 @@
         MockitoAnnotations.initMocks(this);
 
         mMockKeyStore = new MockKeyStore();
-        mWifiConfigStore = new WifiConfigStore(mWifiNative, mMockKeyStore.createMock(), null,
-                false, true);
+        mWifiConfigStore = new WifiConfigStore(mWifiNative, mMockKeyStore.createMock(), null);
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index aa48cad..d9574e1 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -21,6 +21,8 @@
 import static org.junit.Assert.*;
 import static org.mockito.Mockito.*;
 
+import android.app.test.TestAlarmManager;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.wifi.ScanResult;
@@ -37,10 +39,10 @@
 import android.net.wifi.WifiSsid;
 import android.os.SystemClock;
 import android.os.WorkSource;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.R;
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
 
 import org.junit.After;
 import org.junit.Before;
@@ -64,7 +66,7 @@
     public void setUp() throws Exception {
         mWifiInjector = mockWifiInjector();
         mResource = mockResource();
-        mAlarmManager = new MockAlarmManager();
+        mAlarmManager = new TestAlarmManager();
         mContext = mockContext();
         mWifiStateMachine = mockWifiStateMachine();
         mWifiConfigManager = mockWifiConfigManager();
@@ -88,8 +90,8 @@
 
     private Resources mResource;
     private Context mContext;
-    private MockAlarmManager mAlarmManager;
-    private MockLooper mLooper = new MockLooper();
+    private TestAlarmManager mAlarmManager;
+    private TestLooper mLooper = new TestLooper();
     private WifiConnectivityManager mWifiConnectivityManager;
     private WifiQualifiedNetworkSelector mWifiQNS;
     private WifiStateMachine mWifiStateMachine;
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
index 44a710a..8de4b82 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
@@ -16,7 +16,10 @@
 
 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_SET_AP;
@@ -28,6 +31,8 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.os.WorkSource;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
 
@@ -73,13 +78,13 @@
         when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(true);
     }
 
-    MockLooper mLooper;
+    TestLooper mLooper;
     @Mock Context mContext;
     @Mock WifiServiceImpl mService;
     @Mock FrameworkFacade mFacade;
     @Mock WifiSettingsStore mSettingsStore;
     @Mock WifiStateMachine mWifiStateMachine;
-    @Mock WifiServiceImpl.LockList mLockList;
+    @Mock WifiLockManager mWifiLockManager;
 
     WifiController mWifiController;
 
@@ -87,14 +92,14 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
 
         initializeSettingsStore();
 
         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,38 @@
         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());
+    }
 }
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 2a30ffd..cae65e1 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiLoggerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi;
 
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -30,8 +31,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
index 41e4e46..c3fcc96 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNotificationControllerTest.java
@@ -34,6 +34,7 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiManager;
 import android.os.UserHandle;
+import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -79,7 +80,7 @@
         when(mFrameworkFacade.getIntegerSetting(mContext,
                 Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1)).thenReturn(1);
 
-        MockLooper mock_looper = new MockLooper();
+        TestLooper mock_looper = new TestLooper();
         mWifiNotificationController = new WifiNotificationController(
                 mContext, mock_looper.getLooper(), mWifiStateMachine, mFrameworkFacade,
                 mock(Notification.Builder.class));
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java b/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java
index 64fee84..e7128ad 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiQualifiedNetworkSelectorTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.Context;
 import android.content.res.Resources;
 import android.net.NetworkScoreManager;
@@ -45,7 +46,6 @@
 import android.util.LocalLog;
 
 import com.android.internal.R;
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
index e0f94ad..d3e247d 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
@@ -22,6 +22,8 @@
 import static org.mockito.Mockito.*;
 
 import android.app.ActivityManager;
+import android.app.test.TestAlarmManager;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -53,6 +55,7 @@
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.security.KeyStore;
 import android.telephony.TelephonyManager;
@@ -65,7 +68,6 @@
 import com.android.internal.util.AsyncChannel;
 import com.android.internal.util.IState;
 import com.android.internal.util.StateMachine;
-import com.android.server.wifi.MockAnswerUtil.AnswerWithArguments;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.hotspot2.Utils;
 import com.android.server.wifi.p2p.WifiP2pServiceImpl;
@@ -229,7 +231,7 @@
         when(context.getSystemService(Context.POWER_SERVICE)).thenReturn(
                 new PowerManager(context, mock(IPowerManager.class), new Handler()));
 
-        mAlarmManager = new MockAlarmManager();
+        mAlarmManager = new TestAlarmManager();
         when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(
                 mAlarmManager.getAlarmManager());
 
@@ -307,10 +309,10 @@
     HandlerThread mP2pThread;
     HandlerThread mSyncThread;
     AsyncChannel  mWsmAsyncChannel;
-    MockAlarmManager mAlarmManager;
+    TestAlarmManager mAlarmManager;
     MockWifiMonitor mWifiMonitor;
     TestIpManager mTestIpManager;
-    MockLooper mLooper;
+    TestLooper mLooper;
     WifiConfigManager mWifiConfigManager;
 
     @Mock WifiNative mWifiNative;
@@ -334,7 +336,7 @@
         Log.d(TAG, "Setting up ...");
 
         // Ensure looper exists
-        mLooper = new MockLooper();
+        mLooper = new TestLooper();
 
         MockitoAnnotations.initMocks(this);
 
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/TlvBufferUtilsTest.java b/tests/wifitests/src/com/android/server/wifi/nan/TlvBufferUtilsTest.java
index 5c83185..6fc0ae6 100644
--- a/tests/wifitests/src/com/android/server/wifi/nan/TlvBufferUtilsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/nan/TlvBufferUtilsTest.java
@@ -24,7 +24,6 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ErrorCollector;
-import org.junit.rules.ExpectedException;
 
 /**
  * Unit test harness for WifiNanManager class.
@@ -34,9 +33,6 @@
     @Rule
     public ErrorCollector collector = new ErrorCollector();
 
-    @Rule
-    public ExpectedException thrown = ExpectedException.none();
-
     /*
      * TlvBufferUtils Tests
      */
@@ -130,82 +126,72 @@
         }
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvInvalidSizeT1L0() {
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, 0);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvInvalidSizeTm3L2() {
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(-3, 2);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvInvalidSizeT1Lm2() {
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, -2);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvInvalidSizeT1L3() {
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, 3);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvInvalidSizeT3L1() {
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(3, 1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvItInvalidSizeT1L0() {
         final byte[] dummy = {
                 0, 1, 2 };
         final int dummyLength = 3;
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(1, 0, dummy,
                 dummyLength);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvItInvalidSizeTm3L2() {
         final byte[] dummy = {
                 0, 1, 2 };
         final int dummyLength = 3;
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(-3, 2, dummy,
                 dummyLength);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvItInvalidSizeT1Lm2() {
         final byte[] dummy = {
                 0, 1, 2 };
         final int dummyLength = 3;
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(1, -2, dummy,
                 dummyLength);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvItInvalidSizeT1L3() {
         final byte[] dummy = {
                 0, 1, 2 };
         final int dummyLength = 3;
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(1, 3, dummy,
                 dummyLength);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testTlvItInvalidSizeT3L1() {
         final byte[] dummy = {
                 0, 1, 2 };
         final int dummyLength = 3;
-        thrown.expect(IllegalArgumentException.class);
         TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(3, 1, dummy,
                 dummyLength);
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalMock.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalMock.java
index 4f3ba4d..60fdd1e 100644
--- a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalMock.java
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalMock.java
@@ -39,6 +39,10 @@
         throw new IllegalStateException("Please mock this class!");
     }
 
+    public void configHalMockNative(short transactionId, String jsonArgs) {
+        throw new IllegalStateException("Please mock this class!");
+    }
+
     public void disableHalMockNative(short transactionId) {
         throw new IllegalStateException("Please mock this class!");
     }
@@ -82,15 +86,17 @@
 
     public static native void callDisabled(String jsonArgs);
 
+    public static native void callTransmitFollowup(String jsonArgs);
+
     /**
      * initialize NAN mock
      */
     private static native int initNanHalMock();
 
-    public static void initNanHalMockLibrary() throws Exception {
-        Field field = WifiNanNative.class.getDeclaredField("sNanNativeInit");
+    public static void initNanHalMockLibrary(WifiNanNative instance) throws Exception {
+        Field field = WifiNanNative.class.getDeclaredField("mNanNativeInit");
         field.setAccessible(true);
-        field.setBoolean(null, true);
+        field.setBoolean(instance, true);
 
         initNanHalMock();
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalTest.java
index 536c924..465bc14 100644
--- a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanHalTest.java
@@ -19,15 +19,14 @@
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
 import android.net.wifi.nan.TlvBufferUtils;
-import android.net.wifi.nan.WifiNanSessionListener;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanSessionCallback;
 import android.os.Bundle;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -68,7 +67,7 @@
         MockitoAnnotations.initMocks(this);
 
         HalMockUtils.initHalMockLibrary();
-        WifiNanHalMock.initNanHalMockLibrary();
+        WifiNanHalMock.initNanHalMockLibrary(mDut);
         WifiNanNative.initNanHandlersNative(WifiNative.class, WifiNative.sWlan0Index);
         HalMockUtils.setHalMockObject(mNanHalMock);
         installMockNanStateManager(mNanStateManager);
@@ -97,6 +96,14 @@
     }
 
     @Test
+    public void testConfigCall() throws JSONException {
+        final short transactionId = 31235;
+        final short masterPref = 157;
+
+        testConfig(transactionId, masterPref);
+    }
+
+    @Test
     public void testDisable() {
         final short transactionId = 5478;
 
@@ -113,6 +120,7 @@
         final String ssi = "some much longer and more arbitrary data";
         final int publishCount = 7;
         final int publishTtl = 66;
+        final boolean enableTerminateNotification = true;
 
         TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
         tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
@@ -122,8 +130,8 @@
         tlvRx.allocate(150).putByte(0, (byte) 66).putInt(0, 127).putString(0, "some other string")
                 .putZeroLengthElement(0).putByteArray(0, serviceName.getBytes());
 
-        testPublish(transactionId, publishId, PublishSettings.PUBLISH_TYPE_UNSOLICITED, serviceName,
-                ssi, tlvTx, tlvRx, publishCount, publishTtl);
+        testPublish(transactionId, publishId, PublishConfig.PUBLISH_TYPE_UNSOLICITED, serviceName,
+                ssi, tlvTx, tlvRx, publishCount, publishTtl, enableTerminateNotification);
     }
 
     @Test
@@ -134,6 +142,7 @@
         final String ssi = "some much longer arbitrary data";
         final int publishCount = 32;
         final int publishTtl = 33;
+        final boolean enableTerminateNotification = false;
 
         TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
         tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
@@ -143,8 +152,8 @@
         tlvRx.allocate(150).putByte(0, (byte) 66).putInt(0, 127).putString(0, "some other string")
                 .putZeroLengthElement(0).putByteArray(0, serviceName.getBytes());
 
-        testPublish(transactionId, publishId, PublishSettings.PUBLISH_TYPE_SOLICITED, serviceName,
-                ssi, tlvTx, tlvRx, publishCount, publishTtl);
+        testPublish(transactionId, publishId, PublishConfig.PUBLISH_TYPE_SOLICITED, serviceName,
+                ssi, tlvTx, tlvRx, publishCount, publishTtl, enableTerminateNotification);
     }
 
     @Test
@@ -169,6 +178,8 @@
         final String ssi = "some much longer arbitrary data";
         final int subscribeCount = 32;
         final int subscribeTtl = 33;
+        final int matchStyle = SubscribeConfig.MATCH_STYLE_ALL;
+        final boolean enableTerminateNotification = true;
 
         TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
         tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
@@ -178,8 +189,9 @@
         tlvRx.allocate(150).putByte(0, (byte) 66).putInt(0, 127).putString(0, "some other string")
                 .putZeroLengthElement(0).putByteArray(0, serviceName.getBytes());
 
-        testSubscribe(transactionId, subscribeId, SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE,
-                serviceName, ssi, tlvTx, tlvRx, subscribeCount, subscribeTtl);
+        testSubscribe(transactionId, subscribeId, SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE,
+                serviceName, ssi, tlvTx, tlvRx, subscribeCount, subscribeTtl, matchStyle,
+                enableTerminateNotification);
     }
 
     @Test
@@ -190,6 +202,8 @@
         final String ssi = "some much longer arbitrary data";
         final int subscribeCount = 32;
         final int subscribeTtl = 33;
+        final int matchStyle = SubscribeConfig.MATCH_STYLE_FIRST_ONLY;
+        final boolean enableTerminateNotification = false;
 
         TlvBufferUtils.TlvConstructor tlvTx = new TlvBufferUtils.TlvConstructor(0, 1);
         tlvTx.allocate(150).putByte(0, (byte) 10).putInt(0, 100).putString(0, "some string")
@@ -199,8 +213,9 @@
         tlvRx.allocate(150).putByte(0, (byte) 66).putInt(0, 127).putString(0, "some other string")
                 .putZeroLengthElement(0).putByteArray(0, serviceName.getBytes());
 
-        testSubscribe(transactionId, subscribeId, SubscribeSettings.SUBSCRIBE_TYPE_ACTIVE,
-                serviceName, ssi, tlvTx, tlvRx, subscribeCount, subscribeTtl);
+        testSubscribe(transactionId, subscribeId, SubscribeConfig.SUBSCRIBE_TYPE_ACTIVE,
+                serviceName, ssi, tlvTx, tlvRx, subscribeCount, subscribeTtl, matchStyle,
+                enableTerminateNotification);
     }
 
     @Test
@@ -260,6 +275,7 @@
         final int max_ndi_interfaces = 10;
         final int max_ndp_sessions = 11;
         final int max_app_info_len = 12;
+        final int max_queued_transmit_followup_msgs = 7;
 
         ArgumentCaptor<WifiNanNative.Capabilities> capabilitiesCapture = ArgumentCaptor
                 .forClass(WifiNanNative.Capabilities.class);
@@ -283,12 +299,13 @@
         args.putInt("body.nan_capabilities.max_ndi_interfaces", max_ndi_interfaces);
         args.putInt("body.nan_capabilities.max_ndp_sessions", max_ndp_sessions);
         args.putInt("body.nan_capabilities.max_app_info_len", max_app_info_len);
+        args.putInt("body.nan_capabilities.max_queued_transmit_followup_msgs",
+                max_queued_transmit_followup_msgs);
 
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onCapabilitiesUpdate(eq(transactionId),
-                capabilitiesCapture.capture());
+        verify(mNanStateManager).onCapabilitiesUpdateNotification(capabilitiesCapture.capture());
         WifiNanNative.Capabilities capabilities = capabilitiesCapture.getValue();
         collector.checkThat("max_concurrent_nan_clusters", capabilities.maxConcurrentNanClusters,
                 equalTo(max_concurrent_nan_clusters));
@@ -312,6 +329,9 @@
                 equalTo(max_ndp_sessions));
         collector.checkThat("max_app_info_len", capabilities.maxAppInfoLen,
                 equalTo(max_app_info_len));
+        collector.checkThat("max_queued_transmit_followup_msgs",
+                capabilities.maxQueuedTransmitMessages, equalTo(max_queued_transmit_followup_msgs));
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -326,7 +346,8 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onConfigCompleted(transactionId);
+        verify(mNanStateManager).onConfigSuccessResponse(transactionId);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -341,8 +362,9 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onConfigFailed(transactionId,
-                WifiNanSessionListener.FAIL_REASON_INVALID_ARGS);
+        verify(mNanStateManager).onConfigFailedResponse(transactionId,
+                WifiNanEventCallback.REASON_INVALID_ARGS);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -359,7 +381,8 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onPublishSuccess(transactionId, publishId);
+        verify(mNanStateManager).onSessionConfigSuccessResponse(transactionId, true, publishId);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -376,14 +399,14 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onPublishFail(transactionId,
-                WifiNanSessionListener.FAIL_REASON_NO_RESOURCES);
+        verify(mNanStateManager).onSessionConfigFailResponse(transactionId, true,
+                WifiNanSessionCallback.REASON_NO_RESOURCES);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
     public void testNotifyResponsePublishCancel() throws JSONException {
         final short transactionId = 23;
-        final int publishId = 127;
 
         Bundle args = new Bundle();
         args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
@@ -393,7 +416,7 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verifyZeroInteractions(mNanStateManager);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -410,7 +433,8 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onSubscribeSuccess(transactionId, subscribeId);
+        verify(mNanStateManager).onSessionConfigSuccessResponse(transactionId, false, subscribeId);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -427,24 +451,24 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onSubscribeFail(transactionId,
-                WifiNanSessionListener.FAIL_REASON_OTHER);
+        verify(mNanStateManager).onSessionConfigFailResponse(transactionId, false,
+                WifiNanSessionCallback.REASON_OTHER);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
     public void testNotifyResponseSubscribeCancel() throws JSONException {
         final short transactionId = 23;
-        final int subscribeId = 127;
 
         Bundle args = new Bundle();
-        args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
+        args.putInt("status", WifiNanNative.NAN_STATUS_DE_FAILURE);
         args.putInt("value", 0);
         args.putInt("response_type", WifiNanNative.NAN_RESPONSE_SUBSCRIBE_CANCEL);
 
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verifyZeroInteractions(mNanStateManager);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -459,7 +483,8 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onMessageSendSuccess(transactionId);
+        verify(mNanStateManager).onMessageSendQueuedSuccessResponse(transactionId);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -474,8 +499,25 @@
         WifiNanHalMock.callNotifyResponse(transactionId,
                 HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onMessageSendFail(transactionId,
-                WifiNanSessionListener.FAIL_REASON_OTHER);
+        verify(mNanStateManager).onMessageSendQueuedFailResponse(transactionId,
+                WifiNanSessionCallback.REASON_OTHER);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testNotifyResponseUnknown() throws JSONException {
+        final int invalidTransactionId = 99999;
+        final short transactionId = 46;
+
+        Bundle args = new Bundle();
+        args.putInt("status", WifiNanNative.NAN_STATUS_SUCCESS);
+        args.putInt("value", 0);
+        args.putInt("response_type", invalidTransactionId);
+
+        WifiNanHalMock.callNotifyResponse(transactionId,
+                HalMockUtils.convertBundleToJson(args).toString());
+
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -488,8 +530,9 @@
 
         WifiNanHalMock.callPublishTerminated(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onPublishTerminated(publishId,
-                WifiNanSessionListener.TERMINATE_REASON_DONE);
+        verify(mNanStateManager).onSessionTerminatedNotification(publishId,
+                WifiNanSessionCallback.TERMINATE_REASON_DONE, true);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -502,8 +545,9 @@
 
         WifiNanHalMock.callSubscribeTerminated(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onSubscribeTerminated(subscribeId,
-                WifiNanSessionListener.TERMINATE_REASON_FAIL);
+        verify(mNanStateManager).onSessionTerminatedNotification(subscribeId,
+                WifiNanSessionCallback.TERMINATE_REASON_FAIL, false);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -523,8 +567,9 @@
 
         WifiNanHalMock.callFollowup(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onMessageReceived(pubSubId, reqInstanceId, peer,
+        verify(mNanStateManager).onMessageReceivedNotification(pubSubId, reqInstanceId, peer,
                 message.getBytes(), message.length());
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -546,8 +591,9 @@
 
         WifiNanHalMock.callMatch(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onMatch(pubSubId, reqInstanceId, peer, ssi.getBytes(),
+        verify(mNanStateManager).onMatchNotification(pubSubId, reqInstanceId, peer, ssi.getBytes(),
                 ssi.length(), filter.getBytes(), filter.length());
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -560,11 +606,12 @@
 
         WifiNanHalMock.callDiscEngEvent(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onInterfaceAddressChange(mac);
+        verify(mNanStateManager).onInterfaceAddressChangeNotification(mac);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
-    public void testClusterChange() throws JSONException {
+    public void testClusterJoined() throws JSONException {
         final byte[] mac = HexEncoding.decode("060504030201".toCharArray(), false);
 
         Bundle args = new Bundle();
@@ -573,8 +620,24 @@
 
         WifiNanHalMock.callDiscEngEvent(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onClusterChange(WifiNanClientState.CLUSTER_CHANGE_EVENT_JOINED,
-                mac);
+        verify(mNanStateManager)
+                .onClusterChangeNotification(WifiNanClientState.CLUSTER_CHANGE_EVENT_JOINED, mac);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testClusterStarted() throws JSONException {
+        final byte[] mac = HexEncoding.decode("0A0B0C0B0A00".toCharArray(), false);
+
+        Bundle args = new Bundle();
+        args.putInt("event_type", WifiNanNative.NAN_EVENT_ID_STARTED_CLUSTER);
+        args.putByteArray("data", mac);
+
+        WifiNanHalMock.callDiscEngEvent(HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager)
+                .onClusterChangeNotification(WifiNanClientState.CLUSTER_CHANGE_EVENT_STARTED, mac);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     @Test
@@ -584,7 +647,37 @@
 
         WifiNanHalMock.callDisabled(HalMockUtils.convertBundleToJson(args).toString());
 
-        verify(mNanStateManager).onNanDown(WifiNanSessionListener.FAIL_REASON_OTHER);
+        verify(mNanStateManager).onNanDownNotification(WifiNanEventCallback.REASON_OTHER);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testTransmitFollowupSuccess() throws JSONException {
+        final short transactionId = 123;
+
+        Bundle args = new Bundle();
+        args.putInt("id", transactionId);
+        args.putInt("reason", WifiNanNative.NAN_STATUS_SUCCESS);
+
+        WifiNanHalMock.callTransmitFollowup(HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onMessageSendSuccessNotification(transactionId);
+        verifyNoMoreInteractions(mNanStateManager);
+    }
+
+    @Test
+    public void testTransmitFollowupFail() throws JSONException {
+        final short transactionId = 5689;
+
+        Bundle args = new Bundle();
+        args.putInt("id", transactionId);
+        args.putInt("reason", WifiNanNative.NAN_STATUS_TX_FAIL);
+
+        WifiNanHalMock.callTransmitFollowup(HalMockUtils.convertBundleToJson(args).toString());
+
+        verify(mNanStateManager).onMessageSendFailNotification(transactionId,
+                WifiNanSessionCallback.REASON_TX_FAIL);
+        verifyNoMoreInteractions(mNanStateManager);
     }
 
     /*
@@ -597,7 +690,7 @@
                 .setClusterHigh(clusterHigh).setMasterPreference(masterPref)
                 .setSupport5gBand(enable5g).build();
 
-        mDut.enableAndConfigure(transactionId, configRequest);
+        mDut.enableAndConfigure(transactionId, configRequest, true);
 
         verify(mNanHalMock).enableHalMockNative(eq(transactionId), mArgs.capture());
 
@@ -646,19 +739,63 @@
                 equalTo(0));
     }
 
+    private void testConfig(short transactionId, int masterPref) throws JSONException {
+        ConfigRequest configRequest = new ConfigRequest.Builder().setMasterPreference(masterPref)
+                .build();
+
+        mDut.enableAndConfigure(transactionId, configRequest, false);
+
+        verify(mNanHalMock).configHalMockNative(eq(transactionId), mArgs.capture());
+
+        Bundle argsData = HalMockUtils.convertJsonToBundle(mArgs.getValue());
+
+        collector.checkThat("config_master_pref", argsData.getInt("config_master_pref"),
+                equalTo(1));
+        collector.checkThat("master_pref", argsData.getInt("master_pref"), equalTo(masterPref));
+
+        collector.checkThat("config_sid_beacon", argsData.getInt("config_sid_beacon"), equalTo(0));
+        collector.checkThat("sid_beacon", argsData.getInt("sid_beacon"), equalTo(0));
+        collector.checkThat("config_rssi_proximity", argsData.getInt("config_rssi_proximity"),
+                equalTo(0));
+        collector.checkThat("rssi_proximity", argsData.getInt("rssi_proximity"), equalTo(0));
+        collector.checkThat("config_5g_rssi_close_proximity",
+                argsData.getInt("config_5g_rssi_close_proximity"), equalTo(0));
+        collector.checkThat("rssi_close_proximity_5g_val",
+                argsData.getInt("rssi_close_proximity_5g_val"), equalTo(0));
+        collector.checkThat("config_rssi_window_size", argsData.getInt("config_rssi_window_size"),
+                equalTo(0));
+        collector.checkThat("rssi_window_size_val", argsData.getInt("rssi_window_size_val"),
+                equalTo(0));
+        collector.checkThat("config_cluster_attribute_val",
+                argsData.getInt("config_cluster_attribute_val"), equalTo(0));
+        collector.checkThat("config_scan_params", argsData.getInt("config_scan_params"),
+                equalTo(0));
+        collector.checkThat("config_random_factor_force",
+                argsData.getInt("config_random_factor_force"), equalTo(0));
+        collector.checkThat("random_factor_force_val", argsData.getInt("random_factor_force_val"),
+                equalTo(0));
+        collector.checkThat("config_hop_count_force", argsData.getInt("config_hop_count_force"),
+                equalTo(0));
+        collector.checkThat("hop_count_force_val", argsData.getInt("hop_count_force_val"),
+                equalTo(0));
+        collector.checkThat("config_conn_capability", argsData.getInt("config_conn_capability"),
+                equalTo(0));
+        collector.checkThat("num_config_discovery_attr",
+                argsData.getInt("num_config_discovery_attr"), equalTo(0));
+        collector.checkThat("config_fam", argsData.getInt("config_fam"), equalTo(0));
+    }
+
     private void testPublish(short transactionId, int publishId, int publishType,
             String serviceName, String ssi, TlvBufferUtils.TlvConstructor tlvTx,
-            TlvBufferUtils.TlvConstructor tlvRx, int publishCount, int publishTtl)
-                    throws JSONException {
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(ssi)
-                .setTxFilter(tlvTx.getArray(), tlvTx.getActualLength())
-                .setRxFilter(tlvRx.getArray(), tlvRx.getActualLength()).build();
+            TlvBufferUtils.TlvConstructor tlvRx, int publishCount, int publishTtl,
+            boolean enableTerminateNotification) throws JSONException {
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(ssi).setTxFilter(tlvTx.getArray(), tlvTx.getActualLength())
+                .setRxFilter(tlvRx.getArray(), tlvRx.getActualLength()).setPublishType(publishType)
+                .setPublishCount(publishCount).setTtlSec(publishTtl)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
-        PublishSettings publishSettings = new PublishSettings.Builder().setPublishType(publishType)
-                .setPublishCount(publishCount).setTtlSec(publishTtl).build();
-
-        mDut.publish(transactionId, publishId, publishData, publishSettings);
+        mDut.publish(transactionId, publishId, publishConfig);
 
         verify(mNanHalMock).publishHalMockNative(eq(transactionId), mArgs.capture());
 
@@ -668,7 +805,7 @@
         collector.checkThat("ttl", argsData.getInt("ttl"), equalTo(publishTtl));
         collector.checkThat("publish_type", argsData.getInt("publish_type"), equalTo(publishType));
         collector.checkThat("tx_type", argsData.getInt("tx_type"),
-                equalTo(publishType == PublishSettings.PUBLISH_TYPE_UNSOLICITED ? 0 : 1));
+                equalTo(publishType == PublishConfig.PUBLISH_TYPE_UNSOLICITED ? 0 : 1));
         collector.checkThat("publish_count", argsData.getInt("publish_count"),
                 equalTo(publishCount));
         collector.checkThat("service_name_len", argsData.getInt("service_name_len"),
@@ -692,22 +829,23 @@
         collector.checkThat("rssi_threshold_flag", argsData.getInt("rssi_threshold_flag"),
                 equalTo(0));
         collector.checkThat("connmap", argsData.getInt("connmap"), equalTo(0));
+        collector.checkThat("recv_indication_cfg", argsData.getInt("recv_indication_cfg"),
+                equalTo(enableTerminateNotification ? 0x0 : 0x1));
     }
 
     private void testSubscribe(short transactionId, int subscribeId, int subscribeType,
             String serviceName, String ssi, TlvBufferUtils.TlvConstructor tlvTx,
-            TlvBufferUtils.TlvConstructor tlvRx, int subscribeCount, int subscribeTtl)
-                    throws JSONException {
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
+            TlvBufferUtils.TlvConstructor tlvRx, int subscribeCount, int subscribeTtl,
+            int matchStyle, boolean enableTerminateNotification) throws JSONException {
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
                 .setServiceSpecificInfo(ssi)
                 .setTxFilter(tlvTx.getArray(), tlvTx.getActualLength())
-                .setRxFilter(tlvRx.getArray(), tlvRx.getActualLength()).build();
-
-        SubscribeSettings subscribeSettings = new SubscribeSettings.Builder()
+                .setRxFilter(tlvRx.getArray(), tlvRx.getActualLength())
                 .setSubscribeType(subscribeType).setSubscribeCount(subscribeCount)
-                .setTtlSec(subscribeTtl).build();
+                .setTtlSec(subscribeTtl).setMatchStyle(matchStyle)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
-        mDut.subscribe(transactionId, subscribeId, subscribeData, subscribeSettings);
+        mDut.subscribe(transactionId, subscribeId, subscribeConfig);
 
         verify(mNanHalMock).subscribeHalMockNative(eq(transactionId), mArgs.capture());
 
@@ -727,7 +865,7 @@
         collector.checkThat("ssiRequiredForMatchIndication",
                 argsData.getInt("ssiRequiredForMatchIndication"), equalTo(0));
         collector.checkThat("subscribe_match_indicator",
-                argsData.getInt("subscribe_match_indicator"), equalTo(0));
+                argsData.getInt("subscribe_match_indicator"), equalTo(matchStyle));
         collector.checkThat("subscribe_count", argsData.getInt("subscribe_count"),
                 equalTo(subscribeCount));
         collector.checkThat("service_name_len", argsData.getInt("service_name_len"),
@@ -751,6 +889,8 @@
         collector.checkThat("connmap", argsData.getInt("connmap"), equalTo(0));
         collector.checkThat("num_intf_addr_present", argsData.getInt("num_intf_addr_present"),
                 equalTo(0));
+        collector.checkThat("recv_indication_cfg", argsData.getInt("recv_indication_cfg"),
+                equalTo(enableTerminateNotification ? 0x0 : 0x1));
     }
 
     private static void installMockNanStateManager(WifiNanStateManager nanStateManager)
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanManagerTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanManagerTest.java
index bd4d43b..93c3ab5 100644
--- a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanManagerTest.java
@@ -18,107 +18,605 @@
 
 import static org.hamcrest.core.IsEqual.equalTo;
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
+import android.net.wifi.RttManager;
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
+import android.net.wifi.nan.IWifiNanEventCallback;
+import android.net.wifi.nan.IWifiNanManager;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.nan.WifiNanEventCallback;
+import android.net.wifi.nan.WifiNanManager;
+import android.net.wifi.nan.WifiNanPublishSession;
+import android.net.wifi.nan.WifiNanSessionCallback;
+import android.net.wifi.nan.WifiNanSubscribeSession;
+import android.os.IBinder;
 import android.os.Parcel;
+import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ErrorCollector;
-import org.junit.rules.ExpectedException;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 /**
  * Unit test harness for WifiNanManager class.
  */
 @SmallTest
 public class WifiNanManagerTest {
+    private WifiNanManager mDut;
+    private TestLooper mMockLooper;
+
     @Rule
     public ErrorCollector collector = new ErrorCollector();
 
-    @Rule
-    public ExpectedException thrown = ExpectedException.none();
+    @Mock
+    public WifiNanEventCallback mockCallback;
+
+    @Mock
+    public WifiNanSessionCallback mockSessionCallback;
+
+    @Mock
+    public IWifiNanManager mockNanService;
+
+    @Mock
+    public WifiNanPublishSession mockPublishSession;
+
+    @Mock
+    public WifiNanSubscribeSession mockSubscribeSession;
+
+    @Mock
+    public RttManager.RttListener mockRttListener;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mDut = new WifiNanManager(mockNanService);
+        mMockLooper = new TestLooper();
+    }
+
+    /*
+     * Straight pass-through tests
+     */
+
+    /**
+     * Validate pass-through of enableUsage() API.
+     */
+    @Test
+    public void testEnableUsage() throws Exception {
+        mDut.enableUsage();
+
+        verify(mockNanService).enableUsage();
+    }
+
+    /**
+     * Validate pass-through of disableUsage() API.
+     */
+    @Test
+    public void testDisableUsage() throws Exception {
+        mDut.disableUsage();
+
+        verify(mockNanService).disableUsage();
+    }
+
+    /**
+     * Validate pass-through of isUsageEnabled() API.
+     */
+    @Test
+    public void testIsUsageEnable() throws Exception {
+        mDut.isUsageEnabled();
+
+        verify(mockNanService).isUsageEnabled();
+    }
+
+    /*
+     * WifiNanEventCallbackProxy Tests
+     */
+
+    /**
+     * Validate the successful connect flow: (1) try subscribing (2) connect +
+     * success (3) publish, (4) disconnect (5) try publishing (6) connect again
+     */
+    @Test
+    public void testConnectFlow() throws Exception {
+        final int clientId = 4565;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                any(ConfigRequest.class))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IBinder> binder = ArgumentCaptor.forClass(IBinder.class);
+
+        // (1) try subscribing on an unconnected manager: fails silently
+        mDut.subscribe(new SubscribeConfig.Builder().build(), mockSessionCallback);
+
+        // (2) connect + success
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(binder.capture(),
+                clientProxyCallback.capture(), (ConfigRequest) isNull());
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (3) publish - should succeed
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+        mDut.publish(publishConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).publish(eq(clientId), eq(publishConfig),
+                any(IWifiNanSessionCallback.class));
+
+        // (4) disconnect
+        mDut.disconnect();
+        inOrder.verify(mockNanService).disconnect(eq(clientId), eq(binder.getValue()));
+
+        // (5) try publishing again - fails silently
+        mDut.publish(new PublishConfig.Builder().build(), mockSessionCallback);
+
+        // (6) connect
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(binder.capture(), any(IWifiNanEventCallback.class),
+                (ConfigRequest) isNull());
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService);
+    }
+
+    /**
+     * Validate the failed connect flow: (1) connect + failure, (2) try
+     * publishing (3) connect + success (4) subscribe
+     */
+    @Test
+    public void testConnectFailure() throws Exception {
+        final int clientId = 4565;
+        final int reason = WifiNanEventCallback.REASON_OTHER;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                any(ConfigRequest.class))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+
+        // (1) connect + failure
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                (ConfigRequest) isNull());
+        clientProxyCallback.getValue().onConnectFail(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectFail(reason);
+
+        // (2) try publishing - silent failure (since already know that no
+        // connection)
+        mDut.publish(new PublishConfig.Builder().build(), mockSessionCallback);
+
+        // (3) connect + success
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                (ConfigRequest) isNull());
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (4) subscribe: should succeed
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+        mDut.subscribe(subscribeConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).subscribe(eq(clientId), eq(subscribeConfig),
+                any(IWifiNanSessionCallback.class));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService);
+    }
+
+    /**
+     * Validate that cannot call connect on an existing connection: (1) connect
+     * + success, (2) try connect again
+     */
+    @Test
+    public void testInvalidConnectSequence() throws Exception {
+        final int clientId = 4565;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                any(ConfigRequest.class))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+
+        // (1) connect + success
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                (ConfigRequest) isNull());
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) connect - forward to service (though will fail silently)
+        mDut.connect(mMockLooper.getLooper(), mockCallback);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                (ConfigRequest) isNull());
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService);
+    }
+
+    /*
+     * WifiNanSessionCallbackProxy Tests
+     */
+
+    /**
+     * Validate the publish flow: (0) connect + success, (1) publish, (2)
+     * success creates session, (3) pass through everything, (4) update publish
+     * through session, (5) terminate locally, (6) try another command -
+     * ignored.
+     */
+    @Test
+    public void testPublishFlow() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+        final int peerId = 873;
+        final String string1 = "hey from here...";
+        final String string2 = "some other arbitrary string...";
+        final int messageId = 2123;
+        final int reason = WifiNanSessionCallback.REASON_OTHER;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                eq(configRequest))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanPublishSession> publishSession = ArgumentCaptor
+                .forClass(WifiNanPublishSession.class);
+
+        // (0) connect + success
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class),
+                clientProxyCallback.capture(), eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (1) publish
+        mDut.publish(publishConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+
+        // (2) publish session created
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+
+        // (3) ...
+        publishSession.getValue().sendMessage(peerId, string1.getBytes(), string1.length(),
+                messageId);
+        sessionProxyCallback.getValue().onMatch(peerId, string1.getBytes(),
+                string1.length(), string2.getBytes(), string2.length());
+        sessionProxyCallback.getValue().onMessageReceived(peerId, string1.getBytes(),
+                string1.length());
+        sessionProxyCallback.getValue().onMessageSendFail(messageId, reason);
+        sessionProxyCallback.getValue().onMessageSendSuccess(messageId);
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mockNanService).sendMessage(eq(clientId), eq(sessionId), eq(peerId),
+                eq(string1.getBytes()), eq(string1.length()), eq(messageId), eq(0));
+        inOrder.verify(mockSessionCallback).onMatch(eq(peerId), eq(string1.getBytes()),
+                eq(string1.length()), eq(string2.getBytes()), eq(string2.length()));
+        inOrder.verify(mockSessionCallback).onMessageReceived(eq(peerId), eq(string1.getBytes()),
+                eq(string1.length()));
+        inOrder.verify(mockSessionCallback).onMessageSendFail(eq(messageId), eq(reason));
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(eq(messageId));
+
+        // (4) update publish
+        publishSession.getValue().updatePublish(publishConfig);
+        sessionProxyCallback.getValue().onSessionConfigFail(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockNanService).updatePublish(eq(clientId), eq(sessionId),
+                eq(publishConfig));
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(eq(reason));
+
+        // (5) terminate
+        publishSession.getValue().terminate();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockNanService).terminateSession(clientId, sessionId);
+
+        // (6) try an update (nothing)
+        publishSession.getValue().updatePublish(publishConfig);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession);
+    }
+
+    /**
+     * Validate race condition of session terminate and session action: (1)
+     * connect, (2) publish success + terminate, (3) update.
+     */
+    @Test
+    public void testPublishRemoteTerminate() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+        final int reason = WifiNanSessionCallback.TERMINATE_REASON_DONE;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                eq(configRequest))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanPublishSession> publishSession = ArgumentCaptor
+                .forClass(WifiNanPublishSession.class);
+
+        // (1) connect successfully
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) publish: successfully - then terminated
+        mDut.publish(publishConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        sessionProxyCallback.getValue().onSessionTerminated(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+        inOrder.verify(mockSessionCallback).onSessionTerminated(reason);
+
+        // (3) failure when trying to update: NOP
+        publishSession.getValue().updatePublish(publishConfig);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession);
+    }
+
+    /**
+     * Validate the subscribe flow: (0) connect + success, (1) subscribe, (2)
+     * success creates session, (3) pass through everything, (4) update
+     * subscribe through session, (5) terminate locally, (6) try another command
+     * - ignored.
+     */
+    @Test
+    public void testSubscribeFlow() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+        final int peerId = 873;
+        final String string1 = "hey from here...";
+        final String string2 = "some other arbitrary string...";
+        final int messageId = 2123;
+        final int reason = WifiNanSessionCallback.REASON_OTHER;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                eq(configRequest))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockSubscribeSession);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanSubscribeSession> subscribeSession = ArgumentCaptor
+                .forClass(WifiNanSubscribeSession.class);
+
+        // (0) connect + success
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (1) subscribe
+        mDut.subscribe(subscribeConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).subscribe(eq(clientId), eq(subscribeConfig),
+                sessionProxyCallback.capture());
+
+        // (2) subscribe session created
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSubscribeStarted(subscribeSession.capture());
+
+        // (3) ...
+        subscribeSession.getValue().sendMessage(peerId, string1.getBytes(), string1.length(),
+                messageId);
+        sessionProxyCallback.getValue().onMatch(peerId, string1.getBytes(), string1.length(),
+                string2.getBytes(), string2.length());
+        sessionProxyCallback.getValue().onMessageReceived(peerId, string1.getBytes(),
+                string1.length());
+        sessionProxyCallback.getValue().onMessageSendFail(messageId, reason);
+        sessionProxyCallback.getValue().onMessageSendSuccess(messageId);
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mockNanService).sendMessage(eq(clientId), eq(sessionId), eq(peerId),
+                eq(string1.getBytes()), eq(string1.length()), eq(messageId), eq(0));
+        inOrder.verify(mockSessionCallback).onMatch(eq(peerId), eq(string1.getBytes()),
+                eq(string1.length()), eq(string2.getBytes()), eq(string2.length()));
+        inOrder.verify(mockSessionCallback).onMessageReceived(eq(peerId), eq(string1.getBytes()),
+                eq(string1.length()));
+        inOrder.verify(mockSessionCallback).onMessageSendFail(eq(messageId), eq(reason));
+        inOrder.verify(mockSessionCallback).onMessageSendSuccess(eq(messageId));
+
+        // (4) update subscribe
+        subscribeSession.getValue().updateSubscribe(subscribeConfig);
+        sessionProxyCallback.getValue().onSessionConfigFail(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockNanService).updateSubscribe(eq(clientId), eq(sessionId),
+                eq(subscribeConfig));
+        inOrder.verify(mockSessionCallback).onSessionConfigFail(eq(reason));
+
+        // (5) terminate
+        subscribeSession.getValue().terminate();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockNanService).terminateSession(clientId, sessionId);
+
+        // (6) try an update (nothing)
+        subscribeSession.getValue().updateSubscribe(subscribeConfig);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockSubscribeSession);
+    }
+
+    /**
+     * Validate race condition of session terminate and session action: (1)
+     * connect, (2) subscribe success + terminate, (3) update.
+     */
+    @Test
+    public void testSubscribeRemoteTerminate() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+        final int reason = WifiNanSessionCallback.TERMINATE_REASON_DONE;
+
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                eq(configRequest))).thenReturn(clientId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockSubscribeSession);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanSubscribeSession> subscribeSession = ArgumentCaptor
+                .forClass(WifiNanSubscribeSession.class);
+
+        // (1) connect successfully
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
+
+        // (2) subscribe: successfully - then terminated
+        mDut.subscribe(subscribeConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).subscribe(eq(clientId), eq(subscribeConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        sessionProxyCallback.getValue().onSessionTerminated(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSubscribeStarted(subscribeSession.capture());
+        inOrder.verify(mockSessionCallback).onSessionTerminated(reason);
+
+        // (3) failure when trying to update: NOP
+        subscribeSession.getValue().updateSubscribe(subscribeConfig);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockSubscribeSession);
+    }
 
     /*
      * ConfigRequest Tests
      */
 
     @Test
+    public void testConfigRequestBuilderDefaults() {
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        collector.checkThat("mClusterHigh", ConfigRequest.CLUSTER_ID_MAX,
+                equalTo(configRequest.mClusterHigh));
+        collector.checkThat("mClusterLow", ConfigRequest.CLUSTER_ID_MIN,
+                equalTo(configRequest.mClusterLow));
+        collector.checkThat("mMasterPreference", 0,
+                equalTo(configRequest.mMasterPreference));
+        collector.checkThat("mSupport5gBand", false, equalTo(configRequest.mSupport5gBand));
+        collector.checkThat("mEnableIdentityChangeCallback", false,
+                equalTo(configRequest.mEnableIdentityChangeCallback));
+    }
+
+    @Test
     public void testConfigRequestBuilder() {
         final int clusterHigh = 100;
         final int clusterLow = 5;
         final int masterPreference = 55;
         final boolean supportBand5g = true;
+        final boolean enableIdentityChangeCallback = true;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
                 .setClusterLow(clusterLow).setMasterPreference(masterPreference)
-                .setSupport5gBand(supportBand5g).build();
+                .setSupport5gBand(supportBand5g)
+                .setEnableIdentityChangeCallback(enableIdentityChangeCallback).build();
 
         collector.checkThat("mClusterHigh", clusterHigh, equalTo(configRequest.mClusterHigh));
         collector.checkThat("mClusterLow", clusterLow, equalTo(configRequest.mClusterLow));
         collector.checkThat("mMasterPreference", masterPreference,
                 equalTo(configRequest.mMasterPreference));
         collector.checkThat("mSupport5gBand", supportBand5g, equalTo(configRequest.mSupport5gBand));
+        collector.checkThat("mEnableIdentityChangeCallback", enableIdentityChangeCallback,
+                equalTo(configRequest.mEnableIdentityChangeCallback));
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderMasterPrefNegative() {
-        thrown.expect(IllegalArgumentException.class);
         ConfigRequest.Builder builder = new ConfigRequest.Builder();
         builder.setMasterPreference(-1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderMasterPrefReserved1() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setMasterPreference(1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderMasterPrefReserved255() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setMasterPreference(255);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderMasterPrefTooLarge() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setMasterPreference(256);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderClusterLowNegative() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setClusterLow(-1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderClusterHighNegative() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setClusterHigh(-1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderClusterLowAboveMax() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setClusterLow(ConfigRequest.CLUSTER_ID_MAX + 1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderClusterHighAboveMax() {
-        thrown.expect(IllegalArgumentException.class);
         new ConfigRequest.Builder().setClusterHigh(ConfigRequest.CLUSTER_ID_MAX + 1);
     }
 
-    @Test
+    @Test(expected = IllegalArgumentException.class)
     public void testConfigRequestBuilderClusterLowLargerThanHigh() {
-        thrown.expect(IllegalArgumentException.class);
-        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterLow(100)
-                .setClusterHigh(5).build();
+        new ConfigRequest.Builder().setClusterLow(100).setClusterHigh(5).build();
     }
 
     @Test
@@ -127,10 +625,12 @@
         final int clusterLow = 25;
         final int masterPreference = 177;
         final boolean supportBand5g = true;
+        final boolean enableIdentityChangeCallback = true;
 
         ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
                 .setClusterLow(clusterLow).setMasterPreference(masterPreference)
-                .setSupport5gBand(supportBand5g).build();
+                .setSupport5gBand(supportBand5g)
+                .setEnableIdentityChangeCallback(enableIdentityChangeCallback).build();
 
         Parcel parcelW = Parcel.obtain();
         configRequest.writeToParcel(parcelW, 0);
@@ -146,237 +646,308 @@
     }
 
     /*
-     * SubscribeData Tests
+     * SubscribeConfig Tests
      */
 
     @Test
-    public void testSubscribeDataBuilder() {
+    public void testSubscribeConfigBuilderDefaults() {
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        collector.checkThat("mServiceName", subscribeConfig.mServiceName, equalTo(null));
+        collector.checkThat("mServiceSpecificInfoLength",
+                subscribeConfig.mServiceSpecificInfoLength, equalTo(0));
+        collector.checkThat("mTxFilterLength", subscribeConfig.mTxFilterLength, equalTo(0));
+        collector.checkThat("mRxFilterLength", subscribeConfig.mRxFilterLength, equalTo(0));
+        collector.checkThat("mSubscribeType", subscribeConfig.mSubscribeType,
+                equalTo(SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE));
+        collector.checkThat("mSubscribeCount", subscribeConfig.mSubscribeCount, equalTo(0));
+        collector.checkThat("mTtlSec", subscribeConfig.mTtlSec, equalTo(0));
+        collector.checkThat("mMatchStyle", subscribeConfig.mMatchStyle,
+                equalTo(SubscribeConfig.MATCH_STYLE_ALL));
+        collector.checkThat("mEnableTerminateNotification",
+                subscribeConfig.mEnableTerminateNotification, equalTo(true));
+    }
+
+    @Test
+    public void testSubscribeConfigBuilder() {
         final String serviceName = "some_service_or_other";
         final String serviceSpecificInfo = "long arbitrary string with some info";
         final byte[] txFilter = {
                 0, 1, 16, 1, 22 };
         final byte[] rxFilter = {
                 1, 127, 0, 1, -5, 1, 22 };
-
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
-                .setRxFilter(rxFilter, rxFilter.length).build();
-
-        collector.checkThat("mServiceName", serviceName, equalTo(subscribeData.mServiceName));
-        String mServiceSpecificInfo = new String(subscribeData.mServiceSpecificInfo, 0,
-                subscribeData.mServiceSpecificInfoLength);
-        collector.checkThat("mServiceSpecificInfo",
-                utilAreArraysEqual(serviceSpecificInfo.getBytes(), serviceSpecificInfo.length(),
-                        subscribeData.mServiceSpecificInfo,
-                        subscribeData.mServiceSpecificInfoLength),
-                equalTo(true));
-        collector.checkThat("mTxFilter", utilAreArraysEqual(txFilter, txFilter.length,
-                subscribeData.mTxFilter, subscribeData.mTxFilterLength), equalTo(true));
-        collector.checkThat("mRxFilter", utilAreArraysEqual(rxFilter, rxFilter.length,
-                subscribeData.mRxFilter, subscribeData.mRxFilterLength), equalTo(true));
-    }
-
-    @Test
-    public void testSubscribeDataParcel() {
-        final String serviceName = "some_service_or_other";
-        final String serviceSpecificInfo = "long arbitrary string with some info";
-        final byte[] txFilter = {
-                0, 1, 16, 1, 22 };
-        final byte[] rxFilter = {
-                1, 127, 0, 1, -5, 1, 22 };
-
-        SubscribeData subscribeData = new SubscribeData.Builder().setServiceName(serviceName)
-                .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
-                .setTxFilter(rxFilter, rxFilter.length).build();
-
-        Parcel parcelW = Parcel.obtain();
-        subscribeData.writeToParcel(parcelW, 0);
-        byte[] bytes = parcelW.marshall();
-        parcelW.recycle();
-
-        Parcel parcelR = Parcel.obtain();
-        parcelR.unmarshall(bytes, 0, bytes.length);
-        parcelR.setDataPosition(0);
-        SubscribeData rereadSubscribeData = SubscribeData.CREATOR.createFromParcel(parcelR);
-
-        assertEquals(subscribeData, rereadSubscribeData);
-    }
-
-    /*
-     * SubscribeSettings Tests
-     */
-
-    @Test
-    public void testSubscribeSettingsBuilder() {
-        final int subscribeType = SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE;
+        final int subscribeType = SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE;
         final int subscribeCount = 10;
         final int subscribeTtl = 15;
+        final int matchStyle = SubscribeConfig.MATCH_STYLE_FIRST_ONLY;
+        final boolean enableTerminateNotification = false;
 
-        SubscribeSettings subscribeSetting = new SubscribeSettings.Builder()
-                .setSubscribeType(subscribeType).setSubscribeCount(subscribeCount)
-                .setTtlSec(subscribeTtl).build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
+                .setRxFilter(rxFilter, rxFilter.length).setSubscribeType(subscribeType)
+                .setSubscribeCount(subscribeCount).setTtlSec(subscribeTtl).setMatchStyle(matchStyle)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
+        collector.checkThat("mServiceName", serviceName, equalTo(subscribeConfig.mServiceName));
+        collector.checkThat("mServiceSpecificInfo",
+                utilAreArraysEqual(serviceSpecificInfo.getBytes(), serviceSpecificInfo.length(),
+                        subscribeConfig.mServiceSpecificInfo,
+                        subscribeConfig.mServiceSpecificInfoLength),
+                equalTo(true));
+        collector.checkThat("mTxFilter", utilAreArraysEqual(txFilter, txFilter.length,
+                subscribeConfig.mTxFilter, subscribeConfig.mTxFilterLength), equalTo(true));
+        collector.checkThat("mRxFilter", utilAreArraysEqual(rxFilter, rxFilter.length,
+                subscribeConfig.mRxFilter, subscribeConfig.mRxFilterLength), equalTo(true));
         collector.checkThat("mSubscribeType", subscribeType,
-                equalTo(subscribeSetting.mSubscribeType));
+                equalTo(subscribeConfig.mSubscribeType));
         collector.checkThat("mSubscribeCount", subscribeCount,
-                equalTo(subscribeSetting.mSubscribeCount));
-        collector.checkThat("mTtlSec", subscribeTtl, equalTo(subscribeSetting.mTtlSec));
+                equalTo(subscribeConfig.mSubscribeCount));
+        collector.checkThat("mTtlSec", subscribeTtl, equalTo(subscribeConfig.mTtlSec));
+        collector.checkThat("mMatchStyle", matchStyle, equalTo(subscribeConfig.mMatchStyle));
+        collector.checkThat("mEnableTerminateNotification", enableTerminateNotification,
+                equalTo(subscribeConfig.mEnableTerminateNotification));
     }
 
     @Test
-    public void testSubscribeSettingsBuilderBadSubscribeType() {
-        thrown.expect(IllegalArgumentException.class);
-        new SubscribeSettings.Builder().setSubscribeType(10);
-    }
-
-    @Test
-    public void testSubscribeSettingsBuilderNegativeCount() {
-        thrown.expect(IllegalArgumentException.class);
-        new SubscribeSettings.Builder().setSubscribeCount(-1);
-    }
-
-    @Test
-    public void testSubscribeSettingsBuilderNegativeTtl() {
-        thrown.expect(IllegalArgumentException.class);
-        new SubscribeSettings.Builder().setTtlSec(-100);
-    }
-
-    @Test
-    public void testSubscribeSettingsParcel() {
-        final int subscribeType = SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE;
+    public void testSubscribeConfigParcel() {
+        final String serviceName = "some_service_or_other";
+        final String serviceSpecificInfo = "long arbitrary string with some info";
+        final byte[] txFilter = {
+                0, 1, 16, 1, 22 };
+        final byte[] rxFilter = {
+                1, 127, 0, 1, -5, 1, 22 };
+        final int subscribeType = SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE;
         final int subscribeCount = 10;
         final int subscribeTtl = 15;
+        final int matchStyle = SubscribeConfig.MATCH_STYLE_FIRST_ONLY;
+        final boolean enableTerminateNotification = true;
 
-        SubscribeSettings subscribeSetting = new SubscribeSettings.Builder()
-                .setSubscribeType(subscribeType).setSubscribeCount(subscribeCount)
-                .setTtlSec(subscribeTtl).build();
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
+                .setTxFilter(rxFilter, rxFilter.length).setSubscribeType(subscribeType)
+                .setSubscribeCount(subscribeCount).setTtlSec(subscribeTtl).setMatchStyle(matchStyle)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
         Parcel parcelW = Parcel.obtain();
-        subscribeSetting.writeToParcel(parcelW, 0);
+        subscribeConfig.writeToParcel(parcelW, 0);
         byte[] bytes = parcelW.marshall();
         parcelW.recycle();
 
         Parcel parcelR = Parcel.obtain();
         parcelR.unmarshall(bytes, 0, bytes.length);
         parcelR.setDataPosition(0);
-        SubscribeSettings rereadSubscribeSettings = SubscribeSettings.CREATOR
-                .createFromParcel(parcelR);
+        SubscribeConfig rereadSubscribeConfig = SubscribeConfig.CREATOR.createFromParcel(parcelR);
 
-        assertEquals(subscribeSetting, rereadSubscribeSettings);
+        assertEquals(subscribeConfig, rereadSubscribeConfig);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderBadSubscribeType() {
+        new SubscribeConfig.Builder().setSubscribeType(10);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderNegativeCount() {
+        new SubscribeConfig.Builder().setSubscribeCount(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderNegativeTtl() {
+        new SubscribeConfig.Builder().setTtlSec(-100);
+    }
+
+    /**
+     * Validate that a bad match style configuration throws an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderBadMatchStyle() {
+        new SubscribeConfig.Builder().setMatchStyle(10);
     }
 
     /*
-     * PublishData Tests
+     * PublishConfig Tests
      */
 
     @Test
-    public void testPublishDataBuilder() {
+    public void testPublishConfigBuilderDefaults() {
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        collector.checkThat("mServiceName", publishConfig.mServiceName, equalTo(null));
+        collector.checkThat("mServiceSpecificInfoLength", publishConfig.mServiceSpecificInfoLength,
+                equalTo(0));
+        collector.checkThat("mTxFilterLength", publishConfig.mTxFilterLength, equalTo(0));
+        collector.checkThat("mRxFilterLength", publishConfig.mRxFilterLength, equalTo(0));
+        collector.checkThat("mPublishType", publishConfig.mPublishType,
+                equalTo(PublishConfig.PUBLISH_TYPE_UNSOLICITED));
+        collector.checkThat("mPublishCount", publishConfig.mPublishCount, equalTo(0));
+        collector.checkThat("mTtlSec", publishConfig.mTtlSec, equalTo(0));
+        collector.checkThat("mEnableTerminateNotification",
+                publishConfig.mEnableTerminateNotification, equalTo(true));
+    }
+
+    @Test
+    public void testPublishConfigBuilder() {
         final String serviceName = "some_service_or_other";
         final String serviceSpecificInfo = "long arbitrary string with some info";
         final byte[] txFilter = {
                 0, 1, 16, 1, 22 };
         final byte[] rxFilter = {
                 1, 127, 0, 1, -5, 1, 22 };
+        final int publishType = PublishConfig.PUBLISH_TYPE_SOLICITED;
+        final int publishCount = 10;
+        final int publishTtl = 15;
+        final boolean enableTerminateNotification = false;
 
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
                 .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
-                .setRxFilter(rxFilter, rxFilter.length).build();
+                .setRxFilter(rxFilter, rxFilter.length).setPublishType(publishType)
+                .setPublishCount(publishCount).setTtlSec(publishTtl)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
-        collector.checkThat("mServiceName", serviceName, equalTo(publishData.mServiceName));
-        String mServiceSpecificInfo = new String(publishData.mServiceSpecificInfo, 0,
-                publishData.mServiceSpecificInfoLength);
+        collector.checkThat("mServiceName", serviceName, equalTo(publishConfig.mServiceName));
         collector.checkThat("mServiceSpecificInfo",
                 utilAreArraysEqual(serviceSpecificInfo.getBytes(), serviceSpecificInfo.length(),
-                        publishData.mServiceSpecificInfo, publishData.mServiceSpecificInfoLength),
+                        publishConfig.mServiceSpecificInfo,
+                        publishConfig.mServiceSpecificInfoLength),
                 equalTo(true));
         collector.checkThat("mTxFilter", utilAreArraysEqual(txFilter, txFilter.length,
-                publishData.mTxFilter, publishData.mTxFilterLength), equalTo(true));
+                publishConfig.mTxFilter, publishConfig.mTxFilterLength), equalTo(true));
         collector.checkThat("mRxFilter", utilAreArraysEqual(rxFilter, rxFilter.length,
-                publishData.mRxFilter, publishData.mRxFilterLength), equalTo(true));
+                publishConfig.mRxFilter, publishConfig.mRxFilterLength), equalTo(true));
+        collector.checkThat("mPublishType", publishType, equalTo(publishConfig.mPublishType));
+        collector.checkThat("mPublishCount", publishCount, equalTo(publishConfig.mPublishCount));
+        collector.checkThat("mTtlSec", publishTtl, equalTo(publishConfig.mTtlSec));
+        collector.checkThat("mEnableTerminateNotification", enableTerminateNotification,
+                equalTo(publishConfig.mEnableTerminateNotification));
     }
 
     @Test
-    public void testPublishDataParcel() {
+    public void testPublishConfigParcel() {
         final String serviceName = "some_service_or_other";
         final String serviceSpecificInfo = "long arbitrary string with some info";
         final byte[] txFilter = {
                 0, 1, 16, 1, 22 };
         final byte[] rxFilter = {
                 1, 127, 0, 1, -5, 1, 22 };
+        final int publishType = PublishConfig.PUBLISH_TYPE_SOLICITED;
+        final int publishCount = 10;
+        final int publishTtl = 15;
+        final boolean enableTerminateNotification = false;
 
-        PublishData publishData = new PublishData.Builder().setServiceName(serviceName)
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
                 .setServiceSpecificInfo(serviceSpecificInfo).setTxFilter(txFilter, txFilter.length)
-                .setTxFilter(rxFilter, rxFilter.length).build();
+                .setTxFilter(rxFilter, rxFilter.length).setPublishType(publishType)
+                .setPublishCount(publishCount).setTtlSec(publishTtl)
+                .setEnableTerminateNotification(enableTerminateNotification).build();
 
         Parcel parcelW = Parcel.obtain();
-        publishData.writeToParcel(parcelW, 0);
+        publishConfig.writeToParcel(parcelW, 0);
         byte[] bytes = parcelW.marshall();
         parcelW.recycle();
 
         Parcel parcelR = Parcel.obtain();
         parcelR.unmarshall(bytes, 0, bytes.length);
         parcelR.setDataPosition(0);
-        PublishData rereadPublishData = PublishData.CREATOR.createFromParcel(parcelR);
+        PublishConfig rereadPublishConfig = PublishConfig.CREATOR.createFromParcel(parcelR);
 
-        assertEquals(publishData, rereadPublishData);
+        assertEquals(publishConfig, rereadPublishConfig);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishConfigBuilderBadPublishType() {
+        new PublishConfig.Builder().setPublishType(5);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishConfigBuilderNegativeCount() {
+        new PublishConfig.Builder().setPublishCount(-4);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishConfigBuilderNegativeTtl() {
+        new PublishConfig.Builder().setTtlSec(-10);
     }
 
     /*
-     * PublishSettings Tests
+     * Ranging tests
      */
 
+    /**
+     * Validate ranging + success flow: (1) connect, (2) create a (publish) session, (3) start
+     * ranging, (4) ranging success callback, (5) ranging aborted callback ignored (since
+     * listener removed).
+     */
     @Test
-    public void testPublishSettingsBuilder() {
-        final int publishType = PublishSettings.PUBLISH_TYPE_SOLICITED;
-        final int publishCount = 10;
-        final int publishTtl = 15;
+    public void testRangingCallbacks() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final int rangingId = 3482;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+        final RttManager.RttParams rttParams = new RttManager.RttParams();
+        rttParams.deviceType = RttManager.RTT_PEER_NAN;
+        rttParams.bssid = Integer.toString(1234);
+        final RttManager.RttResult rttResults = new RttManager.RttResult();
+        rttResults.distance = 10;
 
-        PublishSettings publishSetting = new PublishSettings.Builder().setPublishType(publishType)
-                .setPublishCount(publishCount).setTtlSec(publishTtl).build();
+        when(mockNanService.connect(any(IBinder.class), any(IWifiNanEventCallback.class),
+                eq(configRequest))).thenReturn(clientId);
+        when(mockNanService.startRanging(anyInt(), anyInt(),
+                any(RttManager.ParcelableRttParams.class))).thenReturn(rangingId);
 
-        collector.checkThat("mPublishType", publishType, equalTo(publishSetting.mPublishType));
-        collector.checkThat("mPublishCount", publishCount, equalTo(publishSetting.mPublishCount));
-        collector.checkThat("mTtlSec", publishTtl, equalTo(publishSetting.mTtlSec));
-    }
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession, mockRttListener);
+        ArgumentCaptor<IWifiNanEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanEventCallback.class);
+        ArgumentCaptor<IWifiNanSessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiNanSessionCallback.class);
+        ArgumentCaptor<WifiNanPublishSession> publishSession = ArgumentCaptor
+                .forClass(WifiNanPublishSession.class);
+        ArgumentCaptor<RttManager.ParcelableRttParams> rttParamCaptor = ArgumentCaptor
+                .forClass(RttManager.ParcelableRttParams.class);
+        ArgumentCaptor<RttManager.RttResult[]> rttResultsCaptor = ArgumentCaptor
+                .forClass(RttManager.RttResult[].class);
 
-    @Test
-    public void testPublishSettingsBuilderBadPublishType() {
-        thrown.expect(IllegalArgumentException.class);
-        new PublishSettings.Builder().setPublishType(5);
-    }
+        // (1) connect successfully
+        mDut.connect(mMockLooper.getLooper(), mockCallback, configRequest);
+        inOrder.verify(mockNanService).connect(any(IBinder.class), clientProxyCallback.capture(),
+                eq(configRequest));
+        clientProxyCallback.getValue().onConnectSuccess();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onConnectSuccess();
 
-    @Test
-    public void testPublishSettingsBuilderNegativeCount() {
-        thrown.expect(IllegalArgumentException.class);
-        new PublishSettings.Builder().setPublishCount(-4);
-    }
+        // (2) publish successfully
+        mDut.publish(publishConfig, mockSessionCallback);
+        inOrder.verify(mockNanService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
 
-    @Test
-    public void testPublishSettingsBuilderNegativeTtl() {
-        thrown.expect(IllegalArgumentException.class);
-        new PublishSettings.Builder().setTtlSec(-10);
-    }
+        // (3) start ranging
+        publishSession.getValue().startRanging(new RttManager.RttParams[]{rttParams},
+                mockRttListener);
+        inOrder.verify(mockNanService).startRanging(eq(clientId), eq(sessionId),
+                rttParamCaptor.capture());
+        collector.checkThat("RttParams.deviceType", rttParams.deviceType,
+                equalTo(rttParamCaptor.getValue().mParams[0].deviceType));
+        collector.checkThat("RttParams.bssid", rttParams.bssid,
+                equalTo(rttParamCaptor.getValue().mParams[0].bssid));
 
-    @Test
-    public void testPublishSettingsParcel() {
-        final int publishType = PublishSettings.PUBLISH_TYPE_SOLICITED;
-        final int publishCount = 10;
-        final int publishTtl = 15;
+        // (4) ranging success callback
+        clientProxyCallback.getValue().onRangingSuccess(rangingId,
+                new RttManager.ParcelableRttResults(new RttManager.RttResult[] { rttResults }));
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockRttListener).onSuccess(rttResultsCaptor.capture());
+        collector.checkThat("RttResult.distance", rttResults.distance,
+                equalTo(rttResultsCaptor.getValue()[0].distance));
 
-        PublishSettings configSetting = new PublishSettings.Builder().setPublishType(publishType)
-                .setPublishCount(publishCount).setTtlSec(publishTtl).build();
+        // (5) ranging aborted callback (should be ignored since listener cleared on first callback)
+        clientProxyCallback.getValue().onRangingAborted(rangingId);
+        mMockLooper.dispatchAll();
 
-        Parcel parcelW = Parcel.obtain();
-        configSetting.writeToParcel(parcelW, 0);
-        byte[] bytes = parcelW.marshall();
-        parcelW.recycle();
-
-        Parcel parcelR = Parcel.obtain();
-        parcelR.unmarshall(bytes, 0, bytes.length);
-        parcelR.setDataPosition(0);
-        PublishSettings rereadPublishSettings = PublishSettings.CREATOR.createFromParcel(parcelR);
-
-        assertEquals(configSetting, rereadPublishSettings);
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockNanService,
+                mockPublishSession, mockRttListener);
     }
 
     /*
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanRttStateManagerTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanRttStateManagerTest.java
new file mode 100644
index 0000000..35a600a
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanRttStateManagerTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.nan;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.IRttManager;
+import android.net.wifi.RttManager;
+import android.os.Handler;
+import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.util.test.BidirectionalAsyncChannelServer;
+import android.os.test.TestLooper;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test harness for WifiNanManager class.
+ */
+@SmallTest
+public class WifiNanRttStateManagerTest {
+    private WifiNanRttStateManager mDut;
+    private TestLooper mTestLooper;
+
+    @Mock
+    private Context mMockContext;
+
+    @Mock
+    private Handler mMockHandler;
+
+    @Mock
+    private IRttManager mMockRttService;
+
+    @Rule
+    public ErrorCollector collector = new ErrorCollector();
+
+    /**
+     * Initialize mocks.
+     */
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mDut = new WifiNanRttStateManager();
+        mTestLooper = new TestLooper();
+        BidirectionalAsyncChannelServer server = new BidirectionalAsyncChannelServer(
+                mMockContext, mTestLooper.getLooper(), mMockHandler);
+        when(mMockRttService.getMessenger()).thenReturn(server.getMessenger());
+
+        mDut.startWithRttService(mMockContext, mTestLooper.getLooper(), mMockRttService);
+    }
+
+    /**
+     * Validates that startRanging flow works: (1) start ranging, (2) get success callback - pass
+     * to client (while nulling BSSID info), (3) get fail callback - ignored (since client
+     * cleaned-out after first callback).
+     */
+    @Test
+    public void testStartRanging() throws Exception {
+        final int rangingId = 1234;
+        WifiNanClientState mockClient = mock(WifiNanClientState.class);
+        RttManager.RttParams[] params = new RttManager.RttParams[1];
+        params[0] = new RttManager.RttParams();
+        RttManager.ParcelableRttResults results =
+                new RttManager.ParcelableRttResults(new RttManager.RttResult[2]);
+        results.mResults[0] = new RttManager.RttResult();
+        results.mResults[0].bssid = "something non-null";
+        results.mResults[1] = new RttManager.RttResult();
+        results.mResults[1].bssid = "really really non-null";
+
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+        ArgumentCaptor<RttManager.ParcelableRttResults> rttResultsCaptor =
+                ArgumentCaptor.forClass(RttManager.ParcelableRttResults.class);
+
+        InOrder inOrder = inOrder(mMockHandler, mockClient);
+
+        // (1) start ranging
+        mDut.startRanging(rangingId, mockClient, params);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mMockHandler).handleMessage(messageCaptor.capture());
+        Message msg = messageCaptor.getValue();
+        collector.checkThat("msg.what=RttManager.CMD_OP_START_RANGING", msg.what,
+                equalTo(RttManager.CMD_OP_START_RANGING));
+        collector.checkThat("rangingId", msg.arg2, equalTo(rangingId));
+        collector.checkThat("RTT params", ((RttManager.ParcelableRttParams) msg.obj).mParams,
+                equalTo(params));
+
+        // (2) get success callback - pass to client
+        Message successMessage = Message.obtain();
+        successMessage.what = RttManager.CMD_OP_SUCCEEDED;
+        successMessage.arg2 = rangingId;
+        successMessage.obj = results;
+        msg.replyTo.send(successMessage);
+        mTestLooper.dispatchAll();
+        inOrder.verify(mockClient).onRangingSuccess(eq(rangingId), rttResultsCaptor.capture());
+        collector.checkThat("ParcelableRttResults object", results,
+                equalTo(rttResultsCaptor.getValue()));
+        collector.checkThat("RttResults[0].bssid null",
+                rttResultsCaptor.getValue().mResults[0].bssid, nullValue());
+        collector.checkThat("RttResults[1].bssid null",
+                rttResultsCaptor.getValue().mResults[1].bssid, nullValue());
+
+        // (3) get fail callback - ignored
+        Message failMessage = Message.obtain();
+        failMessage.what = RttManager.CMD_OP_ABORTED;
+        failMessage.arg2 = rangingId;
+        msg.replyTo.send(failMessage);
+        mTestLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mMockHandler, mockClient);
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/nan/WifiNanServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanServiceImplTest.java
new file mode 100644
index 0000000..2d734f7
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/nan/WifiNanServiceImplTest.java
@@ -0,0 +1,479 @@
+/*
+ * 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.nan;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.RttManager;
+import android.net.wifi.nan.ConfigRequest;
+import android.net.wifi.nan.IWifiNanEventCallback;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.os.IBinder;
+import android.os.Looper;
+import android.test.suitebuilder.annotation.SmallTest;
+import andr