Fix access point list jumping around in WifiSettings

This happens because the scan times are very aggressive
(to save power); and we don't find all APs on each scan.
So on each scan, some APs are considered lost and some
other are considered found; resulting in reordering the
list.

This change fixes that by requiring 3 scans to confirm
absence of an AP.

Bug: 7263326

Change-Id: I04f61ec5e5aa1589f457645acbf538c7e275a4bf
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 4cef286..0180a30 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -46,6 +46,7 @@
 
 import com.android.settingslib.R;
 
+import java.util.ArrayList;
 import java.util.Map;
 
 
@@ -79,10 +80,15 @@
      *  For now this data is used only with Verbose Logging so as to show the band and number
      *  of BSSIDs on which that network is seen.
      */
-    public LruCache<String, ScanResult> mScanResultCache;
+    public LruCache<String, ScanResult> mScanResultCache = new LruCache<String, ScanResult>(32);
+
     private static final String KEY_NETWORKINFO = "key_networkinfo";
     private static final String KEY_WIFIINFO = "key_wifiinfo";
     private static final String KEY_SCANRESULT = "key_scanresult";
+    private static final String KEY_SSID = "key_ssid";
+    private static final String KEY_SECURITY = "key_security";
+    private static final String KEY_PSKTYPE = "key_psktype";
+    private static final String KEY_SCANRESULTCACHE = "key_scanresultcache";
     private static final String KEY_CONFIG = "key_config";
 
     /**
@@ -108,7 +114,6 @@
     private int pskType = PSK_UNKNOWN;
 
     private WifiConfiguration mConfig;
-    private ScanResult mScanResult;
 
     private int mRssi = Integer.MAX_VALUE;
     private long mSeen = 0;
@@ -125,20 +130,35 @@
         if (mConfig != null) {
             loadConfig(mConfig);
         }
-        mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT);
-        if (mScanResult != null) {
-            loadResult(mScanResult);
+        if (savedState.containsKey(KEY_SSID)) {
+            ssid = savedState.getString(KEY_SSID);
+        }
+        if (savedState.containsKey(KEY_SECURITY)) {
+            security = savedState.getInt(KEY_SECURITY);
+        }
+        if (savedState.containsKey(KEY_PSKTYPE)) {
+            pskType = savedState.getInt(KEY_PSKTYPE);
         }
         mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO);
         if (savedState.containsKey(KEY_NETWORKINFO)) {
             mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO);
         }
+        if (savedState.containsKey(KEY_SCANRESULTCACHE)) {
+            ArrayList<ScanResult> scanResultArrayList =
+                    savedState.getParcelableArrayList(KEY_SCANRESULTCACHE);
+            mScanResultCache.evictAll();
+            for (ScanResult result : scanResultArrayList) {
+                mScanResultCache.put(result.BSSID, result);
+            }
+        }
         update(mInfo, mNetworkInfo);
+        mRssi = getRssi();
+        mSeen = getSeen();
     }
 
     AccessPoint(Context context, ScanResult result) {
         mContext = context;
-        loadResult(result);
+        initWithScanResult(result);
     }
 
     AccessPoint(Context context, WifiConfiguration config) {
@@ -240,6 +260,28 @@
         return WifiManager.calculateSignalLevel(mRssi, 4);
     }
 
+    public int getRssi() {
+        int rssi = Integer.MIN_VALUE;
+        for (ScanResult result : mScanResultCache.snapshot().values()) {
+            if (result.level > rssi) {
+                rssi = result.level;
+            }
+        }
+
+        return rssi;
+    }
+
+    public long getSeen() {
+        long seen = 0;
+        for (ScanResult result : mScanResultCache.snapshot().values()) {
+            if (result.timestamp > seen) {
+                seen = result.timestamp;
+            }
+        }
+
+        return seen;
+    }
+
     public NetworkInfo getNetworkInfo() {
         return mNetworkInfo;
     }
@@ -455,120 +497,109 @@
             visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate));
         }
 
-        if (mScanResultCache != null) {
-            int rssi5 = WifiConfiguration.INVALID_RSSI;
-            int rssi24 = WifiConfiguration.INVALID_RSSI;
-            int num5 = 0;
-            int num24 = 0;
-            int numBlackListed = 0;
-            int n24 = 0; // Number scan results we included in the string
-            int n5 = 0; // Number scan results we included in the string
-            Map<String, ScanResult> list = mScanResultCache.snapshot();
-            // TODO: sort list by RSSI or age
-            for (ScanResult result : list.values()) {
-                if (result.seen == 0)
-                    continue;
+        int rssi5 = WifiConfiguration.INVALID_RSSI;
+        int rssi24 = WifiConfiguration.INVALID_RSSI;
+        int num5 = 0;
+        int num24 = 0;
+        int numBlackListed = 0;
+        int n24 = 0; // Number scan results we included in the string
+        int n5 = 0; // Number scan results we included in the string
+        Map<String, ScanResult> list = mScanResultCache.snapshot();
+        // TODO: sort list by RSSI or age
+        for (ScanResult result : list.values()) {
+            if (result.seen == 0)
+                continue;
 
-                if (result.autoJoinStatus != ScanResult.ENABLED) numBlackListed++;
+            if (result.autoJoinStatus != ScanResult.ENABLED) numBlackListed++;
 
-                if (result.frequency >= LOWER_FREQ_5GHZ
-                        && result.frequency <= HIGHER_FREQ_5GHZ) {
-                    // Strictly speaking: [4915, 5825]
-                    // number of known BSSID on 5GHz band
-                    num5 = num5 + 1;
-                } else if (result.frequency >= LOWER_FREQ_24GHZ
-                        && result.frequency <= HIGHER_FREQ_24GHZ) {
-                    // Strictly speaking: [2412, 2482]
-                    // number of known BSSID on 2.4Ghz band
-                    num24 = num24 + 1;
-                }
-
-                // Ignore results seen, older than 20 seconds
-                if (now - result.seen > VISIBILITY_OUTDATED_AGE_IN_MILLI) continue;
-
-                if (result.frequency >= LOWER_FREQ_5GHZ
-                        && result.frequency <= HIGHER_FREQ_5GHZ) {
-                    if (result.level > rssi5) {
-                        rssi5 = result.level;
-                    }
-                    if (n5 < 4) {
-                        if (scans5GHz == null) scans5GHz = new StringBuilder();
-                        scans5GHz.append(" \n{").append(result.BSSID);
-                        if (bssid != null && result.BSSID.equals(bssid)) scans5GHz.append("*");
-                        scans5GHz.append("=").append(result.frequency);
-                        scans5GHz.append(",").append(result.level);
-                        if (result.autoJoinStatus != 0) {
-                            scans5GHz.append(",st=").append(result.autoJoinStatus);
-                        }
-                        if (result.numIpConfigFailures != 0) {
-                            scans5GHz.append(",ipf=").append(result.numIpConfigFailures);
-                        }
-                        scans5GHz.append("}");
-                        n5++;
-                    }
-                } else if (result.frequency >= LOWER_FREQ_24GHZ
-                        && result.frequency <= HIGHER_FREQ_24GHZ) {
-                    if (result.level > rssi24) {
-                        rssi24 = result.level;
-                    }
-                    if (n24 < 4) {
-                        if (scans24GHz == null) scans24GHz = new StringBuilder();
-                        scans24GHz.append(" \n{").append(result.BSSID);
-                        if (bssid != null && result.BSSID.equals(bssid)) scans24GHz.append("*");
-                        scans24GHz.append("=").append(result.frequency);
-                        scans24GHz.append(",").append(result.level);
-                        if (result.autoJoinStatus != 0) {
-                            scans24GHz.append(",st=").append(result.autoJoinStatus);
-                        }
-                        if (result.numIpConfigFailures != 0) {
-                            scans24GHz.append(",ipf=").append(result.numIpConfigFailures);
-                        }
-                        scans24GHz.append("}");
-                        n24++;
-                    }
-                }
+            if (result.frequency >= LOWER_FREQ_5GHZ
+                    && result.frequency <= HIGHER_FREQ_5GHZ) {
+                // Strictly speaking: [4915, 5825]
+                // number of known BSSID on 5GHz band
+                num5 = num5 + 1;
+            } else if (result.frequency >= LOWER_FREQ_24GHZ
+                    && result.frequency <= HIGHER_FREQ_24GHZ) {
+                // Strictly speaking: [2412, 2482]
+                // number of known BSSID on 2.4Ghz band
+                num24 = num24 + 1;
             }
-            visibility.append(" [");
-            if (num24 > 0) {
-                visibility.append("(").append(num24).append(")");
-                if (n24 <= 4) {
-                    if (scans24GHz != null) {
-                        visibility.append(scans24GHz.toString());
-                    }
-                } else {
-                    visibility.append("max=").append(rssi24);
-                    if (scans24GHz != null) {
-                        visibility.append(",").append(scans24GHz.toString());
-                    }
+
+            // Ignore results seen, older than 20 seconds
+            if (now - result.seen > VISIBILITY_OUTDATED_AGE_IN_MILLI) continue;
+
+            if (result.frequency >= LOWER_FREQ_5GHZ
+                    && result.frequency <= HIGHER_FREQ_5GHZ) {
+                if (result.level > rssi5) {
+                    rssi5 = result.level;
                 }
-            }
-            visibility.append(";");
-            if (num5 > 0) {
-                visibility.append("(").append(num5).append(")");
-                if (n5 <= 4) {
-                    if (scans5GHz != null) {
-                        visibility.append(scans5GHz.toString());
+                if (n5 < 4) {
+                    if (scans5GHz == null) scans5GHz = new StringBuilder();
+                    scans5GHz.append(" \n{").append(result.BSSID);
+                    if (bssid != null && result.BSSID.equals(bssid)) scans5GHz.append("*");
+                    scans5GHz.append("=").append(result.frequency);
+                    scans5GHz.append(",").append(result.level);
+                    if (result.autoJoinStatus != 0) {
+                        scans5GHz.append(",st=").append(result.autoJoinStatus);
                     }
-                } else {
-                    visibility.append("max=").append(rssi5);
-                    if (scans5GHz != null) {
-                        visibility.append(",").append(scans5GHz.toString());
+                    if (result.numIpConfigFailures != 0) {
+                        scans5GHz.append(",ipf=").append(result.numIpConfigFailures);
                     }
+                    scans5GHz.append("}");
+                    n5++;
                 }
-            }
-            if (numBlackListed > 0)
-                visibility.append("!").append(numBlackListed);
-            visibility.append("]");
-        } else {
-            if (mRssi != Integer.MAX_VALUE) {
-                visibility.append(" rssi=");
-                visibility.append(mRssi);
-                if (mScanResult != null) {
-                    visibility.append(", f=");
-                    visibility.append(mScanResult.frequency);
+            } else if (result.frequency >= LOWER_FREQ_24GHZ
+                    && result.frequency <= HIGHER_FREQ_24GHZ) {
+                if (result.level > rssi24) {
+                    rssi24 = result.level;
+                }
+                if (n24 < 4) {
+                    if (scans24GHz == null) scans24GHz = new StringBuilder();
+                    scans24GHz.append(" \n{").append(result.BSSID);
+                    if (bssid != null && result.BSSID.equals(bssid)) scans24GHz.append("*");
+                    scans24GHz.append("=").append(result.frequency);
+                    scans24GHz.append(",").append(result.level);
+                    if (result.autoJoinStatus != 0) {
+                        scans24GHz.append(",st=").append(result.autoJoinStatus);
+                    }
+                    if (result.numIpConfigFailures != 0) {
+                        scans24GHz.append(",ipf=").append(result.numIpConfigFailures);
+                    }
+                    scans24GHz.append("}");
+                    n24++;
                 }
             }
         }
+        visibility.append(" [");
+        if (num24 > 0) {
+            visibility.append("(").append(num24).append(")");
+            if (n24 <= 4) {
+                if (scans24GHz != null) {
+                    visibility.append(scans24GHz.toString());
+                }
+            } else {
+                visibility.append("max=").append(rssi24);
+                if (scans24GHz != null) {
+                    visibility.append(",").append(scans24GHz.toString());
+                }
+            }
+        }
+        visibility.append(";");
+        if (num5 > 0) {
+            visibility.append("(").append(num5).append(")");
+            if (n5 <= 4) {
+                if (scans5GHz != null) {
+                    visibility.append(scans5GHz.toString());
+                }
+            } else {
+                visibility.append("max=").append(rssi5);
+                if (scans5GHz != null) {
+                    visibility.append(",").append(scans5GHz.toString());
+                }
+            }
+        }
+        if (numBlackListed > 0)
+            visibility.append("!").append(numBlackListed);
+        visibility.append("]");
 
         return visibility.toString();
     }
@@ -635,28 +666,29 @@
             ssid = config.providerFriendlyName;
         else
             ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
-            
+
         security = getSecurity(config);
         networkId = config.networkId;
         mConfig = config;
     }
 
-    private void loadResult(ScanResult result) {
+    private void initWithScanResult(ScanResult result) {
         ssid = result.SSID;
         security = getSecurity(result);
         if (security == SECURITY_PSK)
             pskType = getPskType(result);
         mRssi = result.level;
-        mScanResult = result;
-        if (result.seen > mSeen) {
-            mSeen = result.seen;
-        }
+        mSeen = result.timestamp;
     }
 
     public void saveWifiState(Bundle savedState) {
-        savedState.putParcelable(KEY_CONFIG, mConfig);
-        savedState.putParcelable(KEY_SCANRESULT, mScanResult);
+        if (ssid != null) savedState.putString(KEY_SSID, getSsidStr());
+        savedState.putInt(KEY_SECURITY, security);
+        savedState.putInt(KEY_PSKTYPE, pskType);
+        if (mConfig != null) savedState.putParcelable(KEY_CONFIG, mConfig);
         savedState.putParcelable(KEY_WIFIINFO, mInfo);
+        savedState.putParcelableArrayList(KEY_SCANRESULTCACHE,
+                new ArrayList<ScanResult>(mScanResultCache.snapshot().values()));
         if (mNetworkInfo != null) {
             savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
         }
@@ -667,32 +699,31 @@
     }
 
     boolean update(ScanResult result) {
-        if (result.seen > mSeen) {
-            mSeen = result.seen;
-        }
-        if (WifiTracker.sVerboseLogging > 0) {
-            if (mScanResultCache == null) {
-                mScanResultCache = new LruCache<String, ScanResult>(32);
-            }
-            mScanResultCache.put(result.BSSID, result);
-        }
-
         if (ssid.equals(result.SSID) && security == getSecurity(result)) {
-            if (WifiManager.compareSignalLevel(result.level, mRssi) > 0) {
-                int oldLevel = getLevel();
-                mRssi = result.level;
-                if (getLevel() != oldLevel && mAccessPointListener != null) {
-                    mAccessPointListener.onLevelChanged(this);
-                }
+            /* Update the LRU timestamp, if BSSID exists */
+            mScanResultCache.get(result.BSSID);
+
+            /* Add or update the scan result for the BSSID */
+            mScanResultCache.put(result.BSSID, result);
+
+            int oldLevel = getLevel();
+            int oldRssi = getRssi();
+            mSeen = getSeen();
+            mRssi = (getRssi() + oldRssi)/2;
+            int newLevel = getLevel();
+
+            if (newLevel > 0 && newLevel != oldLevel && mAccessPointListener != null) {
+                mAccessPointListener.onLevelChanged(this);
             }
             // This flag only comes from scans, is not easily saved in config
             if (security == SECURITY_PSK) {
                 pskType = getPskType(result);
             }
