Snap for 4657601 from 2bb42242ccfcf9f3c1a0e89b83fb90b1dd9212f7 to oc-m4-release

Change-Id: I17ea24f7256db3ec951748c1bca86c93403ae4d8
diff --git a/service/java/com/android/server/wifi/WifiMetrics.java b/service/java/com/android/server/wifi/WifiMetrics.java
index 071b4f8..a8ad08c 100644
--- a/service/java/com/android/server/wifi/WifiMetrics.java
+++ b/service/java/com/android/server/wifi/WifiMetrics.java
@@ -31,11 +31,14 @@
 import android.util.Pair;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wifi.aware.WifiAwareMetrics;
+import com.android.server.wifi.hotspot2.ANQPNetworkKey;
 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.hotspot2.Utils;
 import com.android.server.wifi.nano.WifiMetricsProto;
 import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
 import com.android.server.wifi.nano.WifiMetricsProto.PnoScanMetrics;
@@ -49,9 +52,11 @@
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Calendar;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -76,6 +81,8 @@
     public static final long TIMEOUT_RSSI_DELTA_MILLIS =  3000;
     private static final int MIN_WIFI_SCORE = 0;
     private static final int MAX_WIFI_SCORE = NetworkAgent.WIFI_BASE_SCORE;
+    @VisibleForTesting
+    static final int LOW_WIFI_SCORE = 50; // Mobile data score
     private final Object mLock = new Object();
     private static final int MAX_CONNECTION_EVENTS = 256;
     // Largest bucket in the NumConnectableNetworkCount histogram,
@@ -84,6 +91,9 @@
     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;
+    public static final int MAX_TOTAL_PASSPOINT_APS_BUCKET = 50;
+    public static final int MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET = 20;
+    public static final int MAX_PASSPOINT_APS_PER_UNIQUE_ESS_BUCKET = 50;
     private static final int CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER = 1000;
     private Clock mClock;
     private boolean mScreenOn;
@@ -161,6 +171,13 @@
     private int mNumOpenNetworkConnectMessageFailedToSend = 0;
     private int mNumOpenNetworkRecommendationUpdates = 0;
 
+    private final SparseIntArray mObservedHotspotR1ApInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2ApInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR1EssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2EssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR1ApsPerEssInScanHistogram = new SparseIntArray();
+    private final SparseIntArray mObservedHotspotR2ApsPerEssInScanHistogram = new SparseIntArray();
+
     class RouterFingerPrint {
         private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
         RouterFingerPrint() {
@@ -1041,10 +1058,14 @@
         }
     }
 
