Merge changes I5d55b287,I3d39c193,If60282ad,I132fd395,I09a7f6f5

* changes:
  WifiLog: add CheckReturnValue annotation
  logging: add convenience APIs for literal messages
  WifiDiagnostics: migrate to chained logging API
  logging: add chained logging API
  WifiDiagnostics: remove getAllRingBufferData()
diff --git a/service/java/com/android/server/wifi/ScanDetail.java b/service/java/com/android/server/wifi/ScanDetail.java
index 23e52db..dba8ab3 100644
--- a/service/java/com/android/server/wifi/ScanDetail.java
+++ b/service/java/com/android/server/wifi/ScanDetail.java
@@ -50,7 +50,7 @@
                 networkDetail.getAnqpDomainID(), networkDetail.getOsuProviders(),
                 caps, level, frequency, tsf);
         mSeen = System.currentTimeMillis();
-        //mScanResult.seen = mSeen;
+        mScanResult.seen = mSeen;
         mScanResult.channelWidth = networkDetail.getChannelWidth();
         mScanResult.centerFreq0 = networkDetail.getCenterfreq0();
         mScanResult.centerFreq1 = networkDetail.getCenterfreq1();
@@ -70,7 +70,7 @@
         mNetworkDetail = null;
         mScanResult = new ScanResult(wifiSsid, bssid, 0L, -1, null, caps, level, frequency, tsf);
         mSeen = seen;
-        //mScanResult.seen = mSeen;
+        mScanResult.seen = mSeen;
         mScanResult.channelWidth = 0;
         mScanResult.centerFreq0 = 0;
         mScanResult.centerFreq1 = 0;
diff --git a/service/java/com/android/server/wifi/WifiConfigManagerNew.java b/service/java/com/android/server/wifi/WifiConfigManagerNew.java
index 922405f..2dc77cd 100644
--- a/service/java/com/android/server/wifi/WifiConfigManagerNew.java
+++ b/service/java/com/android/server/wifi/WifiConfigManagerNew.java
@@ -40,6 +40,7 @@
 import android.util.LocalLog;
 import android.util.Log;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.wifi.util.ScanResultUtil;
@@ -51,6 +52,7 @@
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -139,6 +141,17 @@
     @VisibleForTesting
     public static final int SCAN_CACHE_ENTRIES_TRIM_SIZE = 128;
     /**
+     * Link networks only if they have less than this number of scan cache entries.
+     */
+    @VisibleForTesting
+    public static final int LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES = 6;
+    /**
+     * Link networks only if the bssid in scan results for the networks match in the first
+     * 16 ASCII chars in the bssid string. For example = "af:de:56;34:15:7"
+     */
+    @VisibleForTesting
+    public static final int LINK_CONFIGURATION_BSSID_MATCH_LENGTH = 16;
+    /**
      * Flags to be passed in for |canModifyNetwork| to decide if the change is minor and can
      * bypass the lockdown checks.
      */
@@ -202,7 +215,15 @@
      * The SSIDs are encoded in a String as per definition of WifiConfiguration.SSID field.
      */
     private final Set<String> mDeletedEphemeralSSIDs;
-
+    /**
+     * Flag to indicate if only networks with the same psk should be linked.
+     * TODO(b/30706406): Remove this flag if unused.
+     */
+    private final boolean mOnlyLinkSameCredentialConfigurations;
+    /**
+     * Number of channels to scan for during partial scans initiated while connected.
+     */
+    private final int mMaxNumActiveChannelsForPartialScans;
     /**
      * Verbose logging flag. Toggled by developer options.
      */
@@ -233,7 +254,12 @@
 
         mConfiguredNetworks = new ConfigurationMap(userManager);
         mScanDetailCaches = new ConcurrentHashMap<>(16, 0.75f, 2);
