Use a LRU to in memory store the network connection order

In memory use a LRU to determine the network connection order for non-passpoint network.
And persist a bool on the disk, only set 16(or whatever overlay config) most recently connected
network to true.

Bug: 150799593
Test: atest com.android.server.wifi
Change-Id: Ic4594810409fbfe91e0c7d840d8a6d9f571e184f
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index 1fd3c51..b13251d 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.hotspot2.PasspointManager;
+import com.android.server.wifi.util.LruConnectionTracker;
 import com.android.server.wifi.util.MissingCounterTimerLockList;
 import com.android.server.wifi.util.TelephonyUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
@@ -234,6 +235,8 @@
     private final MacAddressUtil mMacAddressUtil;
     private final TelephonyUtil mTelephonyUtil;
     private final WifiScoreCard mWifiScoreCard;
+    // Keep order of network connection.
+    private final LruConnectionTracker mLruConnectionTracker;
 
     /**
      * Local log used for debugging any WifiConfigManager issues.
@@ -256,7 +259,6 @@
      */
     private final MissingCounterTimerLockList<String> mUserTemporarilyDisabledList;
 
-
     /**
      * Framework keeps a mapping from configKey to the randomized MAC address so that
      * when a user forgets a network and thne adds it back, the same randomized MAC address
@@ -331,7 +333,8 @@
             NetworkListUserStoreData networkListUserStoreData,
             RandomizedMacStoreData randomizedMacStoreData,
             FrameworkFacade frameworkFacade, Handler handler,
-            DeviceConfigFacade deviceConfigFacade, WifiScoreCard wifiScoreCard) {
+            DeviceConfigFacade deviceConfigFacade, WifiScoreCard wifiScoreCard,
+            LruConnectionTracker lruConnectionTracker) {
         mContext = context;
         mClock = clock;
         mUserManager = userManager;
@@ -365,6 +368,7 @@
         mLocalLog = new LocalLog(
                 context.getSystemService(ActivityManager.class).isLowRamDevice() ? 128 : 256);
         mMacAddressUtil = mWifiInjector.getMacAddressUtil();
+        mLruConnectionTracker = lruConnectionTracker;
     }
 
     /**
@@ -1429,6 +1433,9 @@
         if (networkId == mLastSelectedNetworkId) {
             clearLastSelectedNetwork();
         }
+        if (!config.ephemeral && !config.isPasspoint()) {
+            mLruConnectionTracker.removeNetwork(config);
+        }
         sendConfiguredNetworkChangedBroadcast(config, WifiManager.CHANGE_REASON_REMOVED);
         // Unless the removed network is ephemeral or Passpoint, persist the network removal.
         if (!config.ephemeral && !config.isPasspoint()) {
@@ -1953,6 +1960,11 @@
         if (config == null) {
             return false;
         }
+
+        // Only record connection order for non-passpoint from user saved or suggestion.
+        if (!config.isPasspoint() && (config.fromWifiNetworkSuggestion || !config.ephemeral)) {
+            mLruConnectionTracker.addNetwork(config);
+        }
         config.lastConnected = mClock.getWallClockMillis();
         config.numAssociation++;
         config.getNetworkSelectionStatus().clearDisableReasonCounter();
@@ -3109,6 +3121,10 @@
             } catch (IllegalArgumentException e) {
                 Log.e(TAG, "Failed to add network to config map", e);
             }
+
+            if (configuration.isMostRecentlyConnected) {
+                mLruConnectionTracker.addNetwork(configuration);
+            }
         }
     }
 
@@ -3280,6 +3296,9 @@
                 continue;
             }
 
+            config.isMostRecentlyConnected =
+                    mLruConnectionTracker.isMostRecentlyConnected(config);
+
             // We push all shared networks & private networks not belonging to the current
             // user to the shared store. Ideally, private networks for other users should
             // not even be in memory,
@@ -3428,5 +3447,4 @@
         }
         return scanMaxRssi;
     }
-
 }
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index f388d23..93fe2b5b 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -53,6 +53,7 @@
 import com.android.server.wifi.p2p.WifiP2pMonitor;
 import com.android.server.wifi.p2p.WifiP2pNative;
 import com.android.server.wifi.rtt.RttMetrics;
+import com.android.server.wifi.util.LruConnectionTracker;
 import com.android.server.wifi.util.NetdWrapper;
 import com.android.server.wifi.util.SettingsMigrationDataHolder;
 import com.android.server.wifi.util.TelephonyUtil;
@@ -75,6 +76,10 @@
 public class WifiInjector {
     private static final String TAG = "WifiInjector";
     private static final String BOOT_DEFAULT_WIFI_COUNTRY_CODE = "ro.boot.wificountrycode";
+    /**
+     * Maximum number in-memory store network connection order;
+     */
+    private static final int MAX_RECENTLY_CONNECTED_NETWORK = 100;
 
     static WifiInjector sWifiInjector = null;
 
