WifiNetworkSuggestionStoreData: New Module to persist suggestion

The new module serializes/deserializes network suggestions from store
files.

Bug: 115504887
Test: ./frameworks/opt/net/wifi/tests/wifitests/runtests.sh
Change-Id: I5655f56bf592a326d91e6c5e69add437e5467e52
diff --git a/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java b/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java
new file mode 100644
index 0000000..76f8910
--- /dev/null
+++ b/service/java/com/android/server/wifi/NetworkSuggestionStoreData.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2018 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.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiNetworkSuggestion;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.util.XmlUtils;
+import com.android.server.wifi.util.XmlUtil;
+import com.android.server.wifi.util.XmlUtil.WifiConfigurationXmlUtil;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * This class performs serialization and parsing of XML data block that contain the list of WiFi
+ * network suggestions.
+ */
+public class NetworkSuggestionStoreData implements WifiConfigStore.StoreData {
+    private static final String TAG = "NetworkSuggestionStoreData";
+
+    private static final String XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION_MAP =
+            "NetworkSuggestionMap";
+    private static final String XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION_PER_APP =
+            "NetworkSuggestionPerApp";
+    private static final String XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION = "NetworkSuggestion";
+    private static final String XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION = "WifiConfiguration";
+    private static final String XML_TAG_IS_APP_INTERACTION_REQUIRED = "IsAppInteractionRequired";
+    private static final String XML_TAG_IS_USER_INTERACTION_REQUIRED = "IsUserInteractionRequired";
+    private static final String XML_TAG_SUGGESTOR_UID = "SuggestorUid";
+    private static final String XML_TAG_SUGGESTOR_PACKAGE_NAME = "SuggestorPackageName";
+
+    /**
+     * Interface define the data source for the network suggestions store data.
+     */
+    public interface DataSource {
+        /**
+         * Retrieve the network suggestion list from the data source.
+         *
+         * @return Map of package name to set of {@link WifiNetworkSuggestion}
+         */
+        Map<String, Set<WifiNetworkSuggestion>> getNetworkSuggestions();
+
+        /**
+         * Set the network suggestions list in the data source.
+         *
+         * @param networkSuggestions Map of package name to set of {@link WifiNetworkSuggestion}
+         */
+        void setNetworkSuggestions(Map<String, Set<WifiNetworkSuggestion>> networkSuggestions);
+
+        /**
+         * Clear internal data structure in preparation for user switch or initial store read.
+         */
+        void reset();
+
+        /**
+         * Indicates whether there is new data to serialize.
+         */
+        boolean hasNewDataToSerialize();
+    }
+
+    private final DataSource mDataSource;
+
+    public NetworkSuggestionStoreData(DataSource dataSource) {
+        mDataSource = dataSource;
+    }
+
+    @Override
+    public void serializeData(XmlSerializer out)
+            throws XmlPullParserException, IOException {
+        serializeNetworkSuggestionsMap(out, mDataSource.getNetworkSuggestions());
+    }
+
+    @Override
+    public void deserializeData(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        // Ignore empty reads.
+        if (in == null) {
+            return;
+        }
+        mDataSource.setNetworkSuggestions(parseNetworkSuggestionsMap(in, outerTagDepth));
+    }
+
+    @Override
+    public void resetData() {
+        mDataSource.reset();
+    }
+
+    @Override
+    public boolean hasNewDataToSerialize() {
+        return mDataSource.hasNewDataToSerialize();
+    }
+
+    @Override
+    public String getName() {
+        return XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION_MAP;
+    }
+
+    @Override
+    public @WifiConfigStore.StoreFileId int getStoreFileId() {
+        return WifiConfigStore.STORE_FILE_USER_NETWORK_SUGGESTIONS;
+    }
+
+    /**
+     * Serialize the map of package name to network suggestions to an output stream in XML format.
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void serializeNetworkSuggestionsMap(
+            XmlSerializer out, Map<String, Set<WifiNetworkSuggestion>> networkSuggestionsMap)
+            throws XmlPullParserException, IOException {
+        if (networkSuggestionsMap == null) {
+            return;
+        }
+        for (Entry<String, Set<WifiNetworkSuggestion>> entry : networkSuggestionsMap.entrySet()) {
+            String packageName = entry.getKey();
+            Set<WifiNetworkSuggestion> networkSuggestions = entry.getValue();
+
+            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION_PER_APP);
+            XmlUtil.writeNextValue(out, XML_TAG_SUGGESTOR_PACKAGE_NAME, packageName);
+            serializeNetworkSuggestions(out, networkSuggestions);
+            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION_PER_APP);
+        }
+    }
+
+    /**
+     * Serialize the set of network suggestions to an output stream in XML format.
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void serializeNetworkSuggestions(
+            XmlSerializer out, Set<WifiNetworkSuggestion> networkSuggestions)
+            throws XmlPullParserException, IOException {
+        for (WifiNetworkSuggestion networkSuggestion : networkSuggestions) {
+            serializeNetworkSuggestion(out, networkSuggestion);
+        }
+    }
+
+    /**
+     * Serialize a {@link WifiNetworkSuggestion} to an output stream in XML format.
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private void serializeNetworkSuggestion(XmlSerializer out, WifiNetworkSuggestion suggestion)
+            throws XmlPullParserException, IOException {
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION);
+
+        // Serialize WifiConfiguration.
+        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
+        WifiConfigurationXmlUtil.writeToXmlForConfigStore(out, suggestion.wifiConfiguration);
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION);
+
+        // Serialize other fields
+        XmlUtil.writeNextValue(out, XML_TAG_IS_APP_INTERACTION_REQUIRED,
+                suggestion.isAppInteractionRequired);
+        XmlUtil.writeNextValue(out, XML_TAG_IS_USER_INTERACTION_REQUIRED,
+                suggestion.isUserInteractionRequired);
+        XmlUtil.writeNextValue(out, XML_TAG_SUGGESTOR_UID, suggestion.suggestorUid);
+
+        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION);
+    }
+
+    /**
+     * Parse a map of package name to network suggestions from an input stream in XML format.
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private Map<String, Set<WifiNetworkSuggestion>> parseNetworkSuggestionsMap(
+            XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        Map<String, Set<WifiNetworkSuggestion>> networkSuggestionsMap = new HashMap<>();
+        while (XmlUtil.gotoNextSectionWithNameOrEnd(
+                in, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION_PER_APP, outerTagDepth)) {
+            // Try/catch only runtime exceptions (like illegal args), any XML/IO exceptions are
+            // fatal and should abort the entire loading process.
+            try {
+                String packageName =
+                        (String) XmlUtil.readNextValueWithName(in, XML_TAG_SUGGESTOR_PACKAGE_NAME);
+                Set<WifiNetworkSuggestion> networkSuggestions =
+                        parseNetworkSuggestions(in, outerTagDepth + 1);
+                networkSuggestionsMap.put(packageName, networkSuggestions);
+            } catch (RuntimeException e) {
+                // Failed to parse this network, skip it.
+                Log.e(TAG, "Failed to parse network suggestion. Skipping...", e);
+            }
+        }
+        return networkSuggestionsMap;
+    }
+
+    /**
+     * Parse a set of network suggestions from an input stream in XML format.
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private Set<WifiNetworkSuggestion> parseNetworkSuggestions(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        Set<WifiNetworkSuggestion> networkSuggestions = new HashSet<>();
+        while (XmlUtil.gotoNextSectionWithNameOrEnd(
+                in, XML_TAG_SECTION_HEADER_NETWORK_SUGGESTION, outerTagDepth)) {
+            // Try/catch only runtime exceptions (like illegal args), any XML/IO exceptions are
+            // fatal and should abort the entire loading process.
+            try {
+                WifiNetworkSuggestion networkSuggestion =
+                        parseNetworkSuggestion(in, outerTagDepth + 1);
+                networkSuggestions.add(networkSuggestion);
+            } catch (RuntimeException e) {
+                // Failed to parse this network, skip it.
+                Log.e(TAG, "Failed to parse network suggestion. Skipping...", e);
+            }
+        }
+        return networkSuggestions;
+    }
+
+    /**
+     * Parse a {@link WifiNetworkSuggestion} from an input stream in XML format.
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    private WifiNetworkSuggestion parseNetworkSuggestion(XmlPullParser in, int outerTagDepth)
+            throws XmlPullParserException, IOException {
+        WifiConfiguration wifiConfiguration = null;
+        boolean isAppInteractionRequired = false;
+        boolean isUserInteractionRequired = false;
+        int suggestorUid = Process.INVALID_UID;
+
+        // Loop through and parse out all the elements from the stream within this section.
+        while (XmlUtils.nextElementWithin(in, outerTagDepth)) {
+            if (in.getAttributeValue(null, "name") != null) {
+                // Value elements.
+                String[] valueName = new String[1];
+                Object value = XmlUtil.readCurrentValue(in, valueName);
+                switch (valueName[0]) {
+                    case XML_TAG_IS_APP_INTERACTION_REQUIRED:
+                        isAppInteractionRequired = (boolean) value;
+                        break;
+                    case XML_TAG_IS_USER_INTERACTION_REQUIRED:
+                        isUserInteractionRequired = (boolean) value;
+                        break;
+                    case XML_TAG_SUGGESTOR_UID:
+                        suggestorUid = (int) value;
+                        break;
+                    default:
+                        throw new XmlPullParserException(
+                                "Unknown value name found: " + valueName[0]);
+                }
+            } else {
+                if (!TextUtils.equals(in.getName(), XML_TAG_SECTION_HEADER_WIFI_CONFIGURATION)) {
+                    throw new XmlPullParserException("Unexpected section under configuration: "
+                            + in.getName());
+                }
+                Pair<String, WifiConfiguration> parsedConfigWithConfigKey =
+                        WifiConfigurationXmlUtil.parseFromXml(in, outerTagDepth + 1);
+                wifiConfiguration = parsedConfigWithConfigKey.second;
+            }
+        }
+        if (wifiConfiguration == null) {
+            throw new XmlPullParserException("XML parsing of wifi configuration failed");
+        }
+        if (suggestorUid == -1) {
+            throw new XmlPullParserException("XML parsing of suggestor uid failed");
+        }
+        return new WifiNetworkSuggestion(wifiConfiguration, isAppInteractionRequired,
+                isUserInteractionRequired, suggestorUid);
+    }
+}
+
diff --git a/service/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java b/service/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java
new file mode 100644
index 0000000..12776b2
--- /dev/null
+++ b/service/tests/wifitests/src/com/android/server/wifi/NetworkSuggestionStoreDataTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2018 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.*;
+import static org.mockito.Mockito.*;
+
+import android.net.wifi.WifiNetworkSuggestion;
+import android.support.test.filters.SmallTest;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+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.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link com.android.server.wifi.NetworkSuggestionStoreData}.
+ */
+@SmallTest
+public class NetworkSuggestionStoreDataTest {
+    private static final int TEST_UID_1 = 14556;
+    private static final int TEST_UID_2 = 14536;
+    private static final String TEST_PACKAGE_NAME_1 = "com.android.test.1";
+    private static final String TEST_PACKAGE_NAME_2 = "com.android.test.2";
+    private static final String TEST_CORRUPT_DATA_INVALID_SSID =
+            "<NetworkSuggestionPerApp>\n"
+            + "<string name=\"SuggestorPackageName\">com.android.test.1</string>\n"
+            + "<NetworkSuggestion>\n"
+            + "<WifiConfiguration>\n"
+            + "<string name=\"ConfigKey\">&quot;WifiConfigurationTestUtilSSID7&quot;NONE</string>\n"
+            + "<blah blah=\"SSID\">&quot;WifiConfigurationTestUtilSSID7&quot;</blah>\n"
+            + "<null name=\"BSSID\" />\n"
+            + "<null name=\"PreSharedKey\" />\n"
+            + "<null name=\"WEPKeys\" />\n"
+            + "<int name=\"WEPTxKeyIndex\" value=\"0\" />\n"
+            + "<boolean name=\"HiddenSSID\" value=\"false\" />\n"
+            + "<boolean name=\"RequirePMF\" value=\"false\" />\n"
+            + "<byte-array name=\"AllowedKeyMgmt\" num=\"1\">01</byte-array>\n"
+            + "<byte-array name=\"AllowedProtocols\" num=\"0\"></byte-array>\n"
+            + "<byte-array name=\"AllowedAuthAlgos\" num=\"0\"></byte-array>\n"
+            + "<byte-array name=\"AllowedGroupCiphers\" num=\"0\"></byte-array>\n"
+            + "<byte-array name=\"AllowedPairwiseCiphers\" num=\"0\"></byte-array>\n"
+            + "<byte-array name=\"AllowedGroupMgmtCiphers\" num=\"0\"></byte-array>\n"
+            + "<byte-array name=\"AllowedSuiteBCiphers\" num=\"0\"></byte-array>\n"
+            + "<boolean name=\"Shared\" value=\"true\" />\n"
+            + "<int name=\"Status\" value=\"2\" />\n"
+            + "<null name=\"FQDN\" />\n"
+            + "<null name=\"ProviderFriendlyName\" />\n"
+            + "<null name=\"LinkedNetworksList\" />\n"
+            + "<null name=\"DefaultGwMacAddress\" />\n"
+            + "<boolean name=\"ValidatedInternetAccess\" value=\"false\" />\n"
+            + "<boolean name=\"NoInternetAccessExpected\" value=\"false\" />\n"
+            + "<int name=\"UserApproved\" value=\"0\" />\n"
+            + "<boolean name=\"MeteredHint\" value=\"false\" />\n"
+            + "<int name=\"MeteredOverride\" value=\"0\" />\n"
+            + "<boolean name=\"UseExternalScores\" value=\"false\" />\n"
+            + "<int name=\"NumAssociation\" value=\"0\" />\n"
+            + "<int name=\"CreatorUid\" value=\"5\" />\n"
+            + "<null name=\"CreatorName\" />\n"
+            + "<null name=\"CreationTime\" />\n"
+            + "<int name=\"LastUpdateUid\" value=\"-1\" />\n"
+            + "<null name=\"LastUpdateName\" />\n"
+            + "<int name=\"LastConnectUid\" value=\"0\" />\n"
+            + "<boolean name=\"IsLegacyPasspointConfig\" value=\"false\" />\n"
+            + "<long-array name=\"RoamingConsortiumOIs\" num=\"0\" />\n"
+            + "<string name=\"RandomizedMacAddress\">02:00:00:00:00:00</string>\n"
+            + "</WifiConfiguration>\n"
+            + "<boolean name=\"IsAppInteractionRequired\" value=\"false\" />\n"
+            + "<boolean name=\"IsUserInteractionRequired\" value=\"false\" />\n"
+            + "<int name=\"SuggestorUid\" value=\"14556\" />\n"
+            + "</NetworkSuggestion>\n"
+            + "</NetworkSuggestionPerApp>";
+
+    private @Mock NetworkSuggestionStoreData.DataSource mDataSource;
+    private NetworkSuggestionStoreData mNetworkSuggestionStoreData;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mNetworkSuggestionStoreData = new NetworkSuggestionStoreData(mDataSource);
+    }
+
+    /**
+     * Helper function for serializing configuration data to a XML block.
+     */
+    private byte[] serializeData() throws Exception {
+        final XmlSerializer out = new FastXmlSerializer();
+        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        out.setOutput(outputStream, StandardCharsets.UTF_8.name());
+        mNetworkSuggestionStoreData.serializeData(out);
+        out.flush();
+        return outputStream.toByteArray();
+    }
+
+    /**
+     * Helper function for parsing configuration data from a XML block.
+     */
+    private void deserializeData(byte[] data) throws Exception {
+        final XmlPullParser in = Xml.newPullParser();
+        final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+        in.setInput(inputStream, StandardCharsets.UTF_8.name());
+        mNetworkSuggestionStoreData.deserializeData(in, in.getDepth());
+    }
+
+    /**
+     * Verify store file Id.
+     */
+    @Test
+    public void verifyStoreFileId() throws Exception {
+        assertEquals(WifiConfigStore.STORE_FILE_USER_NETWORK_SUGGESTIONS,
+                mNetworkSuggestionStoreData.getStoreFileId());
+    }
+
+    /**
+     * Serialize/Deserialize a single network suggestion from a single app.
+     */
+    @Test
+    public void serializeDeserializeSingleNetworkSuggestionFromSingleApp() throws Exception {
+        WifiNetworkSuggestion networkSuggestion = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), false, false, TEST_UID_1);
+        Map<String, Set<WifiNetworkSuggestion>> networkSuggestionsMap = new HashMap<>();
+        Set<WifiNetworkSuggestion> networkSuggestionsSet = new HashSet<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion);
+            }};
+        networkSuggestionsMap.put(TEST_PACKAGE_NAME_1, networkSuggestionsSet);
+
+        assertSerializeDeserialize(networkSuggestionsMap);
+    }
+
+    /**
+     * Serialize/Deserialize a single network suggestion from multiple apps.
+     */
+    @Test
+    public void serializeDeserializeSingleNetworkSuggestionFromMultipleApps() throws Exception {
+        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), false, false, TEST_UID_1);
+        Set<WifiNetworkSuggestion> networkSuggestionsSet1 = new HashSet<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion1);
+            }};
+        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), true, false, TEST_UID_2);
+        Set<WifiNetworkSuggestion> networkSuggestionsSet2 = new HashSet<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion2);
+            }};
+        Map<String, Set<WifiNetworkSuggestion>> networkSuggestionsMap = new HashMap<>();
+        networkSuggestionsMap.put(TEST_PACKAGE_NAME_1, networkSuggestionsSet1);
+        networkSuggestionsMap.put(TEST_PACKAGE_NAME_2, networkSuggestionsSet2);
+
+        assertSerializeDeserialize(networkSuggestionsMap);
+    }
+
+    /**
+     * Serialize/Deserialize multiple network suggestion from multiple apps.
+     */
+    @Test
+    public void serializeDeserializeMultipleNetworkSuggestionFromMultipleApps() throws Exception {
+        WifiNetworkSuggestion networkSuggestion1 = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), false, true, TEST_UID_1);
+        WifiNetworkSuggestion networkSuggestion2 = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), true, false, TEST_UID_1);
+        Set<WifiNetworkSuggestion> networkSuggestionsSet1 = new HashSet<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion1);
+                    add(networkSuggestion2);
+            }};
+        WifiNetworkSuggestion networkSuggestion3 = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), true, false, TEST_UID_2);
+        WifiNetworkSuggestion networkSuggestion4 = new WifiNetworkSuggestion(
+                WifiConfigurationTestUtil.createOpenNetwork(), false, true, TEST_UID_2);
+        Set<WifiNetworkSuggestion> networkSuggestionsSet2 = new HashSet<WifiNetworkSuggestion>() {{
+                    add(networkSuggestion3);
+                    add(networkSuggestion4);
+            }};
+        Map<String, Set<WifiNetworkSuggestion>> networkSuggestionsMap = new HashMap<>();
+        networkSuggestionsMap.put(TEST_PACKAGE_NAME_1, networkSuggestionsSet1);
+        networkSuggestionsMap.put(TEST_PACKAGE_NAME_2, networkSuggestionsSet2);
+
+        assertSerializeDeserialize(networkSuggestionsMap);
+    }
+
+    /**
+     * Deserialize corrupt data and ensure that we gracefully handle any errors in the data.
+     * graceful == throw XmlPullParserException (which is handled in
+     * {@link WifiConfigManager#loadFromStore()}).
+     */
+    @Test(expected = XmlPullParserException.class)
+    public void deserializeCorruptData() throws Exception {
+        deserializeData(TEST_CORRUPT_DATA_INVALID_SSID.getBytes());
+    }
+
+    private void assertSerializeDeserialize(
+            Map<String, Set<WifiNetworkSuggestion>> networkSuggestionsMap) throws Exception {
+        // Setup the data to serialize.
+        when(mDataSource.getNetworkSuggestions()).thenReturn(networkSuggestionsMap);
+
+        // Serialize/deserialize data.
+        deserializeData(serializeData());
+
+        // Verify the deserialized data.
+        ArgumentCaptor<HashMap> deserializedNetworkSuggestionsMap =
+                ArgumentCaptor.forClass(HashMap.class);
+        verify(mDataSource).setNetworkSuggestions(deserializedNetworkSuggestionsMap.capture());
+        assertEquals(networkSuggestionsMap, deserializedNetworkSuggestionsMap.getValue());
+    }
+}