METRICS: histograms of scanresult sums in scan

Generate histograms counting the size of 5 different ScanResults
subsets within a scan. Subsets include: total, open,
saved, open-or-saved, or passpoint related scan results. The size is
counted for unique BSSIDs & unique SSIDS, generating two histograms.

This metric is useful for determining the frequency of N-way
network-selection decisions (where N is the number of connectable
networks).

Exempt-From-Owner-Approval: Prior approval by owners (rpius)
Bug: 36819798
Test: Added unit test
frameworks/opt/net/wifi/tests/wifitests/runtests.sh

Change-Id: I4825a778d45f9306f3e83b2418a047b8809c1802
diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java
index 8d37746..d8b4237 100644
--- a/service/java/com/android/server/wifi/WifiConfigManager.java
+++ b/service/java/com/android/server/wifi/WifiConfigManager.java
@@ -1938,7 +1938,7 @@
      * @return WifiConfiguration object representing the network corresponding to the scanDetail,
      * null if none exists.
      */
-    private WifiConfiguration getConfiguredNetworkForScanDetail(ScanDetail scanDetail) {
+    public WifiConfiguration getConfiguredNetworkForScanDetail(ScanDetail scanDetail) {
         ScanResult scanResult = scanDetail.getScanResult();
         if (scanResult == null) {
             Log.e(TAG, "No scan result found in scan detail");
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index 4a729e0..344242a 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -321,7 +321,10 @@
                     mWaitForFullBandScanResults = false;
                 }
             }
-
+            if (results.length > 0) {
+                mWifiMetrics.incrementAvailableNetworksHistograms(mScanDetails,
+                        results[0].isAllChannelsScanned());
+            }
             boolean wasConnectAttempted = handleScanResults(mScanDetails, "AllSingleScanListener");
             clearScanDetails();
 
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index f7d2e30..80daaea 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -205,10 +205,12 @@
                 mWifiKeyStore, mWifiConfigStore, mWifiConfigStoreLegacy, mWifiPermissionsUtil,
                 mWifiPermissionsWrapper, new NetworkListStoreData(),
                 new DeletedEphemeralSsidsStoreData());
+        mWifiMetrics.setWifiConfigManager(mWifiConfigManager);
         mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative);
         mConnectivityLocalLog = new LocalLog(ActivityManager.isLowRamDeviceStatic() ? 256 : 512);
         mWifiNetworkSelector = new WifiNetworkSelector(mContext, mWifiConfigManager, mClock,
                 mConnectivityLocalLog);
+        mWifiMetrics.setWifiNetworkSelector(mWifiNetworkSelector);
         mSavedNetworkEvaluator = new SavedNetworkEvaluator(mContext,
                 mWifiConfigManager, mClock, mConnectivityLocalLog, mWifiConnectivityHelper);
         mScoredNetworkEvaluator = new ScoredNetworkEvaluator(context, wifiStateMachineLooper,
@@ -220,6 +222,7 @@
                 mWifiMetrics);
         mPasspointNetworkEvaluator = new PasspointNetworkEvaluator(
                 mPasspointManager, mWifiConfigManager, mConnectivityLocalLog);
+        mWifiMetrics.setPasspointManager(mPasspointManager);
         // mWifiStateMachine has an implicit dependency on mJavaRuntime due to WifiDiagnostics.
         mJavaRuntime = Runtime.getRuntime();
         mWifiStateMachine = new WifiStateMachine(mContext, mFrameworkFacade,
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index d317f83..7dfdb86 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -28,10 +28,14 @@
 import android.os.Message;
 import android.util.Base64;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseIntArray;
 
 import com.android.server.wifi.aware.WifiAwareMetrics;
 import com.android.server.wifi.hotspot2.NetworkDetail;
+import com.android.server.wifi.hotspot2.PasspointManager;
+import com.android.server.wifi.hotspot2.PasspointMatch;
+import com.android.server.wifi.hotspot2.PasspointProvider;
 import com.android.server.wifi.nano.WifiMetricsProto;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent.ConfigInfo;
@@ -43,8 +47,10 @@
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Calendar;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Provides storage for wireless connectivity metrics, as they are generated.
@@ -70,11 +76,20 @@
     private static final int MAX_WIFI_SCORE = NetworkAgent.WIFI_BASE_SCORE;
     private final Object mLock = new Object();
     private static final int MAX_CONNECTION_EVENTS = 256;
+    // Largest bucket in the NumConnectableNetworkCount histogram,
+    // anything large will be stored in this bucket
+    public static final int MAX_CONNECTABLE_SSID_NETWORK_BUCKET = 20;
+    public static final int MAX_CONNECTABLE_BSSID_NETWORK_BUCKET = 50;
+    public static final int MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET = 100;
+    public static final int MAX_TOTAL_SCAN_RESULTS_BUCKET = 250;
     private Clock mClock;
     private boolean mScreenOn;
     private int mWifiState;
     private WifiAwareMetrics mWifiAwareMetrics;
     private Handler mHandler;
+    private WifiConfigManager mWifiConfigManager;
+    private WifiNetworkSelector mWifiNetworkSelector;
+    private PasspointManager mPasspointManager;
     /**
      * Metrics are stored within an instance of the WifiLog proto during runtime,
      * The ConnectionEvent, SystemStateEntries & ScanReturnEntries metrics are stored during
@@ -119,6 +134,20 @@
     private final SparseIntArray mWifiScoreCounts = new SparseIntArray();
     /** Mapping of SoftApManager start SoftAp return codes to counts */
     private final SparseIntArray mSoftApManagerReturnCodeCounts = new SparseIntArray();
+
+    private final SparseIntArray mTotalSsidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mTotalBssidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableOpenSsidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableOpenBssidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableSavedSsidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableSavedBssidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableOpenOrSavedSsidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableOpenOrSavedBssidsInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mAvailableSavedPasspointProviderProfilesInScanHistogram =
+            new SparseIntArray();
+    private final SparseIntArray mAvailableSavedPasspointProviderBssidsInScanHistogram =
+            new SparseIntArray();
+
     class RouterFingerPrint {
         private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
         RouterFingerPrint() {
@@ -363,6 +392,21 @@
         };
     }
 
+    /** Sets internal WifiConfigManager member */
+    public void setWifiConfigManager(WifiConfigManager wifiConfigManager) {
+        mWifiConfigManager = wifiConfigManager;
+    }
+
+    /** Sets internal WifiNetworkSelector member */
+    public void setWifiNetworkSelector(WifiNetworkSelector wifiNetworkSelector) {
+        mWifiNetworkSelector = wifiNetworkSelector;
+    }
+
+    /** Sets internal PasspointManager member */
+    public void setPasspointManager(PasspointManager passpointManager) {
+        mPasspointManager = passpointManager;
+    }
+
     // Values used for indexing SystemStateEntries
     private static final int SCREEN_ON = 1;
     private static final int SCREEN_OFF = 0;
@@ -1061,6 +1105,91 @@
         }
     }
 