@@ -162,6 +167,7 @@
     private final WifiScanAlwaysAvailableSettingsCompatibility
             mWifiScanAlwaysAvailableSettingsCompatibility;
     private final SettingsMigrationDataHolder mSettingsMigrationDataHolder;
+    private final LruConnectionTracker mLruConnectionTracker;
 
     public WifiInjector(Context context) {
         if (context == null) {
@@ -258,6 +264,8 @@
                 mFrameworkFacade, mContext, wifiHandler);
         String l2KeySeed = Secure.getString(mContext.getContentResolver(), Secure.ANDROID_ID);
         mWifiScoreCard = new WifiScoreCard(mClock, l2KeySeed, mDeviceConfigFacade);
+        mLruConnectionTracker = new LruConnectionTracker(MAX_RECENTLY_CONNECTED_NETWORK,
+                mContext);
         // Config Manager
         mWifiConfigManager = new WifiConfigManager(mContext, mClock,
                 mUserManager, mTelephonyUtil,
@@ -266,7 +274,7 @@
                 new NetworkListSharedStoreData(mContext),
                 new NetworkListUserStoreData(mContext),
                 new RandomizedMacStoreData(), mFrameworkFacade, wifiHandler, mDeviceConfigFacade,
-                mWifiScoreCard);
+                mWifiScoreCard, mLruConnectionTracker);
         mSettingsConfigStore = new WifiSettingsConfigStore(context, wifiHandler,
                 mSettingsMigrationDataHolder, mWifiConfigManager, mWifiConfigStore);
         mSettingsStore = new WifiSettingsStore(mContext, mSettingsConfigStore);
@@ -292,7 +300,7 @@
         mWifiMetrics.setWifiNetworkSelector(mWifiNetworkSelector);
         mWifiNetworkSuggestionsManager = new WifiNetworkSuggestionsManager(mContext, wifiHandler,
                 this, mWifiPermissionsUtil, mWifiConfigManager, mWifiConfigStore, mWifiMetrics,
-                mTelephonyUtil, mWifiKeyStore);
+                mTelephonyUtil, mWifiKeyStore, mLruConnectionTracker);
         mPasspointManager = new PasspointManager(mContext, this,
                 wifiHandler, mWifiNative, mWifiKeyStore, mClock, new PasspointObjectFactory(),
                 mWifiConfigManager, mWifiConfigStore, mWifiMetrics, mTelephonyUtil);
diff --git a/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java b/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
index d59d2a0..c82539b 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSuggestionsManager.java
@@ -57,6 +57,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.server.wifi.util.ExternalCallbackTracker;
+import com.android.server.wifi.util.LruConnectionTracker;
 import com.android.server.wifi.util.TelephonyUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.wifi.resources.R;
@@ -139,6 +140,8 @@
     private final FrameworkFacade mFrameworkFacade;
     private final TelephonyUtil mTelephonyUtil;
     private final WifiKeyStore mWifiKeyStore;