+    private boolean mWifiWins = false; // Based on scores, use wifi instead of mobile data?
+
     /**
      * Increments occurence of a particular wifi score calculated
      * in WifiScoreReport by current connected network. Scores are bounded
-     * within  [MIN_WIFI_SCORE, MAX_WIFI_SCORE] to limit size of SparseArray
+     * within  [MIN_WIFI_SCORE, MAX_WIFI_SCORE] to limit size of SparseArray.
+     *
+     * Also records events when the current score breaches significant thresholds.
      */
     public void incrementWifiScoreCount(int score) {
         if (score < MIN_WIFI_SCORE || score > MAX_WIFI_SCORE) {
@@ -1053,6 +1074,20 @@
         synchronized (mLock) {
             int count = mWifiScoreCounts.get(score);
             mWifiScoreCounts.put(score, count + 1);
+
+            boolean wifiWins = mWifiWins;
+            if (mWifiWins && score < LOW_WIFI_SCORE) {
+                wifiWins = false;
+            } else if (!mWifiWins && score > LOW_WIFI_SCORE) {
+                wifiWins = true;
+            }
+            mLastScore = score;
+            if (wifiWins != mWifiWins) {
+                mWifiWins = wifiWins;
+                StaEvent event = new StaEvent();
+                event.type = StaEvent.TYPE_SCORE_BREACH;
+                addStaEvent(event);
+            }
         }
     }
 
@@ -1192,6 +1227,10 @@
             Set<PasspointProvider> savedPasspointProviderProfiles =
                     new HashSet<PasspointProvider>();
             int savedPasspointProviderBssids = 0;
+            int passpointR1Aps = 0;
+            int passpointR2Aps = 0;
+            Map<ANQPNetworkKey, Integer> passpointR1UniqueEss = new HashMap<>();
+            Map<ANQPNetworkKey, Integer> passpointR2UniqueEss = new HashMap<>();
             for (ScanDetail scanDetail : scanDetails) {
                 NetworkDetail networkDetail = scanDetail.getNetworkDetail();
                 ScanResult scanResult = scanDetail.getScanResult();
@@ -1205,6 +1244,36 @@
                     providerMatch =
                             mPasspointManager.matchProvider(scanResult);
                     passpointProvider = providerMatch != null ? providerMatch.first : null;
+
+                    if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R1) {
+                        passpointR1Aps++;
+                    } else if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R2) {
+                        passpointR2Aps++;
+                    }
+
+                    long bssid = 0;
+                    boolean validBssid = false;
+                    try {
+                        bssid = Utils.parseMac(scanResult.BSSID);
+                        validBssid = true;
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG,
+                                "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
+                    }
+                    if (validBssid) {
+                        ANQPNetworkKey uniqueEss = ANQPNetworkKey.buildKey(scanResult.SSID, bssid,
+                                scanResult.hessid, networkDetail.getAnqpDomainID());
+                        if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R1) {
+                            Integer countObj = passpointR1UniqueEss.get(uniqueEss);
+                            int count = countObj == null ? 0 : countObj;
+                            passpointR1UniqueEss.put(uniqueEss, count + 1);
+                        } else if (networkDetail.getHSRelease() == NetworkDetail.HSRelease.R2) {
+                            Integer countObj = passpointR2UniqueEss.get(uniqueEss);
+                            int count = countObj == null ? 0 : countObj;
+                            passpointR2UniqueEss.put(uniqueEss, count + 1);
+                        }
+                    }
+
                 }
                 ssids.add(matchInfo);
                 bssids++;
@@ -1245,6 +1314,18 @@
                     savedPasspointProviderProfiles.size());
             incrementBssid(mAvailableSavedPasspointProviderBssidsInScanHistogram,
                     savedPasspointProviderBssids);
+            incrementTotalPasspointAps(mObservedHotspotR1ApInScanHistogram, passpointR1Aps);
+            incrementTotalPasspointAps(mObservedHotspotR2ApInScanHistogram, passpointR2Aps);
+            incrementTotalUniquePasspointEss(mObservedHotspotR1EssInScanHistogram,
+                    passpointR1UniqueEss.size());
+            incrementTotalUniquePasspointEss(mObservedHotspotR2EssInScanHistogram,
+                    passpointR2UniqueEss.size());
+            for (Integer count : passpointR1UniqueEss.values()) {
+                incrementPasspointPerUniqueEss(mObservedHotspotR1ApsPerEssInScanHistogram, count);
+            }
+            for (Integer count : passpointR2UniqueEss.values()) {
+                incrementPasspointPerUniqueEss(mObservedHotspotR2ApsPerEssInScanHistogram, count);
+            }
         }
     }
 
@@ -1561,6 +1642,19 @@
                         + mNumOpenNetworkRecommendationUpdates);
                 pw.println("mWifiLogProto.numOpenNetworkConnectMessageFailedToSend="
                         + mNumOpenNetworkConnectMessageFailedToSend);
+
+                pw.println("mWifiLogProto.observedHotspotR1ApInScanHistogram="
+                        + mObservedHotspotR1ApInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2ApInScanHistogram="
+                        + mObservedHotspotR2ApInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR1EssInScanHistogram="
+                        + mObservedHotspotR1EssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2EssInScanHistogram="
+                        + mObservedHotspotR2EssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR1ApsPerEssInScanHistogram="
+                        + mObservedHotspotR1ApsPerEssInScanHistogram);
+                pw.println("mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram="
+                        + mObservedHotspotR2ApsPerEssInScanHistogram);
             }
         }
     }
