release-request-108343c4-ad88-44f9-aaa2-24d8b8a5c176-for-git_oc-mr1-release-4321077 snap-temp-L97000000100182150

Change-Id: I6389ef4349976122eafc5615b81a3ea022da7c62
diff --git a/service/java/com/android/server/wifi/OpenNetworkNotifier.java b/service/java/com/android/server/wifi/OpenNetworkNotifier.java
index fc144c1..692c8e2 100644
--- a/service/java/com/android/server/wifi/OpenNetworkNotifier.java
+++ b/service/java/com/android/server/wifi/OpenNetworkNotifier.java
@@ -29,6 +29,8 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -36,18 +38,25 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Takes care of handling the "open wi-fi network available" notification
+ *
+ * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread.
  * @hide
  */
 public class OpenNetworkNotifier {
 
+    private static final String TAG = "OpenNetworkNotifier";
+
     static final String ACTION_USER_DISMISSED_NOTIFICATION =
             "com.android.server.wifi.OpenNetworkNotifier.USER_DISMISSED_NOTIFICATION";
     static final String ACTION_USER_TAPPED_CONTENT =
             "com.android.server.wifi.OpenNetworkNotifier.USER_TAPPED_CONTENT";
 
+    /** Identifier of the {@link SsidSetStoreData}. */
+    private static final String STORE_DATA_IDENTIFIER = "OpenNetworkNotifierBlacklist";
     /**
      * The {@link Clock#getWallClockMillis()} must be at least this value for us
      * to show the notification again.
@@ -68,10 +77,14 @@
     /** Whether the screen is on or not. */
     private boolean mScreenOn;
 
+    /** List of SSIDs blacklisted from recommendation. */
+    private final Set<String> mBlacklistedSsids;
+
     private final Context mContext;
     private final Handler mHandler;
     private final FrameworkFacade mFrameworkFacade;
     private final Clock mClock;
+    private final WifiConfigManager mConfigManager;
     private final OpenNetworkRecommender mOpenNetworkRecommender;
     private final OpenNetworkNotificationBuilder mOpenNetworkNotificationBuilder;
 
@@ -82,15 +95,22 @@
             Looper looper,
             FrameworkFacade framework,
             Clock clock,
+            WifiConfigManager wifiConfigManager,
+            WifiConfigStore wifiConfigStore,
             OpenNetworkRecommender openNetworkRecommender) {
         mContext = context;
         mHandler = new Handler(looper);
         mFrameworkFacade = framework;
         mClock = clock;
+        mConfigManager = wifiConfigManager;
         mOpenNetworkRecommender = openNetworkRecommender;
         mOpenNetworkNotificationBuilder = new OpenNetworkNotificationBuilder(context, framework);
         mScreenOn = false;
 
+        mBlacklistedSsids = new ArraySet<>();
+        wifiConfigStore.registerStoreData(new SsidSetStoreData(
+                STORE_DATA_IDENTIFIER, new OpenNetworkNotifierStoreData()));
+
         // Setting is in seconds
         mNotificationRepeatDelay = mFrameworkFacade.getIntegerSetting(context,
                 Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
@@ -165,7 +185,7 @@
         }
 
         mRecommendedNetwork = mOpenNetworkRecommender.recommendNetwork(
-                availableNetworks, mRecommendedNetwork);
+                availableNetworks, new ArraySet<>(mBlacklistedSsids));
 
         postNotification(availableNetworks.size());
     }
@@ -201,8 +221,14 @@
                         .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
     }
 
-    /** A delay is set before the next shown notification after user dismissal. */
     private void handleUserDismissedAction() {
+        if (mRecommendedNetwork != null) {
+            // blacklist dismissed network
+            mBlacklistedSsids.add(mRecommendedNetwork.SSID);
+            mConfigManager.saveToStore(false /* forceWrite */);
+            Log.d(TAG, "Network is added to the open network notification blacklist: "
+                    + mRecommendedNetwork.SSID);
+        }
         mNotificationShown = false;
     }
 
@@ -213,6 +239,19 @@
         pw.println("currentTime: " + mClock.getWallClockMillis());
         pw.println("mNotificationRepeatTime: " + mNotificationRepeatTime);
         pw.println("mNotificationShown: " + mNotificationShown);