+    // Keep order of network connection.
+    private final LruConnectionTracker mLruConnectionTracker;
 
     /**
      * Per app meta data to store network suggestions, status, etc for each app providing network
@@ -391,6 +394,17 @@
     private class NetworkSuggestionDataSource implements NetworkSuggestionStoreData.DataSource {
         @Override
         public Map<String, PerAppInfo> toSerialize() {
+            for (Map.Entry<String, PerAppInfo> entry : mActiveNetworkSuggestionsPerApp.entrySet()) {
+                Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
+                        entry.getValue().extNetworkSuggestions;
+                for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) {
+                    if (ewns.wns.passpointConfiguration != null) {
+                        continue;
+                    }
+                    ewns.wns.wifiConfiguration.isMostRecentlyConnected = mLruConnectionTracker
+                            .isMostRecentlyConnected(ewns.createInternalWifiConfiguration());
+                }
+            }
             // Clear the flag after writing to disk.
             // TODO(b/115504887): Don't reset the flag on write failure.
             mHasNewDataToSerialize = false;
@@ -414,6 +428,10 @@
                     if (ewns.wns.passpointConfiguration != null) {
                         addToPasspointInfoMap(ewns);
                     } else {
+                        if (ewns.wns.wifiConfiguration.isMostRecentlyConnected) {
+                            mLruConnectionTracker
+                                    .addNetwork(ewns.createInternalWifiConfiguration());
+                        }
                         addToScanResultMatchInfoMap(ewns);
                     }
                 }
@@ -551,13 +569,10 @@
             };
 
     public WifiNetworkSuggestionsManager(Context context, Handler handler,
-                                         WifiInjector wifiInjector,
-                                         WifiPermissionsUtil wifiPermissionsUtil,
-                                         WifiConfigManager wifiConfigManager,
-                                         WifiConfigStore wifiConfigStore,
-                                         WifiMetrics wifiMetrics,
-                                         TelephonyUtil telephonyUtil,
-                                         WifiKeyStore keyStore) {
+            WifiInjector wifiInjector, WifiPermissionsUtil wifiPermissionsUtil,
+            WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore,
+            WifiMetrics wifiMetrics, TelephonyUtil telephonyUtil,
+            WifiKeyStore keyStore, LruConnectionTracker lruConnectionTracker) {
         mContext = context;
         mResources = context.getResources();
         mHandler = handler;
@@ -589,6 +604,7 @@
         mIntentFilter.addAction(NOTIFICATION_USER_DISALLOWED_CARRIER_INTENT_ACTION);
 
         mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, handler);
+        mLruConnectionTracker = lruConnectionTracker;
     }
 
     /**
@@ -662,6 +678,8 @@
                 mActiveScanResultMatchInfoWithBssid.remove(lookupPair);
                 if (!mActiveScanResultMatchInfoWithNoBssid.containsKey(scanResultMatchInfo)) {
                     removeNetworkFromScoreCard(extNetworkSuggestion.wns.wifiConfiguration);
+                    mLruConnectionTracker.removeNetwork(
+                            extNetworkSuggestion.wns.wifiConfiguration);
                 }
             }
         } else {
@@ -678,6 +696,8 @@
             if (extNetworkSuggestionsForScanResultMatchInfo.isEmpty()) {
                 mActiveScanResultMatchInfoWithNoBssid.remove(scanResultMatchInfo);
                 removeNetworkFromScoreCard(extNetworkSuggestion.wns.wifiConfiguration);
+                mLruConnectionTracker.removeNetwork(
+                        extNetworkSuggestion.wns.wifiConfiguration);
             }
         }
     }
@@ -1716,7 +1736,6 @@
         if (!(connectedNetwork.fromWifiNetworkSuggestion || connectedNetwork.isOpenNetwork())) {
             return;
         }
-
         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
                     getNetworkSuggestionsForWifiConfiguration(connectedNetwork, connectedBssid);
 
diff --git a/service/java/com/android/server/wifi/util/LruConnectionTracker.java b/service/java/com/android/server/wifi/util/LruConnectionTracker.java
new file mode 100644
index 0000000..2567288
--- /dev/null
+++ b/service/java/com/android/server/wifi/util/LruConnectionTracker.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+
+import com.android.server.wifi.ScanResultMatchInfo;
+import com.android.wifi.resources.R;
+
+/**
+ * Create a lru list to store the network connection order. sorted by most recently connected
+ * first.
+ */
+public class LruConnectionTracker {
+    private final LruList<ScanResultMatchInfo> mList;
+    private final Context mContext;
+    public LruConnectionTracker(int size, Context context) {
+        mList = new LruList<>(size);
+        mContext = context;
+    }
+    /**
+     * Check if a WifiConfiguration is in the most recently connected list.
+     */
+    public boolean isMostRecentlyConnected(@NonNull WifiConfiguration config) {
+        return getAgeIndexOfNetwork(config) < mContext.getResources()
+                .getInteger(R.integer.config_wifiMaxPnoSsidCount);
+    }
+
+    /**
+     * Add a WifiConfiguration into the most recently connected list.
+     */
+    public void addNetwork(@NonNull WifiConfiguration config) {
+        mList.add(ScanResultMatchInfo.fromWifiConfiguration(config));
+    }
+
+    /**
+     * Remove a network from the list.
+     */
+    public void removeNetwork(@NonNull WifiConfiguration config) {
+        mList.remove(ScanResultMatchInfo.fromWifiConfiguration(config));
+    }
+
+    /**
+     * Get the index of the input config inside the MostRecentlyConnectNetworkList.
+     * If input config is in the list will return index number,
+     * otherwise return {@code Integer.MAX_VALUE}
+     */
+    public int getAgeIndexOfNetwork(@NonNull WifiConfiguration config) {
+        int index = mList.indexOf(ScanResultMatchInfo.fromWifiConfiguration(config));
+        // Not in the most recently connected list will return the MAX_INT
+        if (index < 0) {
+            return Integer.MAX_VALUE;
+        }
+        return index;
+    }
+}
diff --git a/service/java/com/android/server/wifi/util/LruList.java b/service/java/com/android/server/wifi/util/LruList.java
index 3be2357..69ed851 100644
--- a/service/java/com/android/server/wifi/util/LruList.java
+++ b/service/java/com/android/server/wifi/util/LruList.java
@@ -62,6 +62,20 @@
     }
 
     /**
+     * Remove an entry from list.
+     */
+    public void remove(@NonNull E entry) {
+        if (entry == null) {
+            return;
+        }
+        int index = mLinkedList.indexOf(entry);
+        if (index < 0) {
+            return;
+        }
+        mLinkedList.remove(index);
+    }
+
+    /**
      * Returns the list of entries sorted by most recently added entries first.
      * @return
      */