-            mScanResult = result;
+
             if (mAccessPointListener != null) {
                 mAccessPointListener.onAccessPointChanged(this);
             }
+
             return true;
         }
         return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index 19be4a5..f4e4ea1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -28,6 +28,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.Log;
 import android.widget.Toast;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -35,9 +36,12 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -45,6 +49,7 @@
  */
 public class WifiTracker {
     private static final String TAG = "WifiTracker";
+    private static final boolean DBG = false;
 
     /** verbose logging flag. this flag is set thru developer debugging options
      * and used so as to assist with in-the-field WiFi connectivity debugging  */
@@ -70,6 +75,10 @@
     private boolean mSavedNetworksExist;
     private boolean mRegistered;
     private ArrayList<AccessPoint> mAccessPoints = new ArrayList<>();
+    private HashMap<String, Integer> mSeenBssids = new HashMap<>();
+    private HashMap<String, ScanResult> mScanResultCache = new HashMap<>();
+    private Integer mScanId = 0;
+    private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
 
     private NetworkInfo mLastNetworkInfo;
     private WifiInfo mLastInfo;
@@ -166,6 +175,11 @@
         if (mScanner == null) {
             mScanner = new Scanner();
         }
+
+        mScanResultCache.clear();
+        mSeenBssids.clear();
+        mScanId = 0;
+
         if (mWifiManager.isWifiEnabled()) {
             mScanner.resume();
         }
@@ -237,6 +251,33 @@
         }
     }
 