-        mDeletedEphemeralSSIDs = new HashSet<String>();
+        mDeletedEphemeralSSIDs = new HashSet<>();
+
+        mOnlyLinkSameCredentialConfigurations = mContext.getResources().getBoolean(
+                R.bool.config_wifi_only_link_same_credential_configurations);
+        mMaxNumActiveChannelsForPartialScans = mContext.getResources().getInteger(
+                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels);
     }
 
     /**
@@ -403,7 +429,12 @@
         if (internalConfig != null) {
             return internalConfig;
         }
-        return mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey());
+        internalConfig = mConfiguredNetworks.getByConfigKeyForCurrentUser(config.configKey());
+        if (internalConfig == null) {
+            Log.e(TAG, "Cannot find network with networkId " + config.networkId
+                    + " or configKey " + config.configKey());
+        }
+        return internalConfig;
     }
 
     /**
@@ -411,7 +442,24 @@
      * provided network ID in our database.
      */
     private WifiConfiguration getInternalConfiguredNetwork(int networkId) {
-        return mConfiguredNetworks.getForCurrentUser(networkId);
+        WifiConfiguration internalConfig = mConfiguredNetworks.getForCurrentUser(networkId);
+        if (internalConfig == null) {
+            Log.e(TAG, "Cannot find network with networkId " + networkId);
+        }
+        return internalConfig;
+    }
+
+    /**
+     * Helper method to retrieve the internal WifiConfiguration object corresponding to the
+     * provided configKey in our database.
+     */
+    private WifiConfiguration getInternalConfiguredNetwork(String configKey) {
+        WifiConfiguration internalConfig =
+                mConfiguredNetworks.getByConfigKeyForCurrentUser(configKey);
+        if (internalConfig == null) {
+            Log.e(TAG, "Cannot find network with configKey " + configKey);
+        }
+        return internalConfig;
     }
 
     /**
@@ -870,7 +918,6 @@
     public boolean removeNetwork(int networkId) {
         WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
-            Log.e(TAG, "Cannot find network with networkId " + networkId);
             return false;
         }
         if (!removeNetworkInternal(config)) {
@@ -1000,7 +1047,7 @@
      *
      * Each network has 2 status:
      * 1. NetworkSelectionStatus: This is internal selection status of the network. This is used
-     * for temporarily disabling a network for QNS.
+     * for temporarily disabling a network for Network Selector.
      * 2. Status: This is the exposed status for a network. This is mostly set by
      * the public API's {@link WifiManager#enableNetwork(int, boolean)} &
      * {@link WifiManager#disableNetwork(int)}.
@@ -1012,7 +1059,6 @@
     public boolean updateNetworkSelectionStatus(int networkId, int reason) {
         WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
-            Log.e(TAG, "Cannot find network with networkId " + networkId);
             return false;
         }
         return updateNetworkSelectionStatus(config, reason);
@@ -1061,7 +1107,6 @@
     public boolean tryEnableNetwork(int networkId) {
         WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
-            Log.e(TAG, "Cannot find network with networkId " + networkId);
             return false;
         }
         return tryEnableNetwork(config);
@@ -1077,7 +1122,6 @@
     public boolean enableNetwork(int networkId, int uid) {
         WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
-            Log.e(TAG, "Cannot find network with networkId " + networkId);
             return false;
         }
         if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
@@ -1099,7 +1143,6 @@
     public boolean disableNetwork(int networkId, int uid) {
         WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
-            Log.e(TAG, "Cannot find network with networkId " + networkId);
             return false;
         }
         if (!canModifyNetwork(config, uid, DISALLOW_LOCKDOWN_CHECK_BYPASS)) {
@@ -1123,7 +1166,6 @@
     public boolean checkAndUpdateLastConnectUid(int networkId, int uid) {
         WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
-            Log.e(TAG, "Cannot find network with networkId " + networkId);
             return false;
         }
         if (!canModifyNetwork(config, uid, ALLOW_LOCKDOWN_CHECK_BYPASS)) {
@@ -1149,7 +1191,6 @@
     public boolean updateNetworkAfterConnect(int networkId) {
         WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
-            Log.e(TAG, "Cannot find network with networkId " + networkId);
             return false;
         }
         config.lastConnected = mClock.getWallClockMillis();
@@ -1160,13 +1201,75 @@
     }
 
     /**
+     * Set default GW MAC address for the provided network.
+     *
+     * @param networkId  network ID corresponding to the network.
+     * @param macAddress MAC address of the gateway to be set.
+     * @return true if the network was found, false otherwise.
+     */
+    public boolean setNetworkDefaultGwMacAddress(int networkId, String macAddress) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
+        }
+        config.defaultGwMacAddress = macAddress;
+        return true;
+    }
+
+    /**
+     * Helper method to clear the {@link NetworkSelectionStatus#mCandidate},
+     * {@link NetworkSelectionStatus#mCandidateScore} &
+     * {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} for the provided network.
+     *
+     * This is invoked by Network Selector at the start of every selection procedure to clear all
+     * configured networks' scan-result-candidates.
+     *
+     * @param networkId network ID corresponding to the network.
+     * @return true if the network was found, false otherwise.
+     */
+    public boolean clearNetworkCandidateScanResult(int networkId) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
+        }
+        config.getNetworkSelectionStatus().setCandidate(null);
+        config.getNetworkSelectionStatus().setCandidateScore(Integer.MIN_VALUE);
+        config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(false);
+        return true;
+    }
+
+    /**
+     * Helper method to set the {@link NetworkSelectionStatus#mCandidate},
+     * {@link NetworkSelectionStatus#mCandidateScore} &
+     * {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} for the provided network.
+     *
+     * This is invoked by Network Selector when it sees a network during network selection procedure to set the
+     * scan result candidate.
+     *
+     * @param networkId  network ID corresponding to the network.
+     * @param scanResult Candidate ScanResult associated with this network.
+     * @param score      Score assigned to the candidate.
+     * @return true if the network was found, false otherwise.
+     */
+    public boolean setNetworkCandidateScanResult(int networkId, ScanResult scanResult, int score) {
+        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
+        if (config == null) {
+            return false;
+        }
+        config.getNetworkSelectionStatus().setCandidate(scanResult);
+        config.getNetworkSelectionStatus().setCandidateScore(score);
+        // Update the network selection status.
+        config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(true);
+        return true;
+    }
+
+    /**
      * Helper method to get the scan detail cache entry {@link #mScanDetailCaches} for the provided
      * network.
      *
      * @param networkId network ID corresponding to the network.
      * @return existing {@link ScanDetailCache} entry if one exists or null.
      */
-    @VisibleForTesting
     public ScanDetailCache getScanDetailCacheForNetwork(int networkId) {
         return mScanDetailCaches.get(networkId);
     }
@@ -1183,9 +1286,8 @@
         if (config == null) return null;
         ScanDetailCache cache = getScanDetailCacheForNetwork(config.networkId);
         if (cache == null && config.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
-            cache =
-                    new ScanDetailCache(
-                            config, SCAN_CACHE_ENTRIES_MAX_SIZE, SCAN_CACHE_ENTRIES_TRIM_SIZE);
+            cache = new ScanDetailCache(
+                    config, SCAN_CACHE_ENTRIES_MAX_SIZE, SCAN_CACHE_ENTRIES_TRIM_SIZE);
             mScanDetailCaches.put(config.networkId, cache);
         }
         return cache;
@@ -1226,8 +1328,9 @@
         // Add the scan detail to this network's scan detail cache.
         scanDetailCache.put(scanDetail);
 
-        // Since we added a scan result to this configuration, re-attempt linking
-        // TODO: linkConfiguration(config);
+        // Since we added a scan result to this configuration, re-attempt linking.
+        // TODO: Do we really need to do this after every scan result?
+        attemptNetworkLinking(config);
     }
 
     /**
@@ -1277,52 +1380,260 @@
     }
 
     /**
-     * Helper method to clear the {@link NetworkSelectionStatus#mCandidate},
-     * {@link NetworkSelectionStatus#mCandidateScore} &
-     * {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} for the provided network.
+     * Helper method to check if the 2 provided networks can be linked or not.
+     * Networks are considered for linking if:
+     * 1. Share the same GW MAC address.
+     * 2. Scan results for the networks have AP's with MAC address which differ only in the last
+     * nibble.
      *
-     * This is invoked by QNS at the start of every selection procedure to clear all configured
-     * networks' scan-result-candidates.
-     *
-     * @param networkId  network ID corresponding to the network.
-     * @return true if the network was found, false otherwise.
+     * @param network1         WifiConfiguration corresponding to network 1.
+     * @param network2         WifiConfiguration corresponding to network 2.
+     * @param scanDetailCache1 ScanDetailCache entry for network 1.
+     * @param scanDetailCache1 ScanDetailCache entry for network 2.
+     * @return true if the networks should be linked, false if the networks should be unlinked.
      */
-    public boolean clearNetworkCandidateScanResult(int networkId) {
-        WifiConfiguration config = getInternalConfiguredNetwork(networkId);
-        if (config == null) {
-            Log.e(TAG, "Cannot find network with networkId " + networkId);
-            return false;
+    private boolean shouldNetworksBeLinked(
+            WifiConfiguration network1, WifiConfiguration network2,
+            ScanDetailCache scanDetailCache1, ScanDetailCache scanDetailCache2) {
+        // TODO (b/30706406): Link networks only with same passwords if the
+        // |mOnlyLinkSameCredentialConfigurations| flag is set.
+        if (mOnlyLinkSameCredentialConfigurations) {
+            if (!TextUtils.equals(network1.preSharedKey, network2.preSharedKey)) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "shouldNetworksBeLinked unlink due to password mismatch");
+                }
+                return false;
+            }
         }