+    /**
+     * Increment N-Way network selection decision histograms:
+     * Counts the size of various sets of scanDetails within a scan, and increment the occurrence
+     * of that size for the associated histogram. There are ten histograms generated for each
+     * combination of: {SSID, BSSID} *{Total, Saved, Open, Saved_or_Open, Passpoint}
+     * Only performs this count if isFullBand is true, otherwise, increments the partial scan count
+     */
+    public void incrementAvailableNetworksHistograms(List<ScanDetail> scanDetails,
+            boolean isFullBand) {
+        synchronized (mLock) {
+            if (mWifiConfigManager == null || mWifiNetworkSelector == null
+                    || mPasspointManager == null) {
+                return;
+            }
+            if (!isFullBand) {
+                mWifiLogProto.partialAllSingleScanListenerResults++;
+                return;
+            }
+            Set<ScanResultMatchInfo> ssids = new HashSet<ScanResultMatchInfo>();
+            int bssids = 0;
+            Set<ScanResultMatchInfo> openSsids = new HashSet<ScanResultMatchInfo>();
+            int openBssids = 0;
+            Set<ScanResultMatchInfo> savedSsids = new HashSet<ScanResultMatchInfo>();
+            int savedBssids = 0;
+            // openOrSavedSsids calculated from union of savedSsids & openSsids
+            int openOrSavedBssids = 0;
+            Set<PasspointProvider> savedPasspointProviderProfiles =
+                    new HashSet<PasspointProvider>();
+            int savedPasspointProviderBssids = 0;
+            for (ScanDetail scanDetail : scanDetails) {
+                NetworkDetail networkDetail = scanDetail.getNetworkDetail();
+                ScanResult scanResult = scanDetail.getScanResult();
+                if (mWifiNetworkSelector.isSignalTooWeak(scanResult)) {
+                    continue;
+                }
+                ScanResultMatchInfo matchInfo = ScanResultMatchInfo.fromScanResult(scanResult);
+                Pair<PasspointProvider, PasspointMatch> providerMatch = null;
+                PasspointProvider passpointProvider = null;
+                if (networkDetail.isInterworking()) {
+                    providerMatch =
+                            mPasspointManager.matchProvider(scanResult);
+                    passpointProvider = providerMatch != null ? providerMatch.first : null;
+                }
+                ssids.add(matchInfo);
+                bssids++;
+                boolean isOpen = matchInfo.networkType == ScanResultMatchInfo.NETWORK_TYPE_OPEN;
+                WifiConfiguration config =
+                        mWifiConfigManager.getConfiguredNetworkForScanDetail(scanDetail);
+                boolean isSaved = (config != null) && !config.isEphemeral()
+                        && !config.isPasspoint();
+                boolean isSavedPasspoint = passpointProvider != null;
+                if (isOpen) {
+                    openSsids.add(matchInfo);
+                    openBssids++;
+                }
+                if (isSaved) {
+                    savedSsids.add(matchInfo);
+                    savedBssids++;
+                }
+                if (isOpen || isSaved) {
+                    openOrSavedBssids++;
+                    // Calculate openOrSavedSsids union later
+                }
+                if (isSavedPasspoint) {
+                    savedPasspointProviderProfiles.add(passpointProvider);
+                    savedPasspointProviderBssids++;
+                }
+            }
+            mWifiLogProto.fullBandAllSingleScanListenerResults++;
+            incrementTotalScanSsids(mTotalSsidsInScanHistogram, ssids.size());
+            incrementTotalScanResults(mTotalBssidsInScanHistogram, bssids);
+            incrementSsid(mAvailableOpenSsidsInScanHistogram, openSsids.size());
+            incrementBssid(mAvailableOpenBssidsInScanHistogram, openBssids);
+            incrementSsid(mAvailableSavedSsidsInScanHistogram, savedSsids.size());
+            incrementBssid(mAvailableSavedBssidsInScanHistogram, savedBssids);
+            openSsids.addAll(savedSsids); // openSsids = Union(openSsids, savedSsids)
+            incrementSsid(mAvailableOpenOrSavedSsidsInScanHistogram, openSsids.size());
+            incrementBssid(mAvailableOpenOrSavedBssidsInScanHistogram, openOrSavedBssids);
+            incrementSsid(mAvailableSavedPasspointProviderProfilesInScanHistogram,
+                    savedPasspointProviderProfiles.size());
+            incrementBssid(mAvailableSavedPasspointProviderBssidsInScanHistogram,
+                    savedPasspointProviderBssids);
+        }
+    }
+
     public static final String PROTO_DUMP_ARG = "wifiMetricsProto";
     public static final String CLEAN_DUMP_ARG = "clean";
 
@@ -1275,14 +1404,36 @@
                         + mWifiLogProto.numPasspointProviderUninstallSuccess);
                 pw.println("mWifiLogProto.numPasspointProvidersSuccessfullyConnected="
                         + mWifiLogProto.numPasspointProvidersSuccessfullyConnected);
-
+                pw.println("mTotalSsidsInScanHistogram:"
+                        + mTotalSsidsInScanHistogram.toString());
+                pw.println("mTotalBssidsInScanHistogram:"
+                        + mTotalBssidsInScanHistogram.toString());
+                pw.println("mAvailableOpenSsidsInScanHistogram:"
+                        + mAvailableOpenSsidsInScanHistogram.toString());
+                pw.println("mAvailableOpenBssidsInScanHistogram:"
+                        + mAvailableOpenBssidsInScanHistogram.toString());
+                pw.println("mAvailableSavedSsidsInScanHistogram:"
+                        + mAvailableSavedSsidsInScanHistogram.toString());
+                pw.println("mAvailableSavedBssidsInScanHistogram:"
+                        + mAvailableSavedBssidsInScanHistogram.toString());
+                pw.println("mAvailableOpenOrSavedSsidsInScanHistogram:"
+                        + mAvailableOpenOrSavedSsidsInScanHistogram.toString());
+                pw.println("mAvailableOpenOrSavedBssidsInScanHistogram:"
+                        + mAvailableOpenOrSavedBssidsInScanHistogram.toString());
+                pw.println("mAvailableSavedPasspointProviderProfilesInScanHistogram:"
+                        + mAvailableSavedPasspointProviderProfilesInScanHistogram.toString());
+                pw.println("mAvailableSavedPasspointProviderBssidsInScanHistogram:"
+                        + mAvailableSavedPasspointProviderBssidsInScanHistogram.toString());
+                pw.println("mWifiLogProto.partialAllSingleScanListenerResults="
+                        + mWifiLogProto.partialAllSingleScanListenerResults);
+                pw.println("mWifiLogProto.fullBandAllSingleScanListenerResults="
+                        + mWifiLogProto.fullBandAllSingleScanListenerResults);
                 pw.println("mWifiAwareMetrics:");
                 mWifiAwareMetrics.dump(fd, pw, args);
             }
         }
     }
 
-
     /**
      * Update various counts of saved network types
      * @param networks List of WifiConfigurations representing all saved networks, must not be null
@@ -1454,13 +1605,49 @@
                 mWifiLogProto.softApReturnCode[sapCode].count =
                         mSoftApManagerReturnCodeCounts.valueAt(sapCode);
             }
-
+            mWifiLogProto.totalSsidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mTotalSsidsInScanHistogram);
+            mWifiLogProto.totalBssidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mTotalBssidsInScanHistogram);
+            mWifiLogProto.availableOpenSsidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mAvailableOpenSsidsInScanHistogram);
+            mWifiLogProto.availableOpenBssidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mAvailableOpenBssidsInScanHistogram);
+            mWifiLogProto.availableSavedSsidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mAvailableSavedSsidsInScanHistogram);
+            mWifiLogProto.availableSavedBssidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mAvailableSavedBssidsInScanHistogram);
+            mWifiLogProto.availableOpenOrSavedSsidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                    mAvailableOpenOrSavedSsidsInScanHistogram);
+            mWifiLogProto.availableOpenOrSavedBssidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                    mAvailableOpenOrSavedBssidsInScanHistogram);
+            mWifiLogProto.availableSavedPasspointProviderProfilesInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                    mAvailableSavedPasspointProviderProfilesInScanHistogram);
+            mWifiLogProto.availableSavedPasspointProviderBssidsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                    mAvailableSavedPasspointProviderBssidsInScanHistogram);
             mWifiLogProto.staEventList = mStaEventList.toArray(mWifiLogProto.staEventList);
-
             mWifiLogProto.wifiAwareLog = mWifiAwareMetrics.consolidateProto();
         }
     }
 
+    private WifiMetricsProto.NumConnectableNetworksBucket[] makeNumConnectableNetworksBucketArray(
+            SparseIntArray sia) {
+        WifiMetricsProto.NumConnectableNetworksBucket[] array =
+                new WifiMetricsProto.NumConnectableNetworksBucket[sia.size()];
+        for (int i = 0; i < sia.size(); i++) {
+            WifiMetricsProto.NumConnectableNetworksBucket keyVal =
+                    new WifiMetricsProto.NumConnectableNetworksBucket();
+            keyVal.numConnectableNetworks = sia.keyAt(i);
+            keyVal.count = sia.valueAt(i);
+            array[i] = keyVal;
+        }
+        return array;
+    }
+
     /**
      * Clear all WifiMetrics, except for currentConnectionEvent.
      */