+    private Collection<ScanResult> fetchScanResults() {
+        mScanId++;
+        final List<ScanResult> newResults = mWifiManager.getScanResults();
+        for (ScanResult newResult : newResults) {
+            mScanResultCache.put(newResult.BSSID, newResult);
+            mSeenBssids.put(newResult.BSSID, mScanId);
+        }
+
+        if (mScanId > NUM_SCANS_TO_CONFIRM_AP_LOSS) {
+            if (DBG) Log.d(TAG, "------ Dumping SSIDs that were expired on this scan ------");
+            Integer threshold = mScanId - NUM_SCANS_TO_CONFIRM_AP_LOSS;
+            for (Iterator<Map.Entry<String, Integer>> it = mSeenBssids.entrySet().iterator();
+                    it.hasNext(); /* nothing */) {
+                Map.Entry<String, Integer> e = it.next();
+                if (e.getValue() < threshold) {
+                    ScanResult result = mScanResultCache.get(e.getKey());
+                    if (DBG) Log.d(TAG, "Removing " + e.getKey() + ":(" + result.SSID + ")");
+                    mScanResultCache.remove(e.getKey());
+                    it.remove();
+                }
+            }
+            if (DBG) Log.d(TAG, "---- Done Dumping SSIDs that were expired on this scan ----");
+        }
+
+        return mScanResultCache.values();
+    }
+
     private void updateAccessPoints() {
         // Swap the current access points into a cached list.
         List<AccessPoint> cachedAccessPoints = getAccessPoints();
@@ -283,7 +324,7 @@
             }
         }
 