@@ -75,4 +89,13 @@
     public int size() {
         return mLinkedList.size();
     }
+
+    /**
+     * Get the index in the list of the input entry.
+     * If not in the list will return -1.
+     * If in the list, smaller index is more recently added.
+     */
+    public int indexOf(E entry) {
+        return mLinkedList.indexOf(entry);
+    }
 }
diff --git a/service/java/com/android/server/wifi/util/XmlUtil.java b/service/java/com/android/server/wifi/util/XmlUtil.java
index da22f8b..d20ee7e 100644
--- a/service/java/com/android/server/wifi/util/XmlUtil.java
+++ b/service/java/com/android/server/wifi/util/XmlUtil.java
@@ -355,6 +355,7 @@
         public static final String XML_TAG_CARRIER_ID = "CarrierId";
         public static final String XML_TAG_IS_AUTO_JOIN = "AutoJoinEnabled";
         public static final String XML_TAG_IS_TRUSTED = "Trusted";
+        private static final String XML_TAG_IS_MOST_RECENTLY_CONNECTED = "IsMostRecentlyConnected";
 
         /**
          * Write WepKeys to the XML stream.
@@ -520,6 +521,8 @@
             XmlUtil.writeNextValue(out, XML_TAG_MAC_RANDOMIZATION_SETTING,
                     configuration.macRandomizationSetting);
             XmlUtil.writeNextValue(out, XML_TAG_CARRIER_ID, configuration.carrierId);
+            XmlUtil.writeNextValue(out, XML_TAG_IS_MOST_RECENTLY_CONNECTED,
+                    configuration.isMostRecentlyConnected);
         }
 
         /**
@@ -713,6 +716,9 @@
                         case XML_TAG_IS_TRUSTED:
                             configuration.trusted = (boolean) value;
                             break;
+                        case XML_TAG_IS_MOST_RECENTLY_CONNECTED:
+                            configuration.isMostRecentlyConnected = (boolean) value;
+                            break;
                         default:
                             Log.w(TAG, "Ignoring unknown value name found: " + valueName[0]);
                             break;
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 76741b1..e51e640 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -369,6 +369,9 @@
     <!-- Enable the PNO frequency culling optimization. -->
     <bool translatable="false" name="config_wifiPnoRecencySortingEnabled">true</bool>
 
+    <!-- Maximum number of SSIDs that can be PNO scanned concurrently-->
+    <integer translatable="false" name="config_wifiMaxPnoSsidCount">16</integer>
+
     <!-- Suspend optimization. -->
     <bool translatable="false" name="config_wifiSuspendOptimizationsEnabled">true</bool>
 
diff --git a/service/res/values/overlayable.xml b/service/res/values/overlayable.xml
index ff73200..a6a37a3 100644
--- a/service/res/values/overlayable.xml
+++ b/service/res/values/overlayable.xml
@@ -117,6 +117,7 @@
           <item type="bool" name="config_wifiLinkSpeedMetricsEnabled" />
           <item type="bool" name="config_wifiPnoFrequencyCullingEnabled" />
           <item type="bool" name="config_wifiPnoRecencySortingEnabled" />
+          <item type="integer" name="config_wifiMaxPnoSsidCount" />
           <item type="bool" name="config_wifiSuspendOptimizationsEnabled" />
           <item type="bool" name="config_wifiHighMovementNetworkSelectionOptimizationEnabled" />
           <item type="integer" name="config_wifiHighMovementNetworkSelectionOptimizationScanDelayMs" />
diff --git a/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
index 88bcc92..4e2eee4 100644
--- a/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/NetworkListStoreDataTest.java
@@ -106,6 +106,7 @@
                     + "<string name=\"RandomizedMacAddress\">%s</string>\n"
                     + "<int name=\"MacRandomizationSetting\" value=\"1\" />\n"
                     + "<int name=\"CarrierId\" value=\"-1\" />\n"
+                    + "<boolean name=\"IsMostRecentlyConnected\" value=\"false\" />\n"
                     + "</WifiConfiguration>\n"
                     + "<NetworkStatus>\n"
                     + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
@@ -161,6 +162,7 @@
                     + "<string name=\"RandomizedMacAddress\">%s</string>\n"
                     + "<int name=\"MacRandomizationSetting\" value=\"1\" />\n"
                     + "<int name=\"CarrierId\" value=\"-1\" />\n"
+                    + "<boolean name=\"IsMostRecentlyConnected\" value=\"false\" />\n"
                     + "</WifiConfiguration>\n"
                     + "<NetworkStatus>\n"
                     + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
@@ -237,6 +239,7 @@
                     + "<string name=\"RandomizedMacAddress\">%s</string>\n"
                     + "<int name=\"MacRandomizationSetting\" value=\"1\" />\n"
                     + "<int name=\"CarrierId\" value=\"-1\" />\n"
+                    + "<boolean name=\"IsMostRecentlyConnected\" value=\"false\" />\n"
                     + "</WifiConfiguration>\n"
                     + "<NetworkStatus>\n"
                     + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
index 244cad4..1147dd9 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java
@@ -50,6 +50,7 @@
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.server.wifi.WifiScoreCard.PerNetwork;
+import com.android.server.wifi.util.LruConnectionTracker;
 import com.android.server.wifi.util.TelephonyUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
@@ -140,6 +141,7 @@
     @Mock private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
     @Mock private WifiScoreCard mWifiScoreCard;
     @Mock private PerNetwork mPerNetwork;
+    @Mock private LruConnectionTracker mLruConnectionTracker;
 
     private MockResources mResources;
     private InOrder mContextConfigStoreMockOrder;
@@ -150,6 +152,7 @@
     private MockitoSession mSession;
     private TelephonyUtil mTelephonyUtil;
 
+
     /**
      * Setup the mocks and an instance of WifiConfigManager before each test.
      */