@@ -1482,6 +1669,16 @@
             mSoftApManagerReturnCodeCounts.clear();
             mStaEventList.clear();
             mWifiAwareMetrics.clear();
+            mTotalSsidsInScanHistogram.clear();
+            mTotalBssidsInScanHistogram.clear();
+            mAvailableOpenSsidsInScanHistogram.clear();
+            mAvailableOpenBssidsInScanHistogram.clear();
+            mAvailableSavedSsidsInScanHistogram.clear();
+            mAvailableSavedBssidsInScanHistogram.clear();
+            mAvailableOpenOrSavedSsidsInScanHistogram.clear();
+            mAvailableOpenOrSavedBssidsInScanHistogram.clear();
+            mAvailableSavedPasspointProviderProfilesInScanHistogram.clear();
+            mAvailableSavedPasspointProviderBssidsInScanHistogram.clear();
         }
     }
 
@@ -1872,4 +2069,20 @@
         }
         return value;
     }
+    private void incrementSsid(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_CONNECTABLE_SSID_NETWORK_BUCKET));
+    }
+    private void incrementBssid(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_CONNECTABLE_BSSID_NETWORK_BUCKET));
+    }
+    private void incrementTotalScanResults(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_SCAN_RESULTS_BUCKET));
+    }
+    private void incrementTotalScanSsids(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET));
+    }
+    private void increment(SparseIntArray sia, int element) {
+        int count = sia.get(element);
+        sia.put(element, count + 1);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiNetworkSelector.java b/service/java/com/android/server/wifi/WifiNetworkSelector.java
index 5e9e4ef..fe24705 100644
--- a/service/java/com/android/server/wifi/WifiNetworkSelector.java
+++ b/service/java/com/android/server/wifi/WifiNetworkSelector.java
@@ -258,6 +258,14 @@
         return (network.SSID + ":" + network.networkId);
     }
 
+    /**
+     * Compares ScanResult level against the minimum threshold for its band, returns true if lower
+     */
+    public boolean isSignalTooWeak(ScanResult scanResult) {
+        return ((scanResult.is24GHz() && scanResult.level < mThresholdMinimumRssi24)
+                || (scanResult.is5GHz() && scanResult.level < mThresholdMinimumRssi5));
+    }
+
     private List<ScanDetail> filterScanResults(List<ScanDetail> scanDetails,
                 HashSet<String> bssidBlacklist, boolean isConnected, String currentBssid) {
         ArrayList<NetworkKey> unscoredNetworks = new ArrayList<NetworkKey>();
@@ -288,10 +296,7 @@
             }
 
             // Skip network with too weak signals.