@@ -1818,6 +1912,21 @@
                     mNumOpenNetworkRecommendationUpdates;
             mWifiLogProto.numOpenNetworkConnectMessageFailedToSend =
                     mNumOpenNetworkConnectMessageFailedToSend;
+
+            mWifiLogProto.observedHotspotR1ApsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR1ApInScanHistogram);
+            mWifiLogProto.observedHotspotR2ApsInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR2ApInScanHistogram);
+            mWifiLogProto.observedHotspotR1EssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR1EssInScanHistogram);
+            mWifiLogProto.observedHotspotR2EssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(mObservedHotspotR2EssInScanHistogram);
+            mWifiLogProto.observedHotspotR1ApsPerEssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                            mObservedHotspotR1ApsPerEssInScanHistogram);
+            mWifiLogProto.observedHotspotR2ApsPerEssInScanHistogram =
+                    makeNumConnectableNetworksBucketArray(
+                            mObservedHotspotR2ApsPerEssInScanHistogram);
         }
     }
 
@@ -1872,6 +1981,12 @@
             mConnectToNetworkNotificationActionCount.clear();
             mNumOpenNetworkRecommendationUpdates = 0;
             mNumOpenNetworkConnectMessageFailedToSend = 0;
+            mObservedHotspotR1ApInScanHistogram.clear();
+            mObservedHotspotR2ApInScanHistogram.clear();
+            mObservedHotspotR1EssInScanHistogram.clear();
+            mObservedHotspotR2EssInScanHistogram.clear();
+            mObservedHotspotR1ApsPerEssInScanHistogram.clear();
+            mObservedHotspotR2ApsPerEssInScanHistogram.clear();
         }
     }
 