-        config.getNetworkSelectionStatus().setCandidate(null);
-        config.getNetworkSelectionStatus().setCandidateScore(Integer.MIN_VALUE);
-        config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(false);
+        if (network1.defaultGwMacAddress != null && network2.defaultGwMacAddress != null) {
+            // If both default GW are known, link only if they are equal
+            if (network1.defaultGwMacAddress.equals(network2.defaultGwMacAddress)) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "shouldNetworksBeLinked link due to same gw " + network2.SSID
+                            + " and " + network1.SSID + " GW " + network1.defaultGwMacAddress);
+                }
+                return true;
+            }
+        } else {
+            // We do not know BOTH default gateways hence we will try to link
+            // hoping that WifiConfigurations are indeed behind the same gateway.
+            // once both WifiConfiguration have been tried and thus once both default gateways
+            // are known we will revisit the choice of linking them.
+            if (scanDetailCache1 != null && scanDetailCache2 != null) {
+                for (String abssid : scanDetailCache1.keySet()) {
+                    for (String bbssid : scanDetailCache2.keySet()) {
+                        if (abssid.regionMatches(
+                                true, 0, bbssid, 0, LINK_CONFIGURATION_BSSID_MATCH_LENGTH)) {
+                            // If first 16 ASCII characters of BSSID matches,
+                            // we assume this is a DBDC.
+                            if (mVerboseLoggingEnabled) {
+                                Log.v(TAG, "shouldNetworksBeLinked link due to DBDC BSSID match "
+                                        + network2.SSID + " and " + network1.SSID
+                                        + " bssida " + abssid + " bssidb " + bbssid);
+                            }
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Helper methods to link 2 networks together.
+     *
+     * @param network1 WifiConfiguration corresponding to network 1.
+     * @param network2 WifiConfiguration corresponding to network 2.
+     */
+    private void linkNetworks(WifiConfiguration network1, WifiConfiguration network2) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "linkNetworks will link " + network2.configKey()
+                    + " and " + network1.configKey());
+        }
+        if (network2.linkedConfigurations == null) {
+            network2.linkedConfigurations = new HashMap<>();
+        }
+        if (network1.linkedConfigurations == null) {
+            network1.linkedConfigurations = new HashMap<>();
+        }
+        // TODO (b/30638473): This needs to become a set instead of map, but it will need
+        // public interface changes and need some migration of existing store data.
+        network2.linkedConfigurations.put(network1.configKey(), 1);
+        network1.linkedConfigurations.put(network2.configKey(), 1);
+    }
+
+    /**
+     * Helper methods to unlink 2 networks from each other.
+     *
+     * @param network1 WifiConfiguration corresponding to network 1.
+     * @param network2 WifiConfiguration corresponding to network 2.
+     */
+    private void unlinkNetworks(WifiConfiguration network1, WifiConfiguration network2) {
+        if (network2.linkedConfigurations != null
+                && (network2.linkedConfigurations.get(network1.configKey()) != null)) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "unlinkNetworks un-link " + network1.configKey()
+                        + " from " + network2.configKey());
+            }
+            network2.linkedConfigurations.remove(network1.configKey());
+        }
+        if (network1.linkedConfigurations != null
+                && (network1.linkedConfigurations.get(network2.configKey()) != null)) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "unlinkNetworks un-link " + network2.configKey()
+                        + " from " + network1.configKey());
+            }
+            network1.linkedConfigurations.remove(network2.configKey());
+        }
+    }
+
+    /**
+     * This method runs through all the saved networks and checks if the provided network can be
+     * linked with any of them.
+     *
+     * @param config WifiConfiguration object corresponding to the network that needs to be
+     *               checked for potential links.
+     */
+    private void attemptNetworkLinking(WifiConfiguration config) {
+        // Only link WPA_PSK config.
+        if (!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+            return;
+        }
+        ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(config.networkId);
+        // Ignore configurations with large number of BSSIDs.
+        if (scanDetailCache != null
+                && scanDetailCache.size() > LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES) {
+            return;
+        }
+        for (WifiConfiguration linkConfig : getInternalConfiguredNetworks()) {
+            if (linkConfig.configKey().equals(config.configKey())) {
+                continue;
+            }
+            if (linkConfig.ephemeral) {
+                continue;
+            }
+            // Network Selector will be allowed to dynamically jump from a linked configuration
+            // to another, hence only link configurations that have WPA_PSK security type.
+            if (!linkConfig.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
+                continue;
+            }
+            ScanDetailCache linkScanDetailCache =
+                    getScanDetailCacheForNetwork(linkConfig.networkId);
+            // Ignore configurations with large number of BSSIDs.
+            if (linkScanDetailCache != null
+                    && linkScanDetailCache.size() > LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES) {
+                continue;
+            }
+            // Check if the networks should be linked/unlinked.
+            if (shouldNetworksBeLinked(
+                    config, linkConfig, scanDetailCache, linkScanDetailCache)) {
+                linkNetworks(config, linkConfig);
+            } else {
+                unlinkNetworks(config, linkConfig);
+            }
+        }
+    }
+
+    /**
+     * Helper method to fetch list of channels for a network from the associated ScanResult's cache
+     * and add it to the provided channel as long as the size of the set is less than
+     * |maxChannelSetSize|.
+     *
+     * @param channelSet        Channel set holding all the channels for the network.
+     * @param scanDetailCache   ScanDetailCache entry associated with the network.
+     * @param nowInMillis       current timestamp to be used for age comparison.
+     * @param ageInMillis       only consider scan details whose timestamps are earlier than this
+     *                          value.
+     * @param maxChannelSetSize Maximum number of channels to be added to the set.
+     * @return false if the list is full, true otherwise.
+     */
+    private boolean addToChannelSetForNetworkFromScanDetailCache(
+            Set<Integer> channelSet, ScanDetailCache scanDetailCache,
+            long nowInMillis, long ageInMillis, int maxChannelSetSize) {
+        if (scanDetailCache != null && scanDetailCache.size() > 0) {
+            for (ScanDetail scanDetail : scanDetailCache.values()) {
+                ScanResult result = scanDetail.getScanResult();
+                boolean valid = (nowInMillis - result.seen) < ageInMillis;
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "fetchChannelSetForNetwork has " + result.BSSID + " freq "
+                            + result.frequency + " age " + (nowInMillis - result.seen)
+                            + " ?=" + valid);
+                }
+                if (valid) {
+                    channelSet.add(result.frequency);
+                }
+                if (channelSet.size() >= maxChannelSetSize) {
+                    return false;
+                }
+            }
+        }
         return true;
     }
 
     /**
-     * Helper method to set the {@link NetworkSelectionStatus#mCandidate},
-     * {@link NetworkSelectionStatus#mCandidateScore} &
-     * {@link NetworkSelectionStatus#mSeenInLastQualifiedNetworkSelection} for the provided network.
+     * Retrieve a set of channels on which AP's for the provided network was seen using the
+     * internal ScanResult's cache {@link #mScanDetailCaches}. This is used for initiating partial
+     * scans for the currently connected network.
      *
-     * This is invoked by QNS when it sees a network during network selection procedure to set the
-     * scan result candidate.
-     *
-     * @param networkId  network ID corresponding to the network.
-     * @param scanResult Candidate ScanResult associated with this network.
-     * @param score      Score assigned to the candidate.
-     * @return true if the network was found, false otherwise.
+     * @param networkId   network ID corresponding to the network.
+     * @param ageInMillis only consider scan details whose timestamps are earlier than this value.
+     * @return Set containing the frequencies on which this network was found, null if the network
+     * was not found or there are no associated scan details in the cache.
      */