+        pw.println("mBlacklistedSsids: " + mBlacklistedSsids.toString());
+    }
+
+    private class OpenNetworkNotifierStoreData implements SsidSetStoreData.DataSource {
+        @Override
+        public Set<String> getSsids() {
+            return new ArraySet<>(mBlacklistedSsids);
+        }
+
+        @Override
+        public void setSsids(Set<String> ssidList) {
+            mBlacklistedSsids.addAll(ssidList);
+        }
     }
 
     private class NotificationEnabledSettingObserver extends ContentObserver {
diff --git a/service/java/com/android/server/wifi/OpenNetworkRecommender.java b/service/java/com/android/server/wifi/OpenNetworkRecommender.java
index cd460e5..5ceeddd 100644
--- a/service/java/com/android/server/wifi/OpenNetworkRecommender.java
+++ b/service/java/com/android/server/wifi/OpenNetworkRecommender.java
@@ -20,9 +20,12 @@
 import android.net.wifi.ScanResult;
 
 import java.util.List;
+import java.util.Set;
 
 /**
  * Helps recommend the best available network for {@link OpenNetworkNotifier}.
+ *
+ * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread.
  * @hide
  */
 public class OpenNetworkRecommender {
@@ -32,31 +35,24 @@
      *
      * @param networks List of scan details to pick a recommendation. This list should not be null
      *                 or empty.
-     * @param currentRecommendation The currently recommended network.
+     * @param blacklistedSsids The list of SSIDs that should not be recommended.
      */
-    public ScanResult recommendNetwork(
-            @NonNull List<ScanDetail> networks, ScanResult currentRecommendation) {
-        ScanResult currentUpdatedRecommendation = null;
+    public ScanResult recommendNetwork(@NonNull List<ScanDetail> networks,
+                                       @NonNull Set<String> blacklistedSsids) {
         ScanResult result = null;
         int highestRssi = Integer.MIN_VALUE;
         for (ScanDetail scanDetail : networks) {
             ScanResult scanResult = scanDetail.getScanResult();
 
-            if (currentRecommendation != null
-                    && currentRecommendation.SSID.equals(scanResult.SSID)) {
-                currentUpdatedRecommendation = scanResult;
-            }
-
             if (scanResult.level > highestRssi) {
                 result = scanResult;
                 highestRssi = scanResult.level;
             }
         }
-        if (currentUpdatedRecommendation != null
-                && currentUpdatedRecommendation.level >= result.level) {
-            return currentUpdatedRecommendation;
-        } else {
-            return result;
+
+        if (result != null && blacklistedSsids.contains(result.SSID)) {
+            result = null;
         }
+        return result;
     }
 }
diff --git a/service/java/com/android/server/wifi/SsidSetStoreData.java b/service/java/com/android/server/wifi/SsidSetStoreData.java
new file mode 100644
index 0000000..daed26a
--- /dev/null
+++ b/service/java/com/android/server/wifi/SsidSetStoreData.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.text.TextUtils;
+
+import com.android.server.wifi.util.XmlUtil;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Store data for network notifiers.
+ *
+ * Below are the current configuration data for each respective store file:
+ *
+ * Share Store (system wide configurations)
+ * - No data
+ *
+ * User Store (user specific configurations)
+ * - Set of blacklisted SSIDs
+ */
+public class SsidSetStoreData implements WifiConfigStore.StoreData {
+    private static final String XML_TAG_SECTION_HEADER_SUFFIX = "ConfigData";
+    private static final String XML_TAG_SSID_SET = "SSIDSet";
+
+    private final String mTagName;
+    private final DataSource mDataSource;
+
+    /**
+     * Interface define the data source for the notifier store data.
+     */
+    public interface DataSource {
+        /**
+         * Retrieve the SSID set from the data source.
+         *
+         * @return Set of SSIDs
+         */
+        Set<String> getSsids();
+
+        /**
+         * Update the SSID set in the data source.
+         *
+         * @param ssidSet The set of SSIDs
+         */
+        void setSsids(Set<String> ssidSet);
+    }
+
+    /**
+     * Creates the SSID Set store data.
+     *
+     * @param name Identifier of the SSID set.
+     * @param dataSource The DataSource that implements the update and retrieval of the SSID set.
+     */
+    SsidSetStoreData(String name, DataSource dataSource) {
+        mTagName = name + XML_TAG_SECTION_HEADER_SUFFIX;
+        mDataSource = dataSource;
+    }
+
+    @Override
+    public void serializeData(XmlSerializer out, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            throw new XmlPullParserException("Share data not supported");
+        }
+        Set<String> ssidSet = mDataSource.getSsids();
+        if (ssidSet != null && !ssidSet.isEmpty()) {
+            XmlUtil.writeNextValue(out, XML_TAG_SSID_SET, mDataSource.getSsids());
+        }
+    }
+
+    @Override
+    public void deserializeData(XmlPullParser in, int outerTagDepth, boolean shared)
+            throws XmlPullParserException, IOException {
+        if (shared) {
+            throw new XmlPullParserException("Share data not supported");
+        }
+
+        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
+            String[] valueName = new String[1];
+            Object value = XmlUtil.readCurrentValue(in, valueName);
+            if (TextUtils.isEmpty(valueName[0])) {
+                throw new XmlPullParserException("Missing value name");
+            }
+            switch (valueName[0]) {
+                case XML_TAG_SSID_SET:
+                    mDataSource.setSsids((Set<String>) value);
+                    break;
+                default:
+                    throw new XmlPullParserException("Unknown tag under "
+                            + mTagName + ": " + valueName[0]);
+            }
+        }
+    }
+
+    @Override
+    public void resetData(boolean shared) {
+        if (!shared) {
+            mDataSource.setSsids(new HashSet<>());
+        }
+    }
+
+    @Override
+    public String getName() {
+        return mTagName;
+    }
+
+    @Override
+    public boolean supportShareData() {
+        return false;
+    }
+}
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 60c5d2f..26d7024 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -233,7 +233,7 @@
         mCertManager = new WifiCertManager(mContext);
         mOpenNetworkNotifier = new OpenNetworkNotifier(mContext,
                 mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mClock,
-                new OpenNetworkRecommender());
+                mWifiConfigManager, mWifiConfigStore, new OpenNetworkRecommender());
         mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
         mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
                 mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index b950cf2..1d26f04 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -1913,9 +1913,13 @@
 
     public List<WifiConfiguration> syncGetConfiguredNetworks(int uuid, AsyncChannel channel) {
         Message resultMsg = channel.sendMessageSynchronously(CMD_GET_CONFIGURED_NETWORKS, uuid);
-        List<WifiConfiguration> result = (List<WifiConfiguration>) resultMsg.obj;
-        resultMsg.recycle();
-        return result;
+        if (resultMsg == null) { // an error has occurred
+            return null;
+        } else {
+            List<WifiConfiguration> result = (List<WifiConfiguration>) resultMsg.obj;
+            resultMsg.recycle();
+            return result;
+        }
     }
 
     public List<WifiConfiguration> syncGetPrivilegedConfiguredNetwork(AsyncChannel channel) {
diff --git a/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java b/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
index 29c068d..957fc22 100644
--- a/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
@@ -37,6 +37,7 @@
 import android.os.UserManager;
 import android.os.test.TestLooper;
 import android.provider.Settings;
+import android.util.ArraySet;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -47,6 +48,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Unit tests for {@link OpenNetworkNotifier}.
@@ -60,6 +62,8 @@
     @Mock private Resources mResources;
     @Mock private FrameworkFacade mFrameworkFacade;
     @Mock private Clock mClock;
+    @Mock private WifiConfigStore mWifiConfigStore;
+    @Mock private WifiConfigManager mWifiConfigManager;
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Notification.Builder mNotificationBuilder;
     @Mock private NotificationManager mNotificationManager;
     @Mock private OpenNetworkRecommender mOpenNetworkRecommender;
@@ -67,6 +71,8 @@
     private OpenNetworkNotifier mNotificationController;
     private BroadcastReceiver mBroadcastReceiver;
     private ScanResult mDummyNetwork;
+    private List<ScanDetail> mOpenNetworks;
+    private Set<String> mBlacklistedSsids;
 
 
     /** Initialize objects before each test run. */
@@ -90,11 +96,14 @@
         mDummyNetwork.capabilities = "[ESS]";
         mDummyNetwork.level = MIN_RSSI_LEVEL;
         when(mOpenNetworkRecommender.recommendNetwork(any(), any())).thenReturn(mDummyNetwork);
+        mOpenNetworks = new ArrayList<>();
+        mOpenNetworks.add(new ScanDetail(mDummyNetwork, null /* networkDetail */));
+        mBlacklistedSsids = new ArraySet<>();
 
         TestLooper mock_looper = new TestLooper();
         mNotificationController = new OpenNetworkNotifier(
-                mContext, mock_looper.getLooper(), mFrameworkFacade,
-                mClock, mOpenNetworkRecommender);
+                mContext, mock_looper.getLooper(), mFrameworkFacade, mClock, mWifiConfigManager,
+                mWifiConfigStore, mOpenNetworkRecommender);
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
@@ -102,20 +111,14 @@
         mNotificationController.handleScreenStateChanged(true);
     }
 