@@ -4843,7 +4846,7 @@
                         mNetworkListSharedStoreData, mNetworkListUserStoreData,
                         mRandomizedMacStoreData,
                         mFrameworkFacade, new Handler(mLooper.getLooper()), mDeviceConfigFacade,
-                        mWifiScoreCard);
+                        mWifiScoreCard, mLruConnectionTracker);
         mWifiConfigManager.enableVerboseLogging(1);
     }
 
@@ -5313,6 +5316,10 @@
         // Verify if the config store write was triggered without this new configuration.
         verifyNetworkNotInConfigStoreData(configuration);
         verify(mBssidBlocklistMonitor, atLeastOnce()).handleNetworkRemoved(configuration.SSID);
+        ArgumentCaptor<WifiConfiguration> captor = ArgumentCaptor.forClass(WifiConfiguration.class);
+        verify(mLruConnectionTracker).removeNetwork(captor.capture());
+        assertEquals(configuration.networkId, captor.getValue().networkId);
+        reset(mLruConnectionTracker);
     }
 
     /**
@@ -5508,6 +5515,9 @@
         WifiConfiguration retrievedNetwork = mWifiConfigManager.getConfiguredNetwork(networkId);
         assertTrue("hasEverConnected expected to be true after connection.",
                 retrievedNetwork.getNetworkSelectionStatus().hasEverConnected());
+        ArgumentCaptor<WifiConfiguration> captor = ArgumentCaptor.forClass(WifiConfiguration.class);
+        verify(mLruConnectionTracker).addNetwork(captor.capture());
+        assertEquals(networkId, captor.getValue().networkId);
     }
 
     /**
@@ -5632,4 +5642,23 @@
         assertEquals(mWifiConfigManager.findScanRssi(result.getNetworkId(), 5000),
                 WifiInfo.INVALID_RSSI);
     }
+
+    /**
+     * Verify when save to store, isMostRecentlyConnected flag will be set.
+     */
+    @Test
+    public void testMostRecentlyConnectedNetwork() {
+        WifiConfiguration testNetwork1 = WifiConfigurationTestUtil.createOpenNetwork();
+        verifyAddNetworkToWifiConfigManager(testNetwork1);
+        WifiConfiguration testNetwork2 = WifiConfigurationTestUtil.createOpenNetwork();
+        verifyAddNetworkToWifiConfigManager(testNetwork2);
+        when(mLruConnectionTracker.isMostRecentlyConnected(any()))
+                .thenReturn(false).thenReturn(true);
+        mWifiConfigManager.saveToStore(true);
+        Pair<List<WifiConfiguration>, List<WifiConfiguration>> networkStoreData =
+                captureWriteNetworksListStoreData();
+        List<WifiConfiguration> sharedNetwork = networkStoreData.first;
+        assertFalse(sharedNetwork.get(0).isMostRecentlyConnected);
+        assertTrue(sharedNetwork.get(1).isMostRecentlyConnected);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
index ca36873..8341654 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigStoreTest.java
@@ -118,6 +118,7 @@
                     + "<string name=\"RandomizedMacAddress\">%s</string>\n"
                     + "<int name=\"MacRandomizationSetting\" value=\"1\" />\n"
                     + "<int name=\"CarrierId\" value=\"-1\" />\n"
+                    + "<boolean name=\"IsMostRecentlyConnected\" value=\"false\" />\n"
                     + "</WifiConfiguration>\n"
                     + "<NetworkStatus>\n"
                     + "<string name=\"SelectionStatus\">NETWORK_SELECTION_ENABLED</string>\n"
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
index 0ec2eb9..f26ceb6 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiNetworkSuggestionsManagerTest.java
@@ -77,6 +77,7 @@
 import com.android.server.wifi.WifiNetworkSuggestionsManager.ExtendedWifiNetworkSuggestion;
 import com.android.server.wifi.WifiNetworkSuggestionsManager.PerAppInfo;
 import com.android.server.wifi.hotspot2.PasspointManager;
+import com.android.server.wifi.util.LruConnectionTracker;
 import com.android.server.wifi.util.TelephonyUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 import com.android.wifi.resources.R;
@@ -91,6 +92,7 @@
 import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -146,6 +148,7 @@
     private @Mock AlertDialog mAlertDialog;
     private @Mock Notification.Builder mNotificationBuilder;
     private @Mock Notification mNotification;
+    private @Mock LruConnectionTracker mLruConnectionTracker;
     private TestLooper mLooper;
     private ArgumentCaptor<AppOpsManager.OnOpChangedListener> mAppOpChangedListenerCaptor =
             ArgumentCaptor.forClass(AppOpsManager.OnOpChangedListener.class);
@@ -266,7 +269,7 @@
         mWifiNetworkSuggestionsManager =
                 new WifiNetworkSuggestionsManager(mContext, new Handler(mLooper.getLooper()),
                         mWifiInjector, mWifiPermissionsUtil, mWifiConfigManager, mWifiConfigStore,
-                        mWifiMetrics, mTelephonyUtil, mWifiKeyStore);
+                        mWifiMetrics, mTelephonyUtil, mWifiKeyStore, mLruConnectionTracker);
         verify(mContext).getResources();
         verify(mContext).getSystemService(Context.APP_OPS_SERVICE);
         verify(mContext).getSystemService(Context.NOTIFICATION_SERVICE);