-    public boolean setNetworkCandidateScanResult(int networkId, ScanResult scanResult, int score) {
+    public Set<Integer> fetchChannelSetForNetworkForPartialScan(int networkId, long ageInMillis) {
         WifiConfiguration config = getInternalConfiguredNetwork(networkId);
         if (config == null) {
-            Log.e(TAG, "Cannot find network with networkId " + networkId);
-            return false;
+            return null;
         }
-        config.getNetworkSelectionStatus().setCandidate(scanResult);
-        config.getNetworkSelectionStatus().setCandidateScore(score);
-        // Update the network selection status.
-        config.getNetworkSelectionStatus().setSeenInLastQualifiedNetworkSelection(true);
-        return true;
+        ScanDetailCache scanDetailCache = getScanDetailCacheForNetwork(networkId);
+        if (scanDetailCache == null && config.linkedConfigurations == null) {
+            Log.i(TAG, "No scan detail and linked configs associated with networkId " + networkId);
+            return null;
+        }
+        if (mVerboseLoggingEnabled) {
+            StringBuilder dbg = new StringBuilder();
+            dbg.append("fetchChannelSetForNetworkForPartialScan ageInMillis ")
+                    .append(ageInMillis)
+                    .append(" for ")
+                    .append(config.configKey())
+                    .append(" max ")
+                    .append(mMaxNumActiveChannelsForPartialScans);
+            if (scanDetailCache != null) {
+                dbg.append(" bssids " + scanDetailCache.size());
+            }
+            if (config.linkedConfigurations != null) {
+                dbg.append(" linked " + config.linkedConfigurations.size());
+            }
+            Log.v(TAG, dbg.toString());
+        }
+        Set<Integer> channelSet = new HashSet<>();
+        long nowInMillis = mClock.getWallClockMillis();
+
+        // First get channels for the network.
+        if (!addToChannelSetForNetworkFromScanDetailCache(
+                channelSet, scanDetailCache, nowInMillis, ageInMillis,
+                mMaxNumActiveChannelsForPartialScans)) {
+            return channelSet;
+        }
+
+        // Now get channels for linked networks.
+        if (config.linkedConfigurations != null) {
+            for (String configKey : config.linkedConfigurations.keySet()) {
+                WifiConfiguration linkedConfig = getInternalConfiguredNetwork(configKey);
+                if (linkedConfig == null) {
+                    continue;
+                }
+                ScanDetailCache linkedScanDetailCache =
+                        getScanDetailCacheForNetwork(linkedConfig.networkId);
+                if (!addToChannelSetForNetworkFromScanDetailCache(
+                        channelSet, linkedScanDetailCache, nowInMillis, ageInMillis,
+                        mMaxNumActiveChannelsForPartialScans)) {
+                    break;
+                }
+            }
+        }
+        return channelSet;
     }
 
     /**
diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 7c20dd6..38d9f77 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -390,7 +390,9 @@
     public boolean setNetworkVariable(int netId, String name, String value) {
         if (TextUtils.isEmpty(name) || TextUtils.isEmpty(value)) return false;
         if (name.equals(WifiConfiguration.pskVarName)
-                || name.equals(WifiEnterpriseConfig.PASSWORD_KEY)) {
+                || name.equals(WifiEnterpriseConfig.PASSWORD_KEY)
+                || name.equals(WifiEnterpriseConfig.IDENTITY_KEY)
+                || name.equals(WifiEnterpriseConfig.ANON_IDENTITY_KEY)) {
             return doBooleanCommandWithoutLogging("SET_NETWORK " + netId + " " + name + " " + value);
         } else {
             return doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + value);
@@ -2151,13 +2153,12 @@
         synchronized (sLock) {
             if (isHalStarted()) {
                 if (sRttCmdId != 0) {
-                    Log.v("TAG", "Last one is still under measurement!");
+                    Log.w(TAG, "Last one is still under measurement!");
                     return false;
                 } else {
                     sRttCmdId = getNewCmdIdLocked();
                 }
                 sRttEventHandler = handler;
-                Log.v(TAG, "native issue RTT request");
                 return requestRangeNative(sWlan0Index, sRttCmdId, params);
             } else {
                 return false;
@@ -2176,7 +2177,6 @@
 
                 if (cancelRangeRequestNative(sWlan0Index, sRttCmdId, params)) {
                     sRttEventHandler = null;
-                    Log.v(TAG, "RTT cancel Request Successfully");
                     return true;
                 } else {
                     Log.e(TAG, "RTT cancel Request failed");
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index 131d2b0..3caa1f6 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -1766,6 +1766,14 @@
     }
 
     /**
+     * Allow tests to confirm the operational mode for WSM.
+     */
+    @VisibleForTesting
+    protected int getOperationalModeForTest() {
+        return mOperationalMode;
+    }
+
+    /**
      * TODO: doc
      */
     public List<ScanResult> syncGetScanResultsList() {
@@ -4136,6 +4144,9 @@
                     mWificond = mWifiInjector.makeWificond();
                     transitionTo(mSoftApState);
                     break;
+                case CMD_SET_OPERATIONAL_MODE:
+                    mOperationalMode = message.arg1;
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -5443,8 +5454,10 @@
                     // For SIM & AKA/AKA' EAP method Only, get identity from ICC
                     if (targetWificonfiguration != null
                             && targetWificonfiguration.networkId == networkId
-                            && targetWificonfiguration.allowedKeyManagement
-                                    .get(WifiConfiguration.KeyMgmt.IEEE8021X)
+                            && (targetWificonfiguration.allowedKeyManagement
+                                    .get(WifiConfiguration.KeyMgmt.WPA_EAP)
+                            || targetWificonfiguration.allowedKeyManagement
+                                    .get(WifiConfiguration.KeyMgmt.IEEE8021X))
                             && TelephonyUtil.isSimEapMethod(eapMethod)) {
                         String identity =
                                 TelephonyUtil.getSimIdentity(getTelephonyManager(), eapMethod);
diff --git a/service/java/com/android/server/wifi/nan/WifiNanDataPathStateManager.java b/service/java/com/android/server/wifi/nan/WifiNanDataPathStateManager.java
index 20535f6..f6d80b1 100644
--- a/service/java/com/android/server/wifi/nan/WifiNanDataPathStateManager.java
+++ b/service/java/com/android/server/wifi/nan/WifiNanDataPathStateManager.java
@@ -432,8 +432,12 @@
             }
 
             nnri.networkAgent = new WifiNanNetworkAgent(mLooper, mContext,
-                    AGENT_TAG_PREFIX + nnri.ndpId, networkInfo, networkCapabilities, linkProperties,
-                    NETWORK_FACTORY_SCORE_AVAIL, networkSpecifier, ndpId);
+                    AGENT_TAG_PREFIX + nnri.ndpId,
+                    new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORK_TAG, ""),
+                    networkCapabilities, new LinkProperties(), NETWORK_FACTORY_SCORE_AVAIL,
+                    networkSpecifier, ndpId);
+            nnri.networkAgent.sendNetworkInfo(networkInfo);
+            nnri.networkAgent.sendLinkProperties(linkProperties);
         } else {
             if (DBG) {
                 Log.d(TAG, "onDataPathConfirm: data-path for networkSpecifier=" + networkSpecifier
diff --git a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index fdf3dfa..df0c6e6 100644
--- a/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/service/java/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -189,7 +189,7 @@
 
             ClientInfo ci = mClients.get(msg.replyTo);
             if (ci == null) {
-                loge("Could not find client info for message " + msg.replyTo);
+                loge("Could not find client info for message " + msg.replyTo + ", msg=" + msg);
                 replyFailed(msg, WifiScanner.REASON_INVALID_LISTENER, "Could not find listener");
                 return;
             }
diff --git a/service/jni/com_android_server_wifi_WifiNative.cpp b/service/jni/com_android_server_wifi_WifiNative.cpp
index 8297392..e86f4fc 100644
--- a/service/jni/com_android_server_wifi_WifiNative.cpp
+++ b/service/jni/com_android_server_wifi_WifiNative.cpp
@@ -1051,7 +1051,7 @@
 
     JNIHelper helper(mVM);
 
-    ALOGD("onRttResults called, vm = %p, obj = %p", mVM, mCls);
+    if (DBG) ALOGD("onRttResults called, vm = %p, obj = %p", mVM, mCls);
 
     JNIObject<jobjectArray> rttResults = helper.newObjectArray(
             num_results, "android/net/wifi/RttManager$RttResult", NULL);
@@ -1099,14 +1099,12 @@
         JNIObject<jobject> LCI = helper.createObject(
                 "android/net/wifi/RttManager$WifiInformationElement");
         if (result->LCI != NULL && result->LCI->len > 0) {
-            ALOGD("Add LCI in result");
             helper.setByteField(LCI, "id", result->LCI->id);
             JNIObject<jbyteArray> elements = helper.newByteArray(result->LCI->len);
             jbyte *bytes = (jbyte *)&(result->LCI->data[0]);
             helper.setByteArrayRegion(elements, 0, result->LCI->len, bytes);
             helper.setObjectField(LCI, "data", "[B", elements);
         } else {
-            ALOGD("No LCI in result");
             helper.setByteField(LCI, "id", (byte)(0xff));
         }
         helper.setObjectField(rttResult, "LCI",
@@ -1115,14 +1113,12 @@
         JNIObject<jobject> LCR = helper.createObject(
                 "android/net/wifi/RttManager$WifiInformationElement");
         if (result->LCR != NULL && result->LCR->len > 0) {
-            ALOGD("Add LCR in result");
             helper.setByteField(LCR, "id",           result->LCR->id);
             JNIObject<jbyteArray> elements = helper.newByteArray(result->LCI->len);
             jbyte *bytes = (jbyte *)&(result->LCR->data[0]);
             helper.setByteArrayRegion(elements, 0, result->LCI->len, bytes);
             helper.setObjectField(LCR, "data", "[B", elements);
         } else {
-            ALOGD("No LCR in result");
             helper.setByteField(LCR, "id", (byte)(0xff));
         }
         helper.setObjectField(rttResult, "LCR",
@@ -1143,7 +1139,7 @@
     JNIHelper helper(env);
 
     wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("sending rtt request [%d] = %p", id, handle);
+    if (DBG) ALOGD("sending rtt request [%d] = %p", id, handle);
     if (params == NULL) {
         ALOGE("ranging params are empty");
         return false;
@@ -1161,7 +1157,7 @@
 
         JNIObject<jobject> param = helper.getObjectArrayElement((jobjectArray)params, i);
         if (param == NULL) {
-            ALOGD("could not get element %d", i);
+            ALOGW("could not get element %d", i);
             continue;
         }
 
@@ -1186,18 +1182,6 @@
         config.burst_duration = (unsigned) helper.getIntField(param, "burstTimeout");
         config.preamble = (wifi_rtt_preamble) helper.getIntField(param, "preamble");
         config.bw = (wifi_rtt_bw) helper.getIntField(param, "bandwidth");
-
-        ALOGD("RTT request destination %d: type is %d, peer is %d, bw is %d, center_freq is %d ", i,
-                config.type,config.peer, config.channel.width,  config.channel.center_freq);
-        ALOGD("center_freq0 is %d, center_freq1 is %d, num_burst is %d,interval is %d",
-                config.channel.center_freq0, config.channel.center_freq1, config.num_burst,
-                config.burst_period);
-        ALOGD("frames_per_burst is %d, retries of measurement frame is %d, retries_per_ftmr is %d",
-                config.num_frames_per_burst, config.num_retries_per_rtt_frame,
-                config.num_retries_per_ftmr);
-        ALOGD("LCI_requestis %d, LCR_request is %d,  burst_timeout is %d, preamble is %d, bw is %d",
-                config.LCI_request, config.LCR_request, config.burst_duration, config.preamble,
-                config.bw);
     }
 
     wifi_rtt_event_handler handler;
@@ -1211,7 +1195,7 @@
 
     JNIHelper helper(env);
     wifi_interface_handle handle = getIfaceHandle(helper, cls, iface);
-    ALOGD("cancelling rtt request [%d] = %p", id, handle);
+    if (DBG) ALOGD("cancelling rtt request [%d] = %p", id, handle);
 
     if (params == NULL) {
         ALOGE("ranging params are empty");
@@ -1230,7 +1214,7 @@
 
         JNIObject<jobject> param = helper.getObjectArrayElement(params, i);
         if (param == NULL) {
-            ALOGD("could not get element %d", i);
+            ALOGW("could not get element %d", i);
             continue;
         }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerNewTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerNewTest.java
index 53e2d80..c190ac1 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerNewTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerNewTest.java
@@ -31,12 +31,13 @@
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
 
+import com.android.internal.R;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
@@ -46,6 +47,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 
 /**
@@ -60,8 +62,11 @@
     private static final long TEST_ELAPSED_UPDATE_NETWORK_SELECTION_TIME_MILLIS = 29457631;
     private static final int TEST_CREATOR_UID = 5;
     private static final int TEST_UPDATE_UID = 4;
+    private static final int TEST_MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCAN = 5;
+    private static final Integer[] TEST_FREQ_LIST = {2400, 2450, 5150, 5175, 5650};
     private static final String TEST_CREATOR_NAME = "com.wificonfigmanagerNew.creator";
     private static final String TEST_UPDATE_NAME = "com.wificonfigmanagerNew.update";
+    private static final String TEST_DEFAULT_GW_MAC_ADDRESS = "0f:67:ad:ef:09:34";
 
     @Mock private Context mContext;
     @Mock private FrameworkFacade mFrameworkFacade;
@@ -71,8 +76,8 @@
     @Mock private WifiConfigStoreNew mWifiConfigStore;
     @Mock private PackageManager mPackageManager;
 
+    private MockResources mResources;
     private InOrder mContextConfigStoreMockOrder;
-
     private WifiConfigManagerNew mWifiConfigManager;
 
     /**
@@ -88,6 +93,14 @@
 
         // Set up the package name stuff & permission override.
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        mResources = new MockResources();
+        mResources.setBoolean(
+                R.bool.config_wifi_only_link_same_credential_configurations, true);
+        mResources.setInteger(
+                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels,
+                TEST_MAX_NUM_ACTIVE_CHANNELS_FOR_PARTIAL_SCAN);
+        when(mContext.getResources()).thenReturn(mResources);
+
         doAnswer(new AnswerWithArguments() {
             public String answer(int uid) throws Exception {
                 if (uid == TEST_CREATOR_UID) {
@@ -115,11 +128,7 @@
                 .updateNetworkKeys(any(WifiConfiguration.class), any(WifiConfiguration.class)))
                 .thenReturn(true);
 
-        mWifiConfigManager =
-                new WifiConfigManagerNew(
-                        mContext, mFrameworkFacade, mClock, mUserManager, mWifiKeyStore,
-                        mWifiConfigStore);
-        mWifiConfigManager.enableVerboseLogging(1);
+        createWifiConfigManager();
     }
 
     /**
@@ -1173,6 +1182,411 @@
     }
 
     /**
+     * Verifies the linking of networks when they have the same default GW Mac address in
+     * {@link WifiConfigManagerNew#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}.
+     */
+    @Test
+    public void testNetworkLinkUsingGwMacAddress() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+        verifyAddNetworkToWifiConfigManager(network3);
+
+        // Set the same default GW mac address for all of the networks.
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network1.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network2.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network3.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+
+        // Now create dummy scan detail corresponding to the networks.
+        ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1);
+        ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2);
+        ScanDetail networkScanDetail3 = createScanDetailForNetwork(network3);
+
+        // Now save all these scan details corresponding to each of this network and expect
+        // all of these networks to be linked with each other.
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail3));
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertEquals(2, network.linkedConfigurations.size());
+            for (WifiConfiguration otherNetwork : retrievedNetworks) {
+                if (otherNetwork == network) {
+                    continue;
+                }
+                assertNotNull(network.linkedConfigurations.get(otherNetwork.configKey()));
+            }
+        }
+    }
+
+    /**
+     * Verifies the linking of networks when they have scan results with same first 16 ASCII of
+     * bssid in
+     * {@link WifiConfigManagerNew#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}.
+     */
+    @Test
+    public void testNetworkLinkUsingBSSIDMatch() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+        verifyAddNetworkToWifiConfigManager(network3);
+
+        // Create scan results with bssid which is different in only the last char.
+        ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1, "af:89:56:34:56:67");
+        ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2, "af:89:56:34:56:68");
+        ScanDetail networkScanDetail3 = createScanDetailForNetwork(network3, "af:89:56:34:56:69");
+
+        // Now save all these scan details corresponding to each of this network and expect
+        // all of these networks to be linked with each other.
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail3));
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertEquals(2, network.linkedConfigurations.size());
+            for (WifiConfiguration otherNetwork : retrievedNetworks) {
+                if (otherNetwork == network) {
+                    continue;
+                }
+                assertNotNull(network.linkedConfigurations.get(otherNetwork.configKey()));
+            }
+        }
+    }
+
+    /**
+     * Verifies the linking of networks does not happen for non WPA networks when they have scan
+     * results with same first 16 ASCII of bssid in
+     * {@link WifiConfigManagerNew#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}.
+     */
+    @Test
+    public void testNoNetworkLinkUsingBSSIDMatchForNonWpaNetworks() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+
+        // Create scan results with bssid which is different in only the last char.
+        ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1, "af:89:56:34:56:67");
+        ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2, "af:89:56:34:56:68");
+
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertNull(network.linkedConfigurations);
+        }
+    }
+
+    /**
+     * Verifies the linking of networks does not happen for networks with more than
+     * {@link WifiConfigManagerNew#LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES} scan
+     * results with same first 16 ASCII of bssid in
+     * {@link WifiConfigManagerNew#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}.
+     */
+    @Test
+    public void testNoNetworkLinkUsingBSSIDMatchForNetworksWithHighScanDetailCacheSize() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+
+        // Create 7 scan results with bssid which is different in only the last char.
+        String test_bssid_base = "af:89:56:34:56:6";
+        int scan_result_num = 0;
+        for (; scan_result_num < WifiConfigManagerNew.LINK_CONFIGURATION_MAX_SCAN_CACHE_ENTRIES + 1;
+             scan_result_num++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network1, test_bssid_base + Integer.toString(scan_result_num));
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+        }
+
+        // Now add 1 scan result to the other network with bssid which is different in only the
+        // last char.
+        ScanDetail networkScanDetail2 =
+                createScanDetailForNetwork(
+                        network2, test_bssid_base + Integer.toString(scan_result_num++));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertNull(network.linkedConfigurations);
+        }
+    }
+
+    /**
+     * Verifies the linking of networks when they have scan results with same first 16 ASCII of
+     * bssid in {@link WifiConfigManagerNew#getOrCreateScanDetailCacheForNetwork(WifiConfiguration)}
+     * and then subsequently delinked when the networks have default gateway set which do not match.
+     */
+    @Test
+    public void testNetworkLinkUsingBSSIDMatchAndThenUnlinkDueToGwMacAddress() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+
+        // Create scan results with bssid which is different in only the last char.
+        ScanDetail networkScanDetail1 = createScanDetailForNetwork(network1, "af:89:56:34:56:67");
+        ScanDetail networkScanDetail2 = createScanDetailForNetwork(network2, "af:89:56:34:56:68");
+
+        // Now save all these scan details corresponding to each of this network and expect
+        // all of these networks to be linked with each other.
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail1));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail2));
+
+        List<WifiConfiguration> retrievedNetworks =
+                mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertEquals(1, network.linkedConfigurations.size());
+            for (WifiConfiguration otherNetwork : retrievedNetworks) {
+                if (otherNetwork == network) {
+                    continue;
+                }
+                assertNotNull(network.linkedConfigurations.get(otherNetwork.configKey()));
+            }
+        }
+
+        // Now Set different GW mac address for both the networks and ensure they're unlinked.
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network1.networkId, "de:ad:fe:45:23:34"));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network2.networkId, "ad:de:fe:45:23:34"));
+
+        // Add some dummy scan results again to re-evaluate the linking of networks.
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                createScanDetailForNetwork(network1, "af:89:56:34:45:67")));
+        assertNotNull(mWifiConfigManager.getSavedNetworkForScanDetailAndCache(
+                createScanDetailForNetwork(network1, "af:89:56:34:45:68")));
+
+        retrievedNetworks = mWifiConfigManager.getConfiguredNetworks();
+        for (WifiConfiguration network : retrievedNetworks) {
+            assertNull(network.linkedConfigurations);
+        }
+    }
+
+    /*
+     * Verifies the creation of channel list using
+     * {@link WifiConfigManagerNew#fetchChannelSetForNetworkForPartialScan(int, long)}.
+     */
+    @Test
+    public void testFetchChannelSetForNetwork() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        // Create 5 scan results with different bssid's & frequencies.
+        String test_bssid_base = "af:89:56:34:56:6";
+        for (int i = 0; i < TEST_FREQ_LIST.length; i++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network, test_bssid_base + Integer.toString(i), 0, TEST_FREQ_LIST[i]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+
+        }
+        assertEquals(new HashSet<Integer>(Arrays.asList(TEST_FREQ_LIST)),
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(network.networkId, 1));
+    }
+
+    /**
+     * Verifies the creation of channel list using
+     * {@link WifiConfigManagerNew#fetchChannelSetForNetworkForPartialScan(int, long)} and ensures
+     * that scan results which have a timestamp  beyond the provided age are not used in the
+     * channel list.
+     */
+    @Test
+    public void testFetchChannelSetForNetworkIgnoresStaleScanResults() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        long wallClockBase = 0;
+        // Create 5 scan results with different bssid's & frequencies.
+        String test_bssid_base = "af:89:56:34:56:6";
+        for (int i = 0; i < TEST_FREQ_LIST.length; i++) {
+            // Increment the seen value in the scan results for each of them.
+            when(mClock.getWallClockMillis()).thenReturn(wallClockBase + i);
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network, test_bssid_base + Integer.toString(i), 0, TEST_FREQ_LIST[i]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+
+        }
+        int ageInMillis = 4;
+        // Now fetch only scan results which are 4 millis stale. This should ignore the first
+        // scan result.
+        assertEquals(
+                new HashSet<>(Arrays.asList(
+                        Arrays.copyOfRange(
+                                TEST_FREQ_LIST,
+                                TEST_FREQ_LIST.length - ageInMillis, TEST_FREQ_LIST.length))),
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(
+                        network.networkId, ageInMillis));
+    }
+
+    /**
+     * Verifies the creation of channel list using
+     * {@link WifiConfigManagerNew#fetchChannelSetForNetworkForPartialScan(int, long)} and ensures
+     * that the list size does not exceed the max configured for the device.
+     */
+    @Test
+    public void testFetchChannelSetForNetworkIsLimitedToConfiguredSize() {
+        // Need to recreate the WifiConfigManagerNew instance for this test to modify the config
+        // value which is read only in the constructor.
+        int maxListSize = 3;
+        mResources.setInteger(
+                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels,
+                maxListSize);
+        createWifiConfigManager();
+
+        WifiConfiguration network = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network);
+
+        // Create 5 scan results with different bssid's & frequencies.
+        String test_bssid_base = "af:89:56:34:56:6";
+        for (int i = 0; i < TEST_FREQ_LIST.length; i++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network, test_bssid_base + Integer.toString(i), 0, TEST_FREQ_LIST[i]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+
+        }
+        // Ensure that the fetched list size is limited.
+        assertEquals(maxListSize,
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(
+                        network.networkId, 1).size());
+    }
+
+    /**
+     * Verifies the creation of channel list using
+     * {@link WifiConfigManagerNew#fetchChannelSetForNetworkForPartialScan(int, long)} and ensures
+     * that scan results from linked networks are used in the channel list.
+     */
+    @Test
+    public void testFetchChannelSetForNetworkIncludesLinkedNetworks() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+
+        String test_bssid_base = "af:89:56:34:56:6";
+        int TEST_FREQ_LISTIdx = 0;
+        // Create 3 scan results with different bssid's & frequencies for network 1.
+        for (; TEST_FREQ_LISTIdx < TEST_FREQ_LIST.length / 2; TEST_FREQ_LISTIdx++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network1, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
+                            TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+
+        }
+        // Create 3 scan results with different bssid's & frequencies for network 2.
+        for (; TEST_FREQ_LISTIdx < TEST_FREQ_LIST.length; TEST_FREQ_LISTIdx++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network2, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
+                            TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+        }
+
+        // Link the 2 configurations together using the GwMacAddress.
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network1.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network2.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+
+        // The channel list fetched should include scan results from both the linked networks.
+        assertEquals(new HashSet<Integer>(Arrays.asList(TEST_FREQ_LIST)),
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(network1.networkId, 1));
+        assertEquals(new HashSet<Integer>(Arrays.asList(TEST_FREQ_LIST)),
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(network2.networkId, 1));
+    }
+
+    /**
+     * Verifies the creation of channel list using
+     * {@link WifiConfigManagerNew#fetchChannelSetForNetworkForPartialScan(int, long)} and ensures
+     * that scan results from linked networks are used in the channel list and that the list size
+     * does not exceed the max configured for the device.
+     */
+    @Test
+    public void testFetchChannelSetForNetworkIncludesLinkedNetworksIsLimitedToConfiguredSize() {
+        // Need to recreate the WifiConfigManagerNew instance for this test to modify the config
+        // value which is read only in the constructor.
+        int maxListSize = 3;
+        mResources.setInteger(
+                R.integer.config_wifi_framework_associated_partial_scan_max_num_active_channels,
+                maxListSize);
+
+        createWifiConfigManager();
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createPskNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createPskNetwork();
+        verifyAddNetworkToWifiConfigManager(network1);
+        verifyAddNetworkToWifiConfigManager(network2);
+
+        String test_bssid_base = "af:89:56:34:56:6";
+        int TEST_FREQ_LISTIdx = 0;
+        // Create 3 scan results with different bssid's & frequencies for network 1.
+        for (; TEST_FREQ_LISTIdx < TEST_FREQ_LIST.length / 2; TEST_FREQ_LISTIdx++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network1, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
+                            TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+
+        }
+        // Create 3 scan results with different bssid's & frequencies for network 2.
+        for (; TEST_FREQ_LISTIdx < TEST_FREQ_LIST.length; TEST_FREQ_LISTIdx++) {
+            ScanDetail networkScanDetail =
+                    createScanDetailForNetwork(
+                            network2, test_bssid_base + Integer.toString(TEST_FREQ_LISTIdx), 0,
+                            TEST_FREQ_LIST[TEST_FREQ_LISTIdx]);
+            assertNotNull(
+                    mWifiConfigManager.getSavedNetworkForScanDetailAndCache(networkScanDetail));
+        }
+
+        // Link the 2 configurations together using the GwMacAddress.
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network1.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+        assertTrue(mWifiConfigManager.setNetworkDefaultGwMacAddress(
+                network2.networkId, TEST_DEFAULT_GW_MAC_ADDRESS));
+
+        // Ensure that the fetched list size is limited.
+        assertEquals(maxListSize,
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(
+                        network1.networkId, 1).size());
+        assertEquals(maxListSize,
+                mWifiConfigManager.fetchChannelSetForNetworkForPartialScan(
+                        network2.networkId, 1).size());
+    }
+
+    private void createWifiConfigManager() {
+        mWifiConfigManager =
+                new WifiConfigManagerNew(
+                        mContext, mFrameworkFacade, mClock, mUserManager, mWifiKeyStore,
+                        mWifiConfigStore);
+        mWifiConfigManager.enableVerboseLogging(1);
+    }
+
+    /**
      * This method sets defaults in the provided WifiConfiguration object if not set
      * so that it can be used for comparison with the configuration retrieved from
      * WifiConfigManager.
@@ -1586,9 +2000,11 @@
     }
 
     /**
-     * Creates a scan detail corresponding to the provided network and BSSID value.
+     * Creates a scan detail corresponding to the provided network and given BSSID, level &frequency
+     * values.
      */