-            if ((scanResult.is24GHz() && scanResult.level
-                    < mThresholdMinimumRssi24)
-                    || (scanResult.is5GHz() && scanResult.level
-                    < mThresholdMinimumRssi5)) {
+            if (isSignalTooWeak(scanResult)) {
                 lowRssi.append(scanId).append("(")
                     .append(scanResult.is24GHz() ? "2.4GHz" : "5GHz")
                     .append(")").append(scanResult.level).append(" / ");
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index e7216ce..5a13928 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -29,10 +29,13 @@
 import android.os.test.TestLooper;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Base64;
-
+import android.util.Pair;
 
 import com.android.server.wifi.aware.WifiAwareMetrics;
 import com.android.server.wifi.hotspot2.NetworkDetail;
+import com.android.server.wifi.hotspot2.PasspointManager;
+import com.android.server.wifi.hotspot2.PasspointMatch;
+import com.android.server.wifi.hotspot2.PasspointProvider;
 import com.android.server.wifi.nano.WifiMetricsProto;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 
@@ -58,18 +61,24 @@
 public class WifiMetricsTest {
 
     WifiMetrics mWifiMetrics;
-    WifiMetricsProto.WifiLog mDeserializedWifiMetrics;
+    WifiMetricsProto.WifiLog mDecodedProto;
     TestLooper mTestLooper;
     @Mock Clock mClock;
+    @Mock WifiConfigManager mWcm;
+    @Mock PasspointManager mPpm;
+    @Mock WifiNetworkSelector mWns;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
-        mDeserializedWifiMetrics = null;
+        mDecodedProto = null;
         when(mClock.getElapsedSinceBootMillis()).thenReturn((long) 0);
         mTestLooper = new TestLooper();
         mWifiMetrics = new WifiMetrics(mClock, mTestLooper.getLooper(),
                 new WifiAwareMetrics(mClock));
+        mWifiMetrics.setWifiConfigManager(mWcm);
+        mWifiMetrics.setPasspointManager(mPpm);
+        mWifiMetrics.setWifiNetworkSelector(mWns);
     }
 
     /**
@@ -97,10 +106,9 @@
 
     private static final long TEST_RECORD_DURATION_SEC = 12 * 60 * 60;
     private static final long TEST_RECORD_DURATION_MILLIS = TEST_RECORD_DURATION_SEC * 1000;
-
     /**
      * Simulate how dumpsys gets the proto from mWifiMetrics, filter the proto bytes out and
-     * deserialize them into mDeserializedWifiMetrics
+     * deserialize them into mDecodedProto
      */
     public void dumpProtoAndDeserialize() throws Exception {
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
@@ -119,12 +127,12 @@
                 matcher.find());
         String protoByteString = matcher.group(1);
         byte[] protoBytes = Base64.decode(protoByteString, Base64.DEFAULT);
-        mDeserializedWifiMetrics = WifiMetricsProto.WifiLog.parseFrom(protoBytes);
+        mDecodedProto = WifiMetricsProto.WifiLog.parseFrom(protoBytes);
     }
 
     /**
      * Gets the 'clean dump' proto bytes from mWifiMetrics & deserializes it into
-     * mDeserializedWifiMetrics
+     * mDecodedProto
      */
     public void cleanDumpProtoAndDeserialize() throws Exception {
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
@@ -138,7 +146,7 @@
         writer.flush();
         String protoByteString = stream.toString();
         byte[] protoBytes = Base64.decode(protoByteString, Base64.DEFAULT);
-        mDeserializedWifiMetrics = WifiMetricsProto.WifiLog.parseFrom(protoBytes);
+        mDecodedProto = WifiMetricsProto.WifiLog.parseFrom(protoBytes);
     }
 
     /** Verifies that dump() includes the expected header */
@@ -243,7 +251,7 @@
     private static final int NUM_PASSPOINT_PROVIDER_UNINSTALLATION = 3;
     private static final int NUM_PASSPOINT_PROVIDER_UNINSTALL_SUCCESS = 2;
     private static final int NUM_PASSPOINT_PROVIDERS_SUCCESSFULLY_CONNECTED = 1;
-
+    private static final int NUM_PARTIAL_SCAN_RESULTS = 73;
 
     private ScanDetail buildMockScanDetail(boolean hidden, NetworkDetail.HSRelease hSRelease,
             String capabilities) {
@@ -258,6 +266,30 @@
         return mockScanDetail;
     }
 
+    private ScanDetail buildMockScanDetail(String ssid, String bssid, boolean isOpen,
+            boolean isSaved, boolean isProvider, boolean isWeakRssi) {
+        ScanDetail mockScanDetail = mock(ScanDetail.class);
+        NetworkDetail mockNetworkDetail = mock(NetworkDetail.class);
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = ssid;
+        scanResult.BSSID = bssid;
+        when(mockScanDetail.getNetworkDetail()).thenReturn(mockNetworkDetail);
+        when(mockScanDetail.getScanResult()).thenReturn(scanResult);
+        when(mWns.isSignalTooWeak(eq(scanResult))).thenReturn(isWeakRssi);
+        scanResult.capabilities = isOpen ? "" : "PSK";
+        if (isSaved) {
+            when(mWcm.getConfiguredNetworkForScanDetail(eq(mockScanDetail)))
+                    .thenReturn(mock(WifiConfiguration.class));
+        }
+        if (isProvider) {
+            PasspointProvider provider = mock(PasspointProvider.class);
+            Pair<PasspointProvider, PasspointMatch> providerMatch = Pair.create(provider, null);
+            when(mockNetworkDetail.isInterworking()).thenReturn(true);
+            when(mPpm.matchProvider(eq(scanResult))).thenReturn(providerMatch);
+        }
+        return mockScanDetail;
+    }
+
     private List<ScanDetail> buildMockScanDetailList() {
         List<ScanDetail> mockScanDetails = new ArrayList<ScanDetail>();
         mockScanDetails.add(buildMockScanDetail(true, null, "[ESS]"));
@@ -448,37 +480,30 @@
      * Assert that values in deserializedWifiMetrics match those set in 'setAndIncrementMetrics'
      */
     public void assertDeserializedMetricsCorrect() throws Exception {
-        assertEquals("mDeserializedWifiMetrics.numSavedNetworks == NUM_SAVED_NETWORKS",
-                mDeserializedWifiMetrics.numSavedNetworks, NUM_SAVED_NETWORKS);
-        assertEquals("mDeserializedWifiMetrics.numOpenNetworks == NUM_OPEN_NETWORKS",
-                mDeserializedWifiMetrics.numOpenNetworks, NUM_OPEN_NETWORKS);
-        assertEquals("mDeserializedWifiMetrics.numPersonalNetworks == NUM_PERSONAL_NETWORKS",
-                mDeserializedWifiMetrics.numPersonalNetworks, NUM_PERSONAL_NETWORKS);
-        assertEquals("mDeserializedWifiMetrics.numEnterpriseNetworks "
-                        + "== NUM_ENTERPRISE_NETWORKS",
-                mDeserializedWifiMetrics.numEnterpriseNetworks, NUM_ENTERPRISE_NETWORKS);
-        assertEquals("mDeserializedWifiMetrics.numNetworksAddedByUser "
-                        + "== NUM_NETWORKS_ADDED_BY_USER",
-                mDeserializedWifiMetrics.numNetworksAddedByUser, NUM_NETWORKS_ADDED_BY_USER);
-        assertEquals(NUM_HIDDEN_NETWORKS, mDeserializedWifiMetrics.numHiddenNetworks);
-        assertEquals(NUM_PASSPOINT_NETWORKS, mDeserializedWifiMetrics.numPasspointNetworks);
-        assertEquals("mDeserializedWifiMetrics.numNetworksAddedByApps "
-                        + "== NUM_NETWORKS_ADDED_BY_APPS",
-                mDeserializedWifiMetrics.numNetworksAddedByApps, NUM_NETWORKS_ADDED_BY_APPS);
-        assertEquals("mDeserializedWifiMetrics.isLocationEnabled == TEST_VAL_IS_LOCATION_ENABLED",
-                mDeserializedWifiMetrics.isLocationEnabled, TEST_VAL_IS_LOCATION_ENABLED);
-        assertEquals("mDeserializedWifiMetrics.isScanningAlwaysEnabled "
-                        + "== IS_SCANNING_ALWAYS_ENABLED",
-                mDeserializedWifiMetrics.isScanningAlwaysEnabled, IS_SCANNING_ALWAYS_ENABLED);
-        assertEquals("mDeserializedWifiMetrics.numEmptyScanResults == NUM_EMPTY_SCAN_RESULTS",
-                mDeserializedWifiMetrics.numEmptyScanResults, NUM_EMPTY_SCAN_RESULTS);
-        assertEquals("mDeserializedWifiMetrics.numNonEmptyScanResults == "
-                        + "NUM_NON_EMPTY_SCAN_RESULTS",
-                mDeserializedWifiMetrics.numNonEmptyScanResults, NUM_NON_EMPTY_SCAN_RESULTS);
-        assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_UNKNOWN,
-                NUM_SCAN_UNKNOWN);
-        assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_SUCCESS,
-                NUM_SCAN_SUCCESS);
+        assertEquals("mDecodedProto.numSavedNetworks == NUM_SAVED_NETWORKS",
+                mDecodedProto.numSavedNetworks, NUM_SAVED_NETWORKS);
+        assertEquals("mDecodedProto.numOpenNetworks == NUM_OPEN_NETWORKS",
+                mDecodedProto.numOpenNetworks, NUM_OPEN_NETWORKS);
+        assertEquals("mDecodedProto.numPersonalNetworks == NUM_PERSONAL_NETWORKS",
+                mDecodedProto.numPersonalNetworks, NUM_PERSONAL_NETWORKS);
+        assertEquals("mDecodedProto.numEnterpriseNetworks == NUM_ENTERPRISE_NETWORKS",
+                mDecodedProto.numEnterpriseNetworks, NUM_ENTERPRISE_NETWORKS);
+        assertEquals("mDecodedProto.numNetworksAddedByUser == NUM_NETWORKS_ADDED_BY_USER",
+                mDecodedProto.numNetworksAddedByUser, NUM_NETWORKS_ADDED_BY_USER);
+        assertEquals(NUM_HIDDEN_NETWORKS, mDecodedProto.numHiddenNetworks);
+        assertEquals(NUM_PASSPOINT_NETWORKS, mDecodedProto.numPasspointNetworks);
+        assertEquals("mDecodedProto.numNetworksAddedByApps == NUM_NETWORKS_ADDED_BY_APPS",
+                mDecodedProto.numNetworksAddedByApps, NUM_NETWORKS_ADDED_BY_APPS);
+        assertEquals("mDecodedProto.isLocationEnabled == TEST_VAL_IS_LOCATION_ENABLED",
+                mDecodedProto.isLocationEnabled, TEST_VAL_IS_LOCATION_ENABLED);
+        assertEquals("mDecodedProto.isScanningAlwaysEnabled == IS_SCANNING_ALWAYS_ENABLED",
+                mDecodedProto.isScanningAlwaysEnabled, IS_SCANNING_ALWAYS_ENABLED);
+        assertEquals("mDecodedProto.numEmptyScanResults == NUM_EMPTY_SCAN_RESULTS",
+                mDecodedProto.numEmptyScanResults, NUM_EMPTY_SCAN_RESULTS);
+        assertEquals("mDecodedProto.numNonEmptyScanResults == NUM_NON_EMPTY_SCAN_RESULTS",
+                mDecodedProto.numNonEmptyScanResults, NUM_NON_EMPTY_SCAN_RESULTS);
+        assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_UNKNOWN, NUM_SCAN_UNKNOWN);
+        assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_SUCCESS, NUM_SCAN_SUCCESS);
         assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED,
                 NUM_SCAN_FAILURE_INTERRUPTED);
         assertScanReturnEntryEquals(WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION,
@@ -491,119 +516,117 @@
                 WifiMetricsProto.WifiLog.WIFI_ASSOCIATED, false, NUM_WIFI_ASSOCIATED_SCREEN_OFF);
         assertSystemStateEntryEquals(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED, true,
                 NUM_WIFI_ASSOCIATED_SCREEN_ON);