@@ -382,6 +385,7 @@
         verify(mPasspointManager).removeProvider(eq(TEST_UID_2), eq(false),
                 eq(passpointConfiguration.getUniqueId()), isNull());
         verify(mWifiScoreCard).removeNetwork(anyString());
+        verify(mLruConnectionTracker).removeNetwork(any());
 
         assertTrue(mWifiNetworkSuggestionsManager.getAllNetworkSuggestions().isEmpty());
 
@@ -426,6 +430,7 @@
                         TEST_UID_1, TEST_PACKAGE_1));
         // Make sure remove the keyStore with the internal config
         verify(mWifiKeyStore).removeKeys(networkSuggestion1.wifiConfiguration.enterpriseConfig);
+        verify(mLruConnectionTracker).removeNetwork(any());
     }
 
     /**
@@ -464,6 +469,7 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.remove(new ArrayList<>(), TEST_UID_1,
                         TEST_PACKAGE_1));
+        verify(mLruConnectionTracker).removeNetwork(any());
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiNetworkSuggestionsManager.remove(new ArrayList<>(), TEST_UID_2,
                         TEST_PACKAGE_2));
@@ -3588,6 +3594,37 @@
     }
 
     /**
+     * Verify if a suggestion is mostRecently connected, flag will be persist.
+     */
+    @Test
+    public void testIsMostRecentlyConnectedSuggestion() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiNetworkSuggestion networkSuggestion =
+                new WifiNetworkSuggestion(network, null, false, false, true, true);
+        List<WifiNetworkSuggestion> networkSuggestionList = Arrays.asList(networkSuggestion);
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiNetworkSuggestionsManager
+                        .add(networkSuggestionList, TEST_UID_1, TEST_PACKAGE_1, TEST_FEATURE));
+        mWifiNetworkSuggestionsManager.setHasUserApprovedForApp(true, TEST_PACKAGE_1);
+        when(mLruConnectionTracker.isMostRecentlyConnected(any())).thenReturn(true);
+        Map<String, PerAppInfo> suggestionStore = mDataSource.toSerialize();
+        PerAppInfo perAppInfo = suggestionStore.get(TEST_PACKAGE_1);
+        ExtendedWifiNetworkSuggestion ewns = perAppInfo.extNetworkSuggestions.iterator().next();
+        assertTrue(ewns.wns.wifiConfiguration.isMostRecentlyConnected);
+        mDataSource.fromDeserialized(suggestionStore);
+        verify(mLruConnectionTracker).addNetwork(any());
+        reset(mLruConnectionTracker);
+
+        when(mLruConnectionTracker.isMostRecentlyConnected(any())).thenReturn(false);
+        suggestionStore = mDataSource.toSerialize();
+        perAppInfo = suggestionStore.get(TEST_PACKAGE_1);
+        ewns = perAppInfo.extNetworkSuggestions.iterator().next();
+        assertFalse(ewns.wns.wifiConfiguration.isMostRecentlyConnected);
+        mDataSource.fromDeserialized(suggestionStore);
+        verify(mLruConnectionTracker, never()).addNetwork(any());
+    }
+
+    /**
      * Helper function for creating a test configuration with user credential.
      *
      * @return {@link PasspointConfiguration}
diff --git a/tests/wifitests/src/com/android/server/wifi/util/LruConnectionTrackerTest.java b/tests/wifitests/src/com/android/server/wifi/util/LruConnectionTrackerTest.java
new file mode 100644
index 0000000..eb5ae5b
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/util/LruConnectionTrackerTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.WifiConfiguration;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wifi.MockResources;
+import com.android.server.wifi.WifiConfigurationTestUtil;
+import com.android.wifi.resources.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class LruConnectionTrackerTest {
+    private static final int TEST_MAX_SIZE = 4;
+    private LruConnectionTracker mList;
+    private MockResources mResources;
+
+    @Mock private Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mResources = new MockResources();
+        mResources.setInteger(R.integer.config_wifiMaxPnoSsidCount, TEST_MAX_SIZE - 1);
+        when(mContext.getResources()).thenReturn(mResources);
+        mList = new LruConnectionTracker(TEST_MAX_SIZE, mContext);
+    }
+
+    @Test
+    public void testAddRemoveNetwork() {
+        WifiConfiguration network = WifiConfigurationTestUtil.createOpenNetwork();
+        assertEquals(Integer.MAX_VALUE, mList.getAgeIndexOfNetwork(network));
+        mList.addNetwork(network);
+        assertEquals(0, mList.getAgeIndexOfNetwork(network));
+        mList.removeNetwork(network);
+        assertEquals(Integer.MAX_VALUE, mList.getAgeIndexOfNetwork(network));
+    }
+
+    @Test
+    public void testConnectionOrderStore() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network4 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network5 = WifiConfigurationTestUtil.createOpenNetwork();
+        mList.addNetwork(network1);
+        mList.addNetwork(network2);
+        mList.addNetwork(network3);
+        mList.addNetwork(network4);
+        // Expect reverse order of the added order
+        assertEquals(0, mList.getAgeIndexOfNetwork(network4));
+        assertEquals(1, mList.getAgeIndexOfNetwork(network3));
+        assertEquals(2, mList.getAgeIndexOfNetwork(network2));
+        assertEquals(3, mList.getAgeIndexOfNetwork(network1));
+        // Exceed the size of the list, should remove the oldest one.
+        mList.addNetwork(network5);
+        assertEquals(Integer.MAX_VALUE, mList.getAgeIndexOfNetwork(network1));
+        assertEquals(0, mList.getAgeIndexOfNetwork(network5));
+        // Add one already in the list, move it to the head of the list.
+        mList.addNetwork(network3);
+        assertEquals(0, mList.getAgeIndexOfNetwork(network3));
+        assertEquals(1, mList.getAgeIndexOfNetwork(network5));
+        assertEquals(2, mList.getAgeIndexOfNetwork(network4));
+        assertEquals(3, mList.getAgeIndexOfNetwork(network2));
+    }
+
+    @Test
+    public void testIsMostRecentlyNetwork() {
+        WifiConfiguration network1 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network2 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network3 = WifiConfigurationTestUtil.createOpenNetwork();
+        WifiConfiguration network4 = WifiConfigurationTestUtil.createOpenNetwork();
+        mList.addNetwork(network1);
+        mList.addNetwork(network2);
+        mList.addNetwork(network3);
+        assertTrue(mList.isMostRecentlyConnected(network1));
+        assertTrue(mList.isMostRecentlyConnected(network2));
+        assertTrue(mList.isMostRecentlyConnected(network3));
+        assertFalse(mList.isMostRecentlyConnected(network4));
+        // network in the list is more than the threshold, will mark the oldest false.
+        mList.addNetwork(network4);
+        assertFalse(mList.isMostRecentlyConnected(network1));
+        assertTrue(mList.isMostRecentlyConnected(network4));
+        // Add network in the list and which is true. Should not change other network flag.
+        mList.addNetwork(network2);
+        assertFalse(mList.isMostRecentlyConnected(network1));
+        assertTrue(mList.isMostRecentlyConnected(network2));
+        assertTrue(mList.isMostRecentlyConnected(network3));
+        assertTrue(mList.isMostRecentlyConnected(network4));
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/util/LruListTest.java b/tests/wifitests/src/com/android/server/wifi/util/LruListTest.java
index 27cb4e7..00122b1 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/LruListTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/LruListTest.java
@@ -66,4 +66,30 @@
         mLruList.add(null);
         assertEquals(0, mLruList.size());
     }
+
+    @Test
+    public void getIndex() {
+        for (int i = 0; i < TEST_MAX_SIZE; i++) {
+            mLruList.add(i);
+        }
+        for (int i = 0; i < TEST_MAX_SIZE; i++) {
+            assertEquals(i, mLruList.indexOf(TEST_MAX_SIZE - i - 1));
+        }
+        // Verify not in the list will return -1.
+        assertEquals(-1, mLruList.indexOf(3));
+        mLruList.add(3);
+        // Verify add more elements more than list size, will remove oldest one.
+        assertEquals(0, mLruList.indexOf(3));
+        assertEquals(-1, mLruList.indexOf(0));
+        // Verify add an element in the list will make it the most recently one.
+        mLruList.add(1);
+        assertEquals(0, mLruList.indexOf(1));
+        assertEquals(1, mLruList.indexOf(3));
+        assertEquals(2, mLruList.indexOf(2));
+        // Verify remove one element in the list, all element behind it index should decrease 1.
+        mLruList.remove(1);
+        assertEquals(-1, mLruList.indexOf(1));
+        assertEquals(0, mLruList.indexOf(3));
+        assertEquals(1, mLruList.indexOf(2));
+    }
 }