-    private ScanDetail createScanDetailForNetwork(WifiConfiguration configuration, String bssid) {
+    private ScanDetail createScanDetailForNetwork(
+            WifiConfiguration configuration, String bssid, int level, int frequency) {
         String caps;
         if (configuration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
             caps = "[WPA2-PSK-CCMP]";
@@ -1604,7 +2020,15 @@
         WifiSsid ssid = WifiSsid.createFromAsciiEncoded(configuration.getPrintableSsid());
         // Fill in 0's in the fields we don't care about.
         return new ScanDetail(
-                ssid, bssid, caps, 0, 0, SystemClock.uptimeMillis(), System.currentTimeMillis());
+                ssid, bssid, caps, level, frequency, mClock.getUptimeSinceBootMillis(),
+                mClock.getWallClockMillis());
+    }
+
+    /**
+     * Creates a scan detail corresponding to the provided network and BSSID value.
+     */
+    private ScanDetail createScanDetailForNetwork(WifiConfiguration configuration, String bssid) {
+        return createScanDetailForNetwork(configuration, bssid, 0, 0);
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
index e45ddef..bdfdbc8 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationTestUtil.java
@@ -60,7 +60,7 @@
      */
     public static final int TEST_NETWORK_ID = -1;
     public static final int TEST_UID = 1;
-    public static final String TEST_SSID = "WifiConfigurationTestUtilSSID_";
+    public static final String TEST_SSID = "WifiConfigurationTestUtilSSID";
     public static final String TEST_PSK = "WifiConfigurationTestUtilPsk";
     public static final String[] TEST_WEP_KEYS =
             {"WifiConfigurationTestUtilWep1", "WifiConfigurationTestUtilWep2",
@@ -80,6 +80,7 @@
     public static final String TEST_PAC_PROXY_LOCATION = "http://";
     public static final String TEST_CA_CERT_ALIAS = "WifiConfigurationTestUtilCaCertAlias";
 
+    private static final int MAX_SSID_LENGTH = 32;
     /**
      * Index used to assign unique SSIDs for the generation of predefined WifiConfiguration objects.
      */
@@ -214,7 +215,9 @@
      * Create a new SSID for the the network being created.
      */
     private static String createNewSSID() {
-        return "\"" + TEST_SSID + sNetworkIndex++ + "\"";
+        String ssid = TEST_SSID + sNetworkIndex++;
+        assertTrue(ssid.length() <= MAX_SSID_LENGTH);
+        return "\"" + ssid + "\"";
     }
 
     /**
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
index 5531eea..0fa9538 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiStateMachineTest.java
@@ -519,6 +519,73 @@
         assertEquals("InitialState", getCurrentState().getName());
     }
 
+    /**
+     * Test to check that mode changes from WifiController will be properly handled in the
+     * InitialState by WifiStateMachine.
+     */
+    @Test
+    public void checkOperationalModeInInitialState() throws Exception {
+        when(mWifiNative.startHal()).thenReturn(true);
+        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(true);
+
+        mLooper.dispatchAll();
+        assertEquals("InitialState", getCurrentState().getName());
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+
+        mWsm.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE,
+                mWsm.getOperationalModeForTest());
+
+        mWsm.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.SCAN_ONLY_MODE, mWsm.getOperationalModeForTest());
+
+        mWsm.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+    }
+
+    /**
+     * Test that mode changes for WifiStateMachine in the InitialState are realized when supplicant
+     * is started.
+     */
+    @Test
+    public void checkStartInCorrectStateAfterChangingInitialState() throws Exception {
+        when(mWifiNative.startHal()).thenReturn(true);
+        when(mWifiNative.startSupplicant(anyBoolean())).thenReturn(true);
+
+        // Check initial state
+        mLooper.dispatchAll();
+        assertEquals("InitialState", getCurrentState().getName());
+        assertEquals(WifiStateMachine.CONNECT_MODE, mWsm.getOperationalModeForTest());
+
+        // Update the mode
+        mWsm.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+        mLooper.dispatchAll();
+        assertEquals(WifiStateMachine.SCAN_ONLY_MODE, mWsm.getOperationalModeForTest());
+
+        // Start supplicant so we move to the next state
+        mWsm.setSupplicantRunning(true);
+        mLooper.dispatchAll();
+        assertEquals("SupplicantStartingState", getCurrentState().getName());
+        when(mWifiNative.setBand(anyInt())).thenReturn(true);
+        when(mWifiNative.setDeviceName(anyString())).thenReturn(true);
+        when(mWifiNative.setManufacturer(anyString())).thenReturn(true);
+        when(mWifiNative.setModelName(anyString())).thenReturn(true);
+        when(mWifiNative.setModelNumber(anyString())).thenReturn(true);
+        when(mWifiNative.setSerialNumber(anyString())).thenReturn(true);
+        when(mWifiNative.setConfigMethods(anyString())).thenReturn(true);
+        when(mWifiNative.setDeviceType(anyString())).thenReturn(true);
+        when(mWifiNative.setSerialNumber(anyString())).thenReturn(true);
+        when(mWifiNative.setScanningMacOui(any(byte[].class))).thenReturn(true);
+
+        mWsm.sendMessage(WifiMonitor.SUP_CONNECTION_EVENT);
+        mLooper.dispatchAll();
+
+        assertEquals("ScanModeState", getCurrentState().getName());
+    }
+
     private void addNetworkAndVerifySuccess() throws Exception {
         addNetworkAndVerifySuccess(false);
     }