-    private List<ScanDetail> createOpenScanResults() {
-        List<ScanDetail> scanResults = new ArrayList<>();
-        scanResults.add(new ScanDetail(mDummyNetwork, null /* networkDetail */));
-        return scanResults;
-    }
-
     /**
      * When scan results with open networks are handled, a notification is posted.
      */
     @Test
     public void handleScanResults_hasOpenNetworks_notificationDisplayed() {
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
     }
 
@@ -136,9 +139,9 @@
      */
     @Test
     public void handleScanResults_notificationShown_emptyList_notificationCleared() {
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.handleScanResults(new ArrayList<>());
@@ -151,9 +154,9 @@
      */
     @Test
     public void handleScanResults_notificationShown_screenOff_emptyList_notificationCleared() {
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.handleScreenStateChanged(false);
@@ -167,10 +170,10 @@
      */
     @Test
     public void handleScanResults_notificationShowing_doesNotRepostNotification() {
-        mNotificationController.handleScanResults(createOpenScanResults());
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
     }
 
@@ -180,9 +183,9 @@
      */
     @Test
     public void clearPendingNotification_clearsNotificationIfOneIsShowing() {
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(true);
@@ -208,7 +211,7 @@
     @Test
     public void screenOff_handleScanResults_notificationNotDisplayed() {
         mNotificationController.handleScreenStateChanged(false);
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender, never()).recommendNetwork(any(), any());
         verify(mNotificationManager, never()).notify(anyInt(), any());
@@ -220,17 +223,18 @@
      */
     @Test
     public void postNotification_clearNotificationWithoutDelayReset_shouldNotPostNotification() {
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(false);
 
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
         // Recommendation made twice but no new notification posted.
-        verify(mOpenNetworkRecommender, times(2)).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender, times(2)).recommendNetwork(
+                mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
         verify(mNotificationManager).cancel(anyInt());
     }
@@ -241,16 +245,17 @@
      */
     @Test
     public void postNotification_clearNotificationWithDelayReset_shouldPostNotification() {
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(true);
 
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender, times(2)).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender, times(2)).recommendNetwork(
+                mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager, times(2)).notify(anyInt(), any());
     }
 
@@ -259,9 +264,9 @@
      */
     @Test
     public void notificationTap_opensWifiSettings() {
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(
@@ -271,14 +276,37 @@
     }
 
     /**
+     * When user dismissed notification and there is a recommended network, network ssid should be
+     * blacklisted.
+     */
+    @Test
+    public void userDismissedNotification_shouldBlacklistNetwork() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(
+                mContext, new Intent(OpenNetworkNotifier.ACTION_USER_DISMISSED_NOTIFICATION));
+
+        verify(mWifiConfigManager).saveToStore(false /* forceWrite */);
+
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        Set<String> expectedBlacklist = new ArraySet<>();
+        expectedBlacklist.add(mDummyNetwork.SSID);
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, expectedBlacklist);
+    }
+
+    /**
      * When a notification is posted and cleared without reseting delay, after the delay has passed
      * the next scan with open networks should post a notification.
      */
     @Test
     public void delaySet_delayPassed_shouldPostNotification() {
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(false);
@@ -286,9 +314,10 @@
         // twice the delay time passed
         when(mClock.getWallClockMillis()).thenReturn(DEFAULT_REPEAT_DELAY_SEC * 1000L * 2);
 
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender, times(2)).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender, times(2)).recommendNetwork(
+                mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager, times(2)).notify(anyInt(), any());
     }
 