-        assertEquals(mDeserializedWifiMetrics.numConnectivityWatchdogPnoGood,
+        assertEquals(mDecodedProto.numConnectivityWatchdogPnoGood,
                 NUM_CONNECTIVITY_WATCHDOG_PNO_GOOD);
-        assertEquals(mDeserializedWifiMetrics.numConnectivityWatchdogPnoBad,
+        assertEquals(mDecodedProto.numConnectivityWatchdogPnoBad,
                 NUM_CONNECTIVITY_WATCHDOG_PNO_BAD);
-        assertEquals(mDeserializedWifiMetrics.numConnectivityWatchdogBackgroundGood,
+        assertEquals(mDecodedProto.numConnectivityWatchdogBackgroundGood,
                 NUM_CONNECTIVITY_WATCHDOG_BACKGROUND_GOOD);
-        assertEquals(mDeserializedWifiMetrics.numConnectivityWatchdogBackgroundBad,
+        assertEquals(mDecodedProto.numConnectivityWatchdogBackgroundBad,
                 NUM_CONNECTIVITY_WATCHDOG_BACKGROUND_BAD);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_TRIGGERS,
-                mDeserializedWifiMetrics.numLastResortWatchdogTriggers);
+                mDecodedProto.numLastResortWatchdogTriggers);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_BAD_ASSOCIATION_NETWORKS_TOTAL,
-                mDeserializedWifiMetrics.numLastResortWatchdogBadAssociationNetworksTotal);
+                mDecodedProto.numLastResortWatchdogBadAssociationNetworksTotal);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_BAD_AUTHENTICATION_NETWORKS_TOTAL,
-                mDeserializedWifiMetrics.numLastResortWatchdogBadAuthenticationNetworksTotal);
+                mDecodedProto.numLastResortWatchdogBadAuthenticationNetworksTotal);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_BAD_DHCP_NETWORKS_TOTAL,
-                mDeserializedWifiMetrics.numLastResortWatchdogBadDhcpNetworksTotal);
+                mDecodedProto.numLastResortWatchdogBadDhcpNetworksTotal);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_BAD_OTHER_NETWORKS_TOTAL,
-                mDeserializedWifiMetrics.numLastResortWatchdogBadOtherNetworksTotal);
+                mDecodedProto.numLastResortWatchdogBadOtherNetworksTotal);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_AVAILABLE_NETWORKS_TOTAL,
-                mDeserializedWifiMetrics.numLastResortWatchdogAvailableNetworksTotal);
+                mDecodedProto.numLastResortWatchdogAvailableNetworksTotal);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_ASSOCIATION,
-                mDeserializedWifiMetrics.numLastResortWatchdogTriggersWithBadAssociation);
+                mDecodedProto.numLastResortWatchdogTriggersWithBadAssociation);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_AUTHENTICATION,
-                mDeserializedWifiMetrics.numLastResortWatchdogTriggersWithBadAuthentication);
+                mDecodedProto.numLastResortWatchdogTriggersWithBadAuthentication);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_DHCP,
-                mDeserializedWifiMetrics.numLastResortWatchdogTriggersWithBadDhcp);
+                mDecodedProto.numLastResortWatchdogTriggersWithBadDhcp);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_TRIGGERS_WITH_BAD_OTHER,
-                mDeserializedWifiMetrics.numLastResortWatchdogTriggersWithBadOther);
+                mDecodedProto.numLastResortWatchdogTriggersWithBadOther);
         assertEquals(NUM_LAST_RESORT_WATCHDOG_SUCCESSES,
-                mDeserializedWifiMetrics.numLastResortWatchdogSuccesses);
+                mDecodedProto.numLastResortWatchdogSuccesses);
         assertEquals(TEST_RECORD_DURATION_SEC,
-                mDeserializedWifiMetrics.recordDurationSec);
+                mDecodedProto.recordDurationSec);
         for (int i = 0; i < NUM_RSSI_LEVELS_TO_INCREMENT; i++) {
-            assertEquals(MIN_RSSI_LEVEL + i, mDeserializedWifiMetrics.rssiPollRssiCount[i].rssi);
-            assertEquals(i + 1, mDeserializedWifiMetrics.rssiPollRssiCount[i].count);
+            assertEquals(MIN_RSSI_LEVEL + i, mDecodedProto.rssiPollRssiCount[i].rssi);
+            assertEquals(i + 1, mDecodedProto.rssiPollRssiCount[i].count);
         }
         StringBuilder sb_rssi = new StringBuilder();
-        sb_rssi.append("Number of RSSIs = " + mDeserializedWifiMetrics.rssiPollRssiCount.length);
-        assertTrue(sb_rssi.toString(), (mDeserializedWifiMetrics.rssiPollRssiCount.length
+        sb_rssi.append("Number of RSSIs = " + mDecodedProto.rssiPollRssiCount.length);
+        assertTrue(sb_rssi.toString(), (mDecodedProto.rssiPollRssiCount.length
                      <= (MAX_RSSI_LEVEL - MIN_RSSI_LEVEL + 1)));
-        assertEquals(2, mDeserializedWifiMetrics.alertReasonCount[0].count);  // Clamped reasons.
-        assertEquals(3, mDeserializedWifiMetrics.alertReasonCount[1].count);
-        assertEquals(1, mDeserializedWifiMetrics.alertReasonCount[2].count);
-        assertEquals(3, mDeserializedWifiMetrics.alertReasonCount.length);
+        assertEquals(2, mDecodedProto.alertReasonCount[0].count);  // Clamped reasons.
+        assertEquals(3, mDecodedProto.alertReasonCount[1].count);
+        assertEquals(1, mDecodedProto.alertReasonCount[2].count);
+        assertEquals(3, mDecodedProto.alertReasonCount.length);
         assertEquals(NUM_TOTAL_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numTotalScanResults);
+                mDecodedProto.numTotalScanResults);
         assertEquals(NUM_OPEN_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numOpenNetworkScanResults);
+                mDecodedProto.numOpenNetworkScanResults);
         assertEquals(NUM_PERSONAL_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numPersonalNetworkScanResults);
+                mDecodedProto.numPersonalNetworkScanResults);
         assertEquals(NUM_ENTERPRISE_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numEnterpriseNetworkScanResults);
+                mDecodedProto.numEnterpriseNetworkScanResults);
         assertEquals(NUM_HIDDEN_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numHiddenNetworkScanResults);
+                mDecodedProto.numHiddenNetworkScanResults);
         assertEquals(NUM_HOTSPOT2_R1_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numHotspot2R1NetworkScanResults);