-        final List<ScanResult> results = mWifiManager.getScanResults();
+        final Collection<ScanResult> results = fetchScanResults();
         if (results != null) {
             for (ScanResult result : results) {
                 // Ignore hidden and ad-hoc networks.
@@ -328,6 +369,24 @@
 
         // Pre-sort accessPoints to speed preference insertion
         Collections.sort(accessPoints);
+
+        // Log accesspoints that were deleted
+        if (DBG) Log.d(TAG, "------ Dumping SSIDs that were not seen on this scan ------");
+        for (AccessPoint prevAccessPoint : mAccessPoints) {
+            if (prevAccessPoint.getSsid() == null) continue;
+            String prevSsid = prevAccessPoint.getSsidStr();
+            boolean found = false;
+            for (AccessPoint newAccessPoint : accessPoints) {
+                if (newAccessPoint.getSsid() != null && newAccessPoint.getSsid().equals(prevSsid)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found)
+                if (DBG) Log.d(TAG, "Did not find " + prevSsid + " in this scan");
+        }
+        if (DBG)  Log.d(TAG, "---- Done dumping SSIDs that were not seen on this scan ----");
+
         mAccessPoints = accessPoints;
         mMainHandler.sendEmptyMessage(MainHandler.MSG_ACCESS_POINT_CHANGED);
     }