@@ -298,7 +327,7 @@
         when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT))
                 .thenReturn(true);
 
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender, never()).recommendNetwork(any(), any());
         verify(mNotificationManager, never()).notify(anyInt(), any());
@@ -307,15 +336,15 @@
     /** Verifies that {@link UserManager#DISALLOW_CONFIG_WIFI} clears the showing notification. */
     @Test
     public void userHasDisallowConfigWifiRestriction_showingNotificationIsCleared() {
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
-        verify(mOpenNetworkRecommender).recommendNetwork(any(), any());
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
 
         when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT))
                 .thenReturn(true);
 
-        mNotificationController.handleScanResults(createOpenScanResults());
+        mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mNotificationManager).cancel(anyInt());
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/OpenNetworkRecommenderTest.java b/tests/wifitests/src/com/android/server/wifi/OpenNetworkRecommenderTest.java
index becc1d2..720ec37 100644
--- a/tests/wifitests/src/com/android/server/wifi/OpenNetworkRecommenderTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/OpenNetworkRecommenderTest.java
@@ -17,14 +17,18 @@
 package com.android.server.wifi;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import android.net.wifi.ScanResult;
+import android.util.ArraySet;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Tests for {@link OpenNetworkRecommender}.
@@ -36,10 +40,13 @@
     private static final int MIN_RSSI_LEVEL = -127;
 
     private OpenNetworkRecommender mOpenNetworkRecommender;