@@ -1890,6 +2005,7 @@
     public void setWifiState(int wifiState) {
         synchronized (mLock) {
             mWifiState = wifiState;
+            mWifiWins = (wifiState == WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
         }
     }
 
@@ -1995,6 +2111,7 @@
             case StaEvent.TYPE_CONNECT_NETWORK:
             case StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK:
             case StaEvent.TYPE_FRAMEWORK_DISCONNECT:
+            case StaEvent.TYPE_SCORE_BREACH:
                 break;
             default:
                 Log.e(TAG, "Unknown StaEvent:" + type);
@@ -2015,10 +2132,12 @@
         staEvent.lastFreq = mLastPollFreq;
         staEvent.lastLinkSpeed = mLastPollLinkSpeed;
         staEvent.supplicantStateChangesBitmask = mSupplicantStateChangeBitmask;
+        staEvent.lastScore = mLastScore;
         mSupplicantStateChangeBitmask = 0;
         mLastPollRssi = -127;
         mLastPollFreq = -1;
         mLastPollLinkSpeed = -1;
+        mLastScore = -1;
         mStaEventList.add(new StaEventWithTime(staEvent, mClock.getWallClockMillis()));
         // Prune StaEventList if it gets too long
         if (mStaEventList.size() > MAX_STA_EVENTS) mStaEventList.remove();
@@ -2174,6 +2293,9 @@
                         .append(" reason=")
                         .append(frameworkDisconnectReasonToString(event.frameworkDisconnectReason));
                 break;
+            case StaEvent.TYPE_SCORE_BREACH:
+                sb.append("SCORE_BREACH");
+                break;
             default:
                 sb.append("UNKNOWN " + event.type + ":");
                 break;
@@ -2181,6 +2303,7 @@
         if (event.lastRssi != -127) sb.append(" lastRssi=").append(event.lastRssi);
         if (event.lastFreq != -1) sb.append(" lastFreq=").append(event.lastFreq);
         if (event.lastLinkSpeed != -1) sb.append(" lastLinkSpeed=").append(event.lastLinkSpeed);
+        if (event.lastScore != -1) sb.append(" lastScore=").append(event.lastScore);
         if (event.supplicantStateChangesBitmask != 0) {
             sb.append(", ").append(supplicantStateChangesBitmaskToString(
                     event.supplicantStateChangesBitmask));
@@ -2243,11 +2366,12 @@
         return sb.toString();
     }
 
-    public static final int MAX_STA_EVENTS = 512;
+    public static final int MAX_STA_EVENTS = 768;
     private LinkedList<StaEventWithTime> mStaEventList = new LinkedList<StaEventWithTime>();
     private int mLastPollRssi = -127;
     private int mLastPollLinkSpeed = -1;
     private int mLastPollFreq = -1;
+    private int mLastScore = -1;
 
     /**
      * Converts the first 31 bits of a BitSet to a little endian int
@@ -2272,6 +2396,15 @@
     private void incrementTotalScanSsids(SparseIntArray sia, int element) {
         increment(sia, Math.min(element, MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET));
     }
+    private void incrementTotalPasspointAps(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_PASSPOINT_APS_BUCKET));
+    }
+    private void incrementTotalUniquePasspointEss(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET));
+    }
+    private void incrementPasspointPerUniqueEss(SparseIntArray sia, int element) {
+        increment(sia, Math.min(element, MAX_PASSPOINT_APS_PER_UNIQUE_ESS_BUCKET));
+    }
     private void increment(SparseIntArray sia, int element) {
         int count = sia.get(element);
         sia.put(element, count + 1);
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
index 65427be..c6a06e8 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiMetricsTest.java
@@ -18,7 +18,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.net.NetworkAgent;
 import android.net.wifi.ScanResult;
@@ -311,6 +313,23 @@
         return mockScanDetail;
     }
 
+    private ScanDetail buildMockScanDetailPasspoint(String ssid, String bssid, long hessid,
+            int anqpDomainId, NetworkDetail.HSRelease hsRelease) {
+        ScanDetail mockScanDetail = mock(ScanDetail.class);
+        NetworkDetail mockNetworkDetail = mock(NetworkDetail.class);
+        ScanResult scanResult = new ScanResult();
+        scanResult.SSID = ssid;
+        scanResult.BSSID = bssid;
+        scanResult.hessid = hessid;
+        scanResult.capabilities = "PSK";
+        when(mockScanDetail.getNetworkDetail()).thenReturn(mockNetworkDetail);
+        when(mockScanDetail.getScanResult()).thenReturn(scanResult);
+        when(mockNetworkDetail.getHSRelease()).thenReturn(hsRelease);
+        when(mockNetworkDetail.getAnqpDomainID()).thenReturn(anqpDomainId);
+        when(mockNetworkDetail.isInterworking()).thenReturn(true);
+        return mockScanDetail;
+    }
+
     private List<ScanDetail> buildMockScanDetailList() {
         List<ScanDetail> mockScanDetails = new ArrayList<ScanDetail>();
         mockScanDetails.add(buildMockScanDetail(true, null, "[ESS]"));
@@ -763,6 +782,31 @@
         // pending their implementation</TODO>
     }
 
+    /**
+     * Test that score breach events are properly generated
+     */
+    @Test
+    public void testScoreBeachEvents() throws Exception {
+        int upper = WifiMetrics.LOW_WIFI_SCORE + 7;
+        int mid = WifiMetrics.LOW_WIFI_SCORE;
+        int lower = WifiMetrics.LOW_WIFI_SCORE - 8;
+        mWifiMetrics.setWifiState(WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
+        for (int score = upper; score >= mid; score--) mWifiMetrics.incrementWifiScoreCount(score);
+        mWifiMetrics.incrementWifiScoreCount(mid + 1);
+        mWifiMetrics.incrementWifiScoreCount(lower); // First breach
+        for (int score = lower; score <= mid; score++) mWifiMetrics.incrementWifiScoreCount(score);
+        mWifiMetrics.incrementWifiScoreCount(mid - 1);
+        mWifiMetrics.incrementWifiScoreCount(upper); // Second breach
+
+        dumpProtoAndDeserialize();
+
+        assertEquals(2, mDecodedProto.staEventList.length);
+        assertEquals(StaEvent.TYPE_SCORE_BREACH, mDecodedProto.staEventList[0].type);
+        assertEquals(lower, mDecodedProto.staEventList[0].lastScore);
+        assertEquals(StaEvent.TYPE_SCORE_BREACH, mDecodedProto.staEventList[1].type);
+        assertEquals(upper, mDecodedProto.staEventList[1].lastScore);
+    }
+
     private static final String SSID = "red";
     private static final int CONFIG_DTIM = 3;
     private static final int NETWORK_DETAIL_WIFIMODE = 5;
@@ -1041,7 +1085,7 @@
     private static final int ASSOC_TIMEOUT = 1;
     private static final int LOCAL_GEN = 1;
     private static final int AUTH_FAILURE_REASON = WifiManager.ERROR_AUTH_FAILURE_WRONG_PSWD;
-    private static final int NUM_TEST_STA_EVENTS = 14;
+    private static final int NUM_TEST_STA_EVENTS = 15;
     private static final String   sSSID = "\"SomeTestSsid\"";
     private static final WifiSsid sWifiSsid = WifiSsid.createFromAsciiEncoded(sSSID);
     private static final String   sBSSID = "01:02:03:04:05:06";
@@ -1089,7 +1133,8 @@
         {StaEvent.TYPE_CMD_START_ROAM,                  0,                          1},
         {StaEvent.TYPE_CONNECT_NETWORK,                 0,                          1},
         {StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK,     0,                          0},
-        {StaEvent.TYPE_FRAMEWORK_DISCONNECT,            StaEvent.DISCONNECT_API,    0}
+        {StaEvent.TYPE_FRAMEWORK_DISCONNECT,            StaEvent.DISCONNECT_API,    0},
+        {StaEvent.TYPE_SCORE_BREACH,                    0,                          0}
     };
     // Values used to generate the StaEvent log calls from WifiMonitor
     // <type>, <reason>, <status>, <local_gen>,
@@ -1122,6 +1167,8 @@
         {StaEvent.TYPE_NETWORK_AGENT_VALID_NETWORK,     -1,            -1,         0,
             /**/                               0,             0,        0, 0},    /**/
         {StaEvent.TYPE_FRAMEWORK_DISCONNECT,            -1,            -1,         0,
+            /**/                               0,             0,        0, 0},    /**/
+        {StaEvent.TYPE_SCORE_BREACH,                    -1,            -1,         0,
             /**/                               0,             0,        0, 0}     /**/
     };
 
