blob: 76f8910f164548476b3f14fe93932083f12b01fc [file] [log] [blame]
/*
* 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);
}
}