+    private Set<String> mBlacklistedSsids;
 
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         mOpenNetworkRecommender = new OpenNetworkRecommender();
+        mBlacklistedSsids = new ArraySet<>();
     }
 
     private List<ScanDetail> createOpenScanResults(String... ssids) {
@@ -59,7 +66,8 @@
         List<ScanDetail> scanResults = createOpenScanResults(TEST_SSID_1);
         scanResults.get(0).getScanResult().level = MIN_RSSI_LEVEL;
 
-        ScanResult actual = mOpenNetworkRecommender.recommendNetwork(scanResults, null);
+        ScanResult actual = mOpenNetworkRecommender.recommendNetwork(
+                scanResults, mBlacklistedSsids);
         ScanResult expected = scanResults.get(0).getScanResult();
         assertEquals(expected, actual);
     }
@@ -71,28 +79,24 @@
         scanResults.get(0).getScanResult().level = MIN_RSSI_LEVEL;
         scanResults.get(1).getScanResult().level = MIN_RSSI_LEVEL + 1;
 
-        ScanResult actual = mOpenNetworkRecommender.recommendNetwork(scanResults, null);
+        ScanResult actual = mOpenNetworkRecommender.recommendNetwork(
+                scanResults, mBlacklistedSsids);
         ScanResult expected = scanResults.get(1).getScanResult();
         assertEquals(expected, actual);
     }
 
     /**
-     * If the current recommended network is present in the list for the next recommendation and has
-     * an equal RSSI, the recommendation should not change.
+     * If the best available open network is blacklisted, no networks should be recommended.
      */
     @Test
-    public void currentRecommendationHasEquallyHighRssi_shouldNotChangeRecommendation() {
+    public void blacklistBestNetworkSsid_shouldNeverRecommendNetwork() {
         List<ScanDetail> scanResults = createOpenScanResults(TEST_SSID_1, TEST_SSID_2);
         scanResults.get(0).getScanResult().level = MIN_RSSI_LEVEL + 1;
-        scanResults.get(1).getScanResult().level = MIN_RSSI_LEVEL + 1;
+        scanResults.get(1).getScanResult().level = MIN_RSSI_LEVEL;
+        mBlacklistedSsids.add(TEST_SSID_1);
 
-        ScanResult currentRecommendation = new ScanResult(scanResults.get(1).getScanResult());
-        // next recommendation does not depend on the rssi of the input recommendation.
-        currentRecommendation.level = MIN_RSSI_LEVEL;
-
-        ScanResult expected = scanResults.get(1).getScanResult();
         ScanResult actual = mOpenNetworkRecommender.recommendNetwork(
-                scanResults, currentRecommendation);
-        assertEquals(expected, actual);
+                scanResults, mBlacklistedSsids);
+        assertNull(actual);
     }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java b/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java