+                mDecodedProto.numHotspot2R1NetworkScanResults);
         assertEquals(NUM_HOTSPOT2_R2_NETWORK_SCAN_RESULTS * NUM_SCANS,
-                mDeserializedWifiMetrics.numHotspot2R2NetworkScanResults);
+                mDecodedProto.numHotspot2R2NetworkScanResults);
         assertEquals(NUM_SCANS,
-                mDeserializedWifiMetrics.numScans);
+                mDecodedProto.numScans);
         for (int score_index = 0; score_index < NUM_WIFI_SCORES_TO_INCREMENT; score_index++) {
             assertEquals(WIFI_SCORE_RANGE_MIN + score_index,
-                    mDeserializedWifiMetrics.wifiScoreCount[score_index].score);
+                    mDecodedProto.wifiScoreCount[score_index].score);
             assertEquals(score_index + 1,
-                    mDeserializedWifiMetrics.wifiScoreCount[score_index].count);
+                    mDecodedProto.wifiScoreCount[score_index].count);
         }
         StringBuilder sb_wifi_score = new StringBuilder();
-        sb_wifi_score.append("Number of wifi_scores = "
-                + mDeserializedWifiMetrics.wifiScoreCount.length);
-        assertTrue(sb_wifi_score.toString(), (mDeserializedWifiMetrics.wifiScoreCount.length
+        sb_wifi_score.append("Number of wifi_scores = " + mDecodedProto.wifiScoreCount.length);
+        assertTrue(sb_wifi_score.toString(), (mDecodedProto.wifiScoreCount.length
                 <= (WIFI_SCORE_RANGE_MAX - WIFI_SCORE_RANGE_MIN + 1)));
         StringBuilder sb_wifi_limits = new StringBuilder();
         sb_wifi_limits.append("Wifi Score limit is " +  NetworkAgent.WIFI_BASE_SCORE
                 + ">= " + WIFI_SCORE_RANGE_MAX);
         assertTrue(sb_wifi_limits.toString(), NetworkAgent.WIFI_BASE_SCORE <= WIFI_SCORE_RANGE_MAX);
-        assertEquals(MAX_NUM_SOFTAP_RETURN_CODES, mDeserializedWifiMetrics.softApReturnCode.length);
+        assertEquals(MAX_NUM_SOFTAP_RETURN_CODES, mDecodedProto.softApReturnCode.length);
         assertEquals(WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_STARTED_SUCCESSFULLY,
-                     mDeserializedWifiMetrics.softApReturnCode[0].startResult);
-        assertEquals(NUM_SOFTAP_START_SUCCESS, mDeserializedWifiMetrics.softApReturnCode[0].count);
+                     mDecodedProto.softApReturnCode[0].startResult);
+        assertEquals(NUM_SOFTAP_START_SUCCESS, mDecodedProto.softApReturnCode[0].count);
         assertEquals(WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_GENERAL_ERROR,
-                     mDeserializedWifiMetrics.softApReturnCode[1].startResult);
+                     mDecodedProto.softApReturnCode[1].startResult);
         assertEquals(NUM_SOFTAP_FAILED_GENERAL_ERROR,
-                     mDeserializedWifiMetrics.softApReturnCode[1].count);
+                     mDecodedProto.softApReturnCode[1].count);
         assertEquals(WifiMetricsProto.SoftApReturnCodeCount.SOFT_AP_FAILED_NO_CHANNEL,
-                     mDeserializedWifiMetrics.softApReturnCode[2].startResult);
+                     mDecodedProto.softApReturnCode[2].startResult);
         assertEquals(NUM_SOFTAP_FAILED_NO_CHANNEL,
-                     mDeserializedWifiMetrics.softApReturnCode[2].count);
-        assertEquals(NUM_HAL_CRASHES, mDeserializedWifiMetrics.numHalCrashes);
-        assertEquals(NUM_WIFICOND_CRASHES, mDeserializedWifiMetrics.numWificondCrashes);
-        assertEquals(NUM_WIFI_ON_FAILURE_DUE_TO_HAL,
-                mDeserializedWifiMetrics.numWifiOnFailureDueToHal);
+                     mDecodedProto.softApReturnCode[2].count);
+        assertEquals(NUM_HAL_CRASHES, mDecodedProto.numHalCrashes);
+        assertEquals(NUM_WIFICOND_CRASHES, mDecodedProto.numWificondCrashes);
+        assertEquals(NUM_WIFI_ON_FAILURE_DUE_TO_HAL, mDecodedProto.numWifiOnFailureDueToHal);
         assertEquals(NUM_WIFI_ON_FAILURE_DUE_TO_WIFICOND,
-                mDeserializedWifiMetrics.numWifiOnFailureDueToWificond);
-        assertEquals(NUM_PASSPOINT_PROVIDERS, mDeserializedWifiMetrics.numPasspointProviders);
+                mDecodedProto.numWifiOnFailureDueToWificond);
+        assertEquals(NUM_PASSPOINT_PROVIDERS, mDecodedProto.numPasspointProviders);
         assertEquals(NUM_PASSPOINT_PROVIDER_INSTALLATION,
-                mDeserializedWifiMetrics.numPasspointProviderInstallation);
+                mDecodedProto.numPasspointProviderInstallation);
         assertEquals(NUM_PASSPOINT_PROVIDER_INSTALL_SUCCESS,
-                mDeserializedWifiMetrics.numPasspointProviderInstallSuccess);
+                mDecodedProto.numPasspointProviderInstallSuccess);
         assertEquals(NUM_PASSPOINT_PROVIDER_UNINSTALLATION,
-                mDeserializedWifiMetrics.numPasspointProviderUninstallation);
+                mDecodedProto.numPasspointProviderUninstallation);
         assertEquals(NUM_PASSPOINT_PROVIDER_UNINSTALL_SUCCESS,
-                mDeserializedWifiMetrics.numPasspointProviderUninstallSuccess);
+                mDecodedProto.numPasspointProviderUninstallSuccess);
         assertEquals(NUM_PASSPOINT_PROVIDERS_SUCCESSFULLY_CONNECTED,
-                mDeserializedWifiMetrics.numPasspointProvidersSuccessfullyConnected);
+                mDecodedProto.numPasspointProvidersSuccessfullyConnected);
     }
 
     /**
      *  Assert deserialized metrics Scan Return Entry equals count
      */
     public void assertScanReturnEntryEquals(int returnCode, int count) {
-        for (int i = 0; i < mDeserializedWifiMetrics.scanReturnEntries.length; i++) {
-            if (mDeserializedWifiMetrics.scanReturnEntries[i].scanReturnCode == returnCode) {
-                assertEquals(mDeserializedWifiMetrics.scanReturnEntries[i].scanResultsCount, count);
+        for (int i = 0; i < mDecodedProto.scanReturnEntries.length; i++) {
+            if (mDecodedProto.scanReturnEntries[i].scanReturnCode == returnCode) {
+                assertEquals(mDecodedProto.scanReturnEntries[i].scanResultsCount, count);
                 return;
             }
         }
@@ -614,10 +637,10 @@
      *  Assert deserialized metrics SystemState entry equals count
      */
     public void assertSystemStateEntryEquals(int state, boolean screenOn, int count) {
-        for (int i = 0; i < mDeserializedWifiMetrics.wifiSystemStateEntries.length; i++) {
-            if (mDeserializedWifiMetrics.wifiSystemStateEntries[i].wifiState == state
-                    && mDeserializedWifiMetrics.wifiSystemStateEntries[i].isScreenOn == screenOn) {
-                assertEquals(mDeserializedWifiMetrics.wifiSystemStateEntries[i].wifiStateCount,
+        for (int i = 0; i < mDecodedProto.wifiSystemStateEntries.length; i++) {
+            if (mDecodedProto.wifiSystemStateEntries[i].wifiState == state
+                    && mDecodedProto.wifiSystemStateEntries[i].isScreenOn == screenOn) {
+                assertEquals(mDecodedProto.wifiSystemStateEntries[i].wifiStateCount,
                         count);
                 return;
             }
@@ -634,8 +657,8 @@
         startAndEndConnectionEventSucceeds();
         dumpProtoAndDeserialize();
         assertDeserializedMetricsCorrect();
-        assertEquals("mDeserializedWifiMetrics.connectionEvent.length",
-                2, mDeserializedWifiMetrics.connectionEvent.length);
+        assertEquals("mDecodedProto.connectionEvent.length",
+                2, mDecodedProto.connectionEvent.length);
         //<TODO> test individual connectionEvents for correctness,
         // check scanReturnEntries & wifiSystemStateEntries counts and individual elements
         // pending their implementation</TODO>
@@ -684,19 +707,19 @@
                 WifiMetrics.ConnectionEvent.FAILURE_NONE,
                 WifiMetricsProto.ConnectionEvent.HLF_NONE);
 
-        //Dump proto from mWifiMetrics and deserialize it to mDeserializedWifiMetrics
+        //Dump proto from mWifiMetrics and deserialize it to mDecodedProto
         dumpProtoAndDeserialize();
 
         //Check that the correct values are being flowed through
-        assertEquals(mDeserializedWifiMetrics.connectionEvent.length, 2);
-        assertEquals(mDeserializedWifiMetrics.connectionEvent[0].routerFingerprint.dtim,
+        assertEquals(mDecodedProto.connectionEvent.length, 2);
+        assertEquals(mDecodedProto.connectionEvent[0].routerFingerprint.dtim,
                 CONFIG_DTIM);
-        assertEquals(mDeserializedWifiMetrics.connectionEvent[0].signalStrength, SCAN_RESULT_LEVEL);
-        assertEquals(mDeserializedWifiMetrics.connectionEvent[1].routerFingerprint.dtim,
+        assertEquals(mDecodedProto.connectionEvent[0].signalStrength, SCAN_RESULT_LEVEL);
+        assertEquals(mDecodedProto.connectionEvent[1].routerFingerprint.dtim,
                 NETWORK_DETAIL_DTIM);
-        assertEquals(mDeserializedWifiMetrics.connectionEvent[1].signalStrength,
+        assertEquals(mDecodedProto.connectionEvent[1].signalStrength,
                 SCAN_RESULT_LEVEL);
-        assertEquals(mDeserializedWifiMetrics.connectionEvent[1].routerFingerprint.routerTechnology,
+        assertEquals(mDecodedProto.connectionEvent[1].routerFingerprint.routerTechnology,
                 NETWORK_DETAIL_WIFIMODE);
     }
 
@@ -731,9 +754,9 @@
         //This should clear all the metrics in mWifiMetrics,
         dumpProtoAndDeserialize();
         //Check there are only 3 connection events
-        assertEquals(mDeserializedWifiMetrics.connectionEvent.length, 4);
-        assertEquals(mDeserializedWifiMetrics.rssiPollRssiCount.length, 0);
-        assertEquals(mDeserializedWifiMetrics.alertReasonCount.length, 0);
+        assertEquals(mDecodedProto.connectionEvent.length, 4);
+        assertEquals(mDecodedProto.rssiPollRssiCount.length, 0);
+        assertEquals(mDecodedProto.alertReasonCount.length, 0);
 
         // Create 2 ConnectionEvents
         mWifiMetrics.startConnectionEvent(null,  "BLUE",
@@ -750,7 +773,7 @@
         //Dump proto and deserialize
         dumpProtoAndDeserialize();
         //Check there are only 2 connection events
-        assertEquals(mDeserializedWifiMetrics.connectionEvent.length, 2);
+        assertEquals(mDecodedProto.connectionEvent.length, 2);
     }
 
     /**
@@ -763,8 +786,8 @@
         startAndEndConnectionEventSucceeds();
         cleanDumpProtoAndDeserialize();
         assertDeserializedMetricsCorrect();
-        assertEquals("mDeserializedWifiMetrics.connectionEvent.length",
-                2, mDeserializedWifiMetrics.connectionEvent.length);
+        assertEquals("mDecodedProto.connectionEvent.length",
+                2, mDecodedProto.connectionEvent.length);
     }
 
     private static final int NUM_REPEATED_DELTAS = 7;
@@ -792,13 +815,13 @@
         generateRssiDelta(MIN_RSSI_LEVEL, SINGLE_GOOD_DELTA,
                 WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
         dumpProtoAndDeserialize();
-        assertEquals(2, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(2, mDecodedProto.rssiPollDeltaCount.length);
         // Check the repeated deltas
-        assertEquals(NUM_REPEATED_DELTAS, mDeserializedWifiMetrics.rssiPollDeltaCount[0].count);
-        assertEquals(REPEATED_DELTA, mDeserializedWifiMetrics.rssiPollDeltaCount[0].rssi);
+        assertEquals(NUM_REPEATED_DELTAS, mDecodedProto.rssiPollDeltaCount[0].count);
+        assertEquals(REPEATED_DELTA, mDecodedProto.rssiPollDeltaCount[0].rssi);
         // Check the single delta
-        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[1].count);
-        assertEquals(SINGLE_GOOD_DELTA, mDeserializedWifiMetrics.rssiPollDeltaCount[1].rssi);
+        assertEquals(1, mDecodedProto.rssiPollDeltaCount[1].count);
+        assertEquals(SINGLE_GOOD_DELTA, mDecodedProto.rssiPollDeltaCount[1].rssi);
     }
 
     /**
@@ -813,7 +836,7 @@
         generateRssiDelta(MIN_RSSI_LEVEL, SINGLE_TIMEOUT_DELTA,
                 WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS + 1);
         dumpProtoAndDeserialize();
-        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(0, mDecodedProto.rssiPollDeltaCount.length);
     }
 
     /**
@@ -826,11 +849,11 @@
         generateRssiDelta(MAX_RSSI_LEVEL, MIN_DELTA_LEVEL,
                 WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
         dumpProtoAndDeserialize();
-        assertEquals(2, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
-        assertEquals(MIN_DELTA_LEVEL, mDeserializedWifiMetrics.rssiPollDeltaCount[0].rssi);
-        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[0].count);
-        assertEquals(MAX_DELTA_LEVEL, mDeserializedWifiMetrics.rssiPollDeltaCount[1].rssi);
-        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[1].count);
+        assertEquals(2, mDecodedProto.rssiPollDeltaCount.length);
+        assertEquals(MIN_DELTA_LEVEL, mDecodedProto.rssiPollDeltaCount[0].rssi);
+        assertEquals(1, mDecodedProto.rssiPollDeltaCount[0].count);
+        assertEquals(MAX_DELTA_LEVEL, mDecodedProto.rssiPollDeltaCount[1].rssi);
+        assertEquals(1, mDecodedProto.rssiPollDeltaCount[1].count);
     }
 
     /**
@@ -844,7 +867,7 @@
         generateRssiDelta(MAX_RSSI_LEVEL, MIN_DELTA_LEVEL - 1,
                 WifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS);
         dumpProtoAndDeserialize();
-        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(0, mDecodedProto.rssiPollDeltaCount.length);
     }
 
     /**
@@ -861,7 +884,7 @@
         );
 
         dumpProtoAndDeserialize();
-        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(0, mDecodedProto.rssiPollDeltaCount.length);
     }
 
     /**
@@ -877,9 +900,9 @@
                 true // dontDeserializeBeforePoll
         );
         dumpProtoAndDeserialize();
-        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
-        assertEquals(ARBITRARY_DELTA_LEVEL, mDeserializedWifiMetrics.rssiPollDeltaCount[0].rssi);
-        assertEquals(1, mDeserializedWifiMetrics.rssiPollDeltaCount[0].count);
+        assertEquals(1, mDecodedProto.rssiPollDeltaCount.length);
+        assertEquals(ARBITRARY_DELTA_LEVEL, mDecodedProto.rssiPollDeltaCount[0].rssi);
+        assertEquals(1, mDecodedProto.rssiPollDeltaCount[0].count);
     }
 
     /**
@@ -895,7 +918,7 @@
                 true // dontDeserializeBeforePoll
         );
         dumpProtoAndDeserialize();
-        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(0, mDecodedProto.rssiPollDeltaCount.length);
     }
 
     /**
@@ -911,7 +934,7 @@
                 false // dontDeserializeBeforePoll
         );
         dumpProtoAndDeserialize();
-        assertEquals(0, mDeserializedWifiMetrics.rssiPollDeltaCount.length);
+        assertEquals(0, mDecodedProto.rssiPollDeltaCount.length);
     }
 
     private static final int DEAUTH_REASON = 7;
@@ -1049,7 +1072,7 @@
     public void testStaEventsLogSerializeDeserialize() throws Exception {
         generateStaEvents(mWifiMetrics);
         dumpProtoAndDeserialize();
-        verifyDeserializedStaEvents(mDeserializedWifiMetrics);
+        verifyDeserializedStaEvents(mDecodedProto);
     }
 
     /**
@@ -1062,7 +1085,7 @@
             mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_START_CONNECT);
         }
         dumpProtoAndDeserialize();
-        assertEquals(WifiMetrics.MAX_STA_EVENTS, mDeserializedWifiMetrics.staEventList.length);
+        assertEquals(WifiMetrics.MAX_STA_EVENTS, mDecodedProto.staEventList.length);
     }
 
     /**
@@ -1074,6 +1097,95 @@
     }
 
     /**
+     * Test the generation of 'NumConnectableNetwork' histograms from two scans of different
+     * ScanDetails produces the correct histogram values, and relevant bounds are observed
+     */
+    @Test
+    public void testNumConnectableNetworksGeneration() throws Exception {
+        List<ScanDetail> scan = new ArrayList<ScanDetail>();
+        //                                ssid, bssid, isOpen, isSaved, isProvider, isWeakRssi)
+        scan.add(buildMockScanDetail("PASSPOINT_1", "bssid0", false, false, true, false));
+        scan.add(buildMockScanDetail("PASSPOINT_2", "bssid1", false, false, true, false));
+        scan.add(buildMockScanDetail("SSID_B", "bssid2", true, true, false, false));
+        scan.add(buildMockScanDetail("SSID_B", "bssid3", true, true, false, false));
+        scan.add(buildMockScanDetail("SSID_C", "bssid4", true, false, false, false));
+        scan.add(buildMockScanDetail("SSID_D", "bssid5", false, true, false, false));
+        scan.add(buildMockScanDetail("SSID_E", "bssid6", false, true, false, false));
+        scan.add(buildMockScanDetail("SSID_F", "bssid7", false, false, false, false));
+        scan.add(buildMockScanDetail("SSID_G_WEAK", "bssid9", false, false, false, true));
+        scan.add(buildMockScanDetail("SSID_H_WEAK", "bssid10", false, false, false, true));
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        scan.add(buildMockScanDetail("SSID_B", "bssid8", true, true, false, false));
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        for (int i = 0; i < NUM_PARTIAL_SCAN_RESULTS; i++) {
+            mWifiMetrics.incrementAvailableNetworksHistograms(scan, false);
+        }
+        dumpProtoAndDeserialize();
+        verifyHist(mDecodedProto.totalSsidsInScanHistogram, 1,                    a(7),    a(2));
+        verifyHist(mDecodedProto.totalBssidsInScanHistogram, 2,                   a(8, 9), a(1, 1));
+        verifyHist(mDecodedProto.availableOpenSsidsInScanHistogram, 1,            a(2),    a(2));
+        verifyHist(mDecodedProto.availableOpenBssidsInScanHistogram, 2,           a(3, 4), a(1, 1));
+        verifyHist(mDecodedProto.availableSavedSsidsInScanHistogram, 1,           a(3),    a(2));
+        verifyHist(mDecodedProto.availableSavedBssidsInScanHistogram, 2,          a(4, 5), a(1, 1));
+        verifyHist(mDecodedProto.availableOpenOrSavedSsidsInScanHistogram, 1,     a(4),    a(2));
+        verifyHist(mDecodedProto.availableOpenOrSavedBssidsInScanHistogram, 2,    a(5, 6), a(1, 1));
+        verifyHist(mDecodedProto.availableSavedPasspointProviderProfilesInScanHistogram, 1,
+                                                                                  a(2),    a(2));
+        verifyHist(mDecodedProto.availableSavedPasspointProviderBssidsInScanHistogram, 1,
+                                                                                  a(2),    a(2));
+        assertEquals(2, mDecodedProto.fullBandAllSingleScanListenerResults);
+        assertEquals(NUM_PARTIAL_SCAN_RESULTS, mDecodedProto.partialAllSingleScanListenerResults);
+
+        // Check Bounds
+        scan.clear();
+        int lotsOfSSids = Math.max(WifiMetrics.MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET,
+                WifiMetrics.MAX_CONNECTABLE_SSID_NETWORK_BUCKET) + 5;
+        for (int i = 0; i < lotsOfSSids; i++) {
+            scan.add(buildMockScanDetail("SSID_" + i, "bssid_" + i, true, true, false, false));
+        }
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        dumpProtoAndDeserialize();
+        verifyHist(mDecodedProto.totalSsidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableOpenSsidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_SSID_NETWORK_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableSavedSsidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_SSID_NETWORK_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableOpenOrSavedSsidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_SSID_NETWORK_BUCKET), a(1));
+        scan.clear();
+        int lotsOfBssids = Math.max(WifiMetrics.MAX_TOTAL_SCAN_RESULTS_BUCKET,
+                WifiMetrics.MAX_CONNECTABLE_BSSID_NETWORK_BUCKET) + 5;
+        for (int i = 0; i < lotsOfBssids; i++) {
+            scan.add(buildMockScanDetail("SSID", "bssid_" + i, true, true, false, false));
+        }
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        dumpProtoAndDeserialize();
+        verifyHist(mDecodedProto.totalBssidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_SCAN_RESULTS_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableOpenBssidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_BSSID_NETWORK_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableSavedBssidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_BSSID_NETWORK_BUCKET), a(1));
+        verifyHist(mDecodedProto.availableOpenOrSavedBssidsInScanHistogram, 1,
+                a(WifiMetrics.MAX_CONNECTABLE_BSSID_NETWORK_BUCKET), a(1));
+    }
+
+    /** short hand for instantiating an anonymous int array, instead of 'new int[]{a1, a2, ...}' */
+    private int[] a(int... element) {
+        return element;
+    }
+
+    private void verifyHist(WifiMetricsProto.NumConnectableNetworksBucket[] hist, int size,
+            int[] keys, int[] counts) throws Exception {
+        assertEquals(size, hist.length);
+        for (int i = 0; i < keys.length; i++) {
+            assertEquals(keys[i], hist[i].numConnectableNetworks);
+            assertEquals(counts[i], hist[i].count);
+        }
+    }
+
+    /**
      * Generate an RSSI delta event by creating a connection event and an RSSI poll within
      * 'interArrivalTime' milliseconds of each other.
      * Event will not be logged if interArrivalTime > mWifiMetrics.TIMEOUT_RSSI_DELTA_MILLIS
@@ -1115,6 +1227,7 @@
         }
         mWifiMetrics.incrementRssiPollRssiCount(scanRssi + rssiDelta);
     }
+
     /**
      * Generate an RSSI delta event, with all extra conditions set to true.
      */