@@ -1142,6 +1189,7 @@
         }
     }
     private void verifyDeserializedStaEvents(WifiMetricsProto.WifiLog wifiLog) {
+        assertNotNull(mTestWifiConfig);
         assertEquals(NUM_TEST_STA_EVENTS, wifiLog.staEventList.length);
         int j = 0; // De-serialized event index
         for (int i = 0; i < mTestStaMessageInts.length; i++) {
@@ -1161,6 +1209,21 @@
                 j++;
             }
         }
+        for (int i = 0; i < mTestStaLogInts.length; i++) {
+            StaEvent event = wifiLog.staEventList[j];
+            int[] evs = mExpectedValues[j];
+            assertEquals(evs[0], event.type);
+            assertEquals(evs[1], event.reason);
+            assertEquals(evs[2], event.status);
+            assertEquals(evs[3] == 1 ? true : false, event.localGen);
+            assertEquals(evs[4], event.authFailureReason);
+            assertEquals(evs[5] == 1 ? true : false, event.associationTimedOut);
+            assertEquals(evs[6], event.supplicantStateChangesBitmask);
+            assertConfigInfoEqualsWifiConfig(
+                    evs[7] == 1 ? mTestWifiConfig : null, event.configInfo);
+            j++;
+        }
+        assertEquals(mExpectedValues.length, j);
     }
 
     /**
@@ -1271,9 +1334,77 @@
     }
 
     /**
+     * Test that Hotspot 2.0 (Passpoint) scan results are collected correctly and that relevant
+     * bounds are observed.
+     */
+    @Test
+    public void testObservedHotspotAps() throws Exception {
+        List<ScanDetail> scan = new ArrayList<ScanDetail>();
+        // 2 R1 (Unknown AP isn't counted) passpoint APs belonging to a single provider: hessid1
+        long hessid1 = 10;
+        int anqpDomainId1 = 5;
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_XX", "00:02:03:04:05:06", hessid1,
+                anqpDomainId1, NetworkDetail.HSRelease.R1));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_XY", "01:02:03:04:05:06", hessid1,
+                anqpDomainId1, NetworkDetail.HSRelease.R1));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_XYZ", "02:02:03:04:05:06", hessid1,
+                anqpDomainId1, NetworkDetail.HSRelease.Unknown));
+        // 2 R2 passpoint APs belonging to a single provider: hessid2
+        long hessid2 = 12;
+        int anqpDomainId2 = 6;
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Y", "AA:02:03:04:05:06", hessid2,
+                anqpDomainId2, NetworkDetail.HSRelease.R2));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Z", "AB:02:03:04:05:06", hessid2,
+                anqpDomainId2, NetworkDetail.HSRelease.R2));
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        scan = new ArrayList<ScanDetail>();
+        // 3 R2 passpoint APs belonging to a single provider: hessid3 (in next scan)
+        long hessid3 = 15;
+        int anqpDomainId3 = 8;
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Y", "AA:02:03:04:05:06", hessid3,
+                anqpDomainId3, NetworkDetail.HSRelease.R2));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Y", "AA:02:03:04:05:06", hessid3,
+                anqpDomainId3, NetworkDetail.HSRelease.R2));
+        scan.add(buildMockScanDetailPasspoint("PASSPOINT_Z", "AB:02:03:04:05:06", hessid3,
+                anqpDomainId3, NetworkDetail.HSRelease.R2));
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        dumpProtoAndDeserialize();
+
+        verifyHist(mDecodedProto.observedHotspotR1ApsInScanHistogram, 2, a(0, 2), a(1, 1));
+        verifyHist(mDecodedProto.observedHotspotR2ApsInScanHistogram, 2, a(2, 3), a(1, 1));
+        verifyHist(mDecodedProto.observedHotspotR1EssInScanHistogram, 2, a(0, 1), a(1, 1));
+        verifyHist(mDecodedProto.observedHotspotR2EssInScanHistogram, 1, a(1), a(2));
+        verifyHist(mDecodedProto.observedHotspotR1ApsPerEssInScanHistogram, 1, a(2), a(1));
+        verifyHist(mDecodedProto.observedHotspotR2ApsPerEssInScanHistogram, 2, a(2, 3), a(1, 1));
+
+        // check bounds
+        scan.clear();
+        int lotsOfSSids = Math.max(WifiMetrics.MAX_TOTAL_PASSPOINT_APS_BUCKET,
+                WifiMetrics.MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET) + 5;
+        for (int i = 0; i < lotsOfSSids; i++) {
+            scan.add(buildMockScanDetailPasspoint("PASSPOINT_XX" + i, "00:02:03:04:05:06", i,
+                    i + 10, NetworkDetail.HSRelease.R1));
+            scan.add(buildMockScanDetailPasspoint("PASSPOINT_XY" + i, "AA:02:03:04:05:06", 1000 * i,
+                    i + 10, NetworkDetail.HSRelease.R2));
+        }
+        mWifiMetrics.incrementAvailableNetworksHistograms(scan, true);
+        dumpProtoAndDeserialize();
+        verifyHist(mDecodedProto.observedHotspotR1ApsInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_APS_BUCKET), a(1));
+        verifyHist(mDecodedProto.observedHotspotR2ApsInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_APS_BUCKET), a(1));
+        verifyHist(mDecodedProto.observedHotspotR1EssInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET), a(1));
+        verifyHist(mDecodedProto.observedHotspotR2EssInScanHistogram, 1,
+                a(WifiMetrics.MAX_TOTAL_PASSPOINT_UNIQUE_ESS_BUCKET), a(1));
+
+    }
+
+    /**
      * Test Open Network Notification blacklist size and feature state are not cleared when proto
      * is dumped.
      */
+    @Test
     public void testOpenNetworkNotificationBlacklistSizeAndFeatureStateNotCleared()
             throws Exception {
         mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(