new file mode 100644
index 0000000..606b825
--- /dev/null
+++ b/tests/wifitests/src/com/android/server/wifi/SsidSetStoreDataTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.SsidSetStoreData}.
+ */
+public class SsidSetStoreDataTest {
+    private static final String TEST_NOTIFIER_NAME = "TestNetwork";
+    private static final String TEST_SSID1 = "SSID 1";
+    private static final String TEST_SSID2 = "SSID 2";
+    private static final String TEST_SSID_SET_XML_STRING =
+            "<set name=\"SSIDSet\">\n"
+                    + "<string>" + TEST_SSID1 + "</string>\n"
+                    + "<string>" + TEST_SSID2 + "</string>\n"
+                    + "</set>\n";
+    private static final byte[] TEST_SSID_SET_XML_BYTES =
+            TEST_SSID_SET_XML_STRING.getBytes(StandardCharsets.UTF_8);
+
+    @Mock SsidSetStoreData.DataSource mDataSource;
+    SsidSetStoreData mSsidSetStoreData;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mSsidSetStoreData = new SsidSetStoreData(TEST_NOTIFIER_NAME, mDataSource);
+    }
+
+    /**
+     * Helper function for serializing configuration data to a XML block.
+     *
+     * @param shared Flag indicating serializing shared or user configurations
+     * @return byte[] of the XML data
+     * @throws Exception
+     */
+    private byte[] serializeData(boolean shared) throws Exception {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        mSsidSetStoreData.serializeData(out, shared);
+        out.flush();
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Helper function for parsing configuration data from a XML block.
+     *
+     * @param data XML data to parse from
+     * @param shared Flag indicating parsing of shared or user configurations
+     * @throws Exception
+     */
+    private void deserializeData(byte[] data, boolean shared) throws Exception {
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        mSsidSetStoreData.deserializeData(in, in.getDepth(), shared);
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when attempting to serialize data
+     * to the share store.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void serializeShareData() throws Exception {
+        serializeData(true /* shared */);
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when attempting to deserialize
+     * data from the share store.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void deserializeShareData() throws Exception {
+        deserializeData(new byte[0], true /* shared */);
+    }
+
+    /**
+     * Verify that serializing the user store data without any configuration doesn't cause any
+     * crash and no data should be serialized.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeEmptyConfigs() throws Exception {
+        when(mDataSource.getSsids()).thenReturn(new HashSet<String>());
+        assertEquals(0, serializeData(false /* shared */).length);
+    }
+
+    /**
+     * Verify that parsing an empty data doesn't cause any crash and no configuration should
+     * be deserialized.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void deserializeEmptyStoreData() throws Exception {
+        deserializeData(new byte[0], false /* shared */);
+        verify(mDataSource, never()).setSsids(any(Set.class));
+    }
+
+    /**
+     * Verify that {@link SsidSetStoreData} does not support share data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void supportShareData() throws Exception {
+        assertFalse(mSsidSetStoreData.supportShareData());
+    }
+
+    /**
+     * Verify that the store data is serialized correctly, matches the predefined test XML data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void serializeSsidSet() throws Exception {
+        Set<String> ssidSet = new HashSet<>();
+        ssidSet.add(TEST_SSID1);
+        ssidSet.add(TEST_SSID2);
+        when(mDataSource.getSsids()).thenReturn(ssidSet);
+        byte[] actualData = serializeData(false /* shared */);
+        assertTrue(Arrays.equals(TEST_SSID_SET_XML_BYTES, actualData));
+    }
+
+    /**
+     * Verify that the store data is deserialized correctly using the predefined test XML data.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void deserializeSsidSet() throws Exception {
+        Set<String> ssidSet = new HashSet<>();
+        ssidSet.add(TEST_SSID1);
+        ssidSet.add(TEST_SSID2);
+        deserializeData(TEST_SSID_SET_XML_BYTES, false /* shared */);
+        verify(mDataSource).setSsids(eq(ssidSet));
+    }
+
+    /**
+     * Verify that a XmlPullParserException will be thrown when parsing a SSIDSet set with an
+     * unknown tag.
+     *
+     * @throws Exception
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void parseSetWithUnknownTag() throws Exception {
+        String ssidSet =
+                "<set name=\"SSIDSet\">\n"
+                        + "<string>" + TEST_SSID1 + "</string>\n"
+                        + "<string>" + TEST_SSID2 + "</string>\n"
+                        + "<Unknown>" + "badInput" + "</Unknown>" // Unknown tag.
+                        + "</set>\n";
+        deserializeData(ssidSet.getBytes(StandardCharsets.UTF_8), false /* shared */);
+    }
+}