blob: cd1fe8b21ce3f057eb10d23c6425baa870647e2c [file] [log] [blame]
/*
* Copyright (C) 2016 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.content.Context;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.net.wifi.WifiSsid;
import android.os.Environment;
import android.os.Process;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;
import com.android.server.net.DelayedDiskWrite;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Provides an API to read and write the network history from WifiConfigurations to file
* This is largely separate and extra to the supplicant config file.
*/
public class WifiNetworkHistory {
public static final String TAG = "WifiNetworkHistory";
private static final boolean DBG = true;
private static final boolean VDBG = true;
static final String NETWORK_HISTORY_CONFIG_FILE = Environment.getDataDirectory()
+ "/misc/wifi/networkHistory.txt";
/* Network History Keys */
private static final String SSID_KEY = "SSID";
static final String CONFIG_KEY = "CONFIG";
private static final String CONFIG_BSSID_KEY = "CONFIG_BSSID";
private static final String CHOICE_KEY = "CHOICE";
private static final String CHOICE_TIME_KEY = "CHOICE_TIME";
private static final String LINK_KEY = "LINK";
private static final String BSSID_KEY = "BSSID";
private static final String BSSID_KEY_END = "/BSSID";
private static final String RSSI_KEY = "RSSI";
private static final String FREQ_KEY = "FREQ";
private static final String DATE_KEY = "DATE";
private static final String MILLI_KEY = "MILLI";
private static final String NETWORK_ID_KEY = "ID";
private static final String PRIORITY_KEY = "PRIORITY";
private static final String DEFAULT_GW_KEY = "DEFAULT_GW";
private static final String AUTH_KEY = "AUTH";
private static final String BSSID_STATUS_KEY = "BSSID_STATUS";
private static final String SELF_ADDED_KEY = "SELF_ADDED";
private static final String FAILURE_KEY = "FAILURE";
private static final String DID_SELF_ADD_KEY = "DID_SELF_ADD";
private static final String PEER_CONFIGURATION_KEY = "PEER_CONFIGURATION";
static final String CREATOR_UID_KEY = "CREATOR_UID_KEY";
private static final String CONNECT_UID_KEY = "CONNECT_UID_KEY";
private static final String UPDATE_UID_KEY = "UPDATE_UID";
private static final String FQDN_KEY = "FQDN";
private static final String SCORER_OVERRIDE_KEY = "SCORER_OVERRIDE";
private static final String SCORER_OVERRIDE_AND_SWITCH_KEY = "SCORER_OVERRIDE_AND_SWITCH";
private static final String VALIDATED_INTERNET_ACCESS_KEY = "VALIDATED_INTERNET_ACCESS";
private static final String NO_INTERNET_ACCESS_REPORTS_KEY = "NO_INTERNET_ACCESS_REPORTS";
private static final String EPHEMERAL_KEY = "EPHEMERAL";
private static final String USE_EXTERNAL_SCORES_KEY = "USE_EXTERNAL_SCORES";
private static final String METERED_HINT_KEY = "METERED_HINT";
private static final String NUM_ASSOCIATION_KEY = "NUM_ASSOCIATION";
private static final String DELETED_EPHEMERAL_KEY = "DELETED_EPHEMERAL";
private static final String CREATOR_NAME_KEY = "CREATOR_NAME";
private static final String UPDATE_NAME_KEY = "UPDATE_NAME";
private static final String USER_APPROVED_KEY = "USER_APPROVED";
private static final String CREATION_TIME_KEY = "CREATION_TIME";
private static final String UPDATE_TIME_KEY = "UPDATE_TIME";
static final String SHARED_KEY = "SHARED";
private static final String NETWORK_SELECTION_STATUS_KEY = "NETWORK_SELECTION_STATUS";
private static final String NETWORK_SELECTION_DISABLE_REASON_KEY =
"NETWORK_SELECTION_DISABLE_REASON";
private static final String HAS_EVER_CONNECTED_KEY = "HAS_EVER_CONNECTED";
private static final String SEPARATOR = ": ";
private static final String NL = "\n";
protected final DelayedDiskWrite mWriter;
Context mContext;
private final LocalLog mLocalLog;
/*
* Lost config list, whenever we read a config from networkHistory.txt that was not in
* wpa_supplicant.conf
*/
HashSet<String> mLostConfigsDbg = new HashSet<String>();
public WifiNetworkHistory(Context c, LocalLog localLog, DelayedDiskWrite writer) {
mContext = c;
mWriter = writer;
mLocalLog = localLog;
}
/**
* Write network history to file, for configured networks
*
* @param networks List of ConfiguredNetworks to write to NetworkHistory
*/
public void writeKnownNetworkHistory(final List<WifiConfiguration> networks,
final ConcurrentHashMap<Integer, ScanDetailCache> scanDetailCaches,
final Set<String> deletedEphemeralSSIDs) {
/* Make a copy */
//final List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
//for (WifiConfiguration config : mConfiguredNetworks.valuesForAllUsers()) {
// networks.add(new WifiConfiguration(config));
//}
mWriter.write(NETWORK_HISTORY_CONFIG_FILE, new DelayedDiskWrite.Writer() {
public void onWriteCalled(DataOutputStream out) throws IOException {
for (WifiConfiguration config : networks) {
//loge("onWriteCalled write SSID: " + config.SSID);
/* if (config.getLinkProperties() != null)
loge(" lp " + config.getLinkProperties().toString());
else
loge("attempt config w/o lp");
*/
NetworkSelectionStatus status = config.getNetworkSelectionStatus();
if (VDBG) {
int numlink = 0;
if (config.linkedConfigurations != null) {
numlink = config.linkedConfigurations.size();
}
String disableTime;
if (config.getNetworkSelectionStatus().isNetworkEnabled()) {
disableTime = "";
} else {
disableTime = "Disable time: " + DateFormat.getInstance().format(
config.getNetworkSelectionStatus().getDisableTime());
}
logd("saving network history: " + config.configKey() + " gw: "
+ config.defaultGwMacAddress + " Network Selection-status: "
+ status.getNetworkStatusString()
+ disableTime + " ephemeral=" + config.ephemeral
+ " choice:" + status.getConnectChoice()
+ " link:" + numlink
+ " status:" + config.status
+ " nid:" + config.networkId
+ " hasEverConnected: " + status.getHasEverConnected());
}
if (!isValid(config)) {
continue;
}
if (config.SSID == null) {
if (VDBG) {
logv("writeKnownNetworkHistory trying to write config with null SSID");
}
continue;
}
if (VDBG) {
logv("writeKnownNetworkHistory write config " + config.configKey());
}
out.writeUTF(CONFIG_KEY + SEPARATOR + config.configKey() + NL);
if (config.SSID != null) {
out.writeUTF(SSID_KEY + SEPARATOR + config.SSID + NL);
}
if (config.BSSID != null) {
out.writeUTF(CONFIG_BSSID_KEY + SEPARATOR + config.BSSID + NL);
} else {
out.writeUTF(CONFIG_BSSID_KEY + SEPARATOR + "null" + NL);
}
if (config.FQDN != null) {
out.writeUTF(FQDN_KEY + SEPARATOR + config.FQDN + NL);
}
out.writeUTF(PRIORITY_KEY + SEPARATOR + Integer.toString(config.priority) + NL);
out.writeUTF(NETWORK_ID_KEY + SEPARATOR
+ Integer.toString(config.networkId) + NL);
out.writeUTF(SELF_ADDED_KEY + SEPARATOR
+ Boolean.toString(config.selfAdded) + NL);
out.writeUTF(DID_SELF_ADD_KEY + SEPARATOR
+ Boolean.toString(config.didSelfAdd) + NL);
out.writeUTF(NO_INTERNET_ACCESS_REPORTS_KEY + SEPARATOR
+ Integer.toString(config.numNoInternetAccessReports) + NL);
out.writeUTF(VALIDATED_INTERNET_ACCESS_KEY + SEPARATOR
+ Boolean.toString(config.validatedInternetAccess) + NL);
out.writeUTF(EPHEMERAL_KEY + SEPARATOR
+ Boolean.toString(config.ephemeral) + NL);
out.writeUTF(METERED_HINT_KEY + SEPARATOR
+ Boolean.toString(config.meteredHint) + NL);
out.writeUTF(USE_EXTERNAL_SCORES_KEY + SEPARATOR
+ Boolean.toString(config.useExternalScores) + NL);
if (config.creationTime != null) {
out.writeUTF(CREATION_TIME_KEY + SEPARATOR + config.creationTime + NL);
}
if (config.updateTime != null) {
out.writeUTF(UPDATE_TIME_KEY + SEPARATOR + config.updateTime + NL);
}
if (config.peerWifiConfiguration != null) {
out.writeUTF(PEER_CONFIGURATION_KEY + SEPARATOR
+ config.peerWifiConfiguration + NL);
}
out.writeUTF(SCORER_OVERRIDE_KEY + SEPARATOR
+ Integer.toString(config.numScorerOverride) + NL);
out.writeUTF(SCORER_OVERRIDE_AND_SWITCH_KEY + SEPARATOR
+ Integer.toString(config.numScorerOverrideAndSwitchedNetwork) + NL);
out.writeUTF(NUM_ASSOCIATION_KEY + SEPARATOR
+ Integer.toString(config.numAssociation) + NL);
out.writeUTF(CREATOR_UID_KEY + SEPARATOR
+ Integer.toString(config.creatorUid) + NL);
out.writeUTF(CONNECT_UID_KEY + SEPARATOR
+ Integer.toString(config.lastConnectUid) + NL);
out.writeUTF(UPDATE_UID_KEY + SEPARATOR
+ Integer.toString(config.lastUpdateUid) + NL);
out.writeUTF(CREATOR_NAME_KEY + SEPARATOR
+ config.creatorName + NL);
out.writeUTF(UPDATE_NAME_KEY + SEPARATOR
+ config.lastUpdateName + NL);
out.writeUTF(USER_APPROVED_KEY + SEPARATOR
+ Integer.toString(config.userApproved) + NL);
out.writeUTF(SHARED_KEY + SEPARATOR + Boolean.toString(config.shared) + NL);
String allowedKeyManagementString =
makeString(config.allowedKeyManagement,
WifiConfiguration.KeyMgmt.strings);
out.writeUTF(AUTH_KEY + SEPARATOR
+ allowedKeyManagementString + NL);
out.writeUTF(NETWORK_SELECTION_STATUS_KEY + SEPARATOR
+ status.getNetworkSelectionStatus() + NL);
out.writeUTF(NETWORK_SELECTION_DISABLE_REASON_KEY + SEPARATOR
+ status.getNetworkSelectionDisableReason() + NL);
if (status.getConnectChoice() != null) {
out.writeUTF(CHOICE_KEY + SEPARATOR + status.getConnectChoice() + NL);
out.writeUTF(CHOICE_TIME_KEY + SEPARATOR
+ status.getConnectChoiceTimestamp() + NL);
}
if (config.linkedConfigurations != null) {
log("writeKnownNetworkHistory write linked "
+ config.linkedConfigurations.size());
for (String key : config.linkedConfigurations.keySet()) {
out.writeUTF(LINK_KEY + SEPARATOR + key + NL);
}
}
String macAddress = config.defaultGwMacAddress;
if (macAddress != null) {
out.writeUTF(DEFAULT_GW_KEY + SEPARATOR + macAddress + NL);
}
if (getScanDetailCache(config, scanDetailCaches) != null) {
for (ScanDetail scanDetail : getScanDetailCache(config,
scanDetailCaches).values()) {
ScanResult result = scanDetail.getScanResult();
out.writeUTF(BSSID_KEY + SEPARATOR
+ result.BSSID + NL);
out.writeUTF(FREQ_KEY + SEPARATOR
+ Integer.toString(result.frequency) + NL);
out.writeUTF(RSSI_KEY + SEPARATOR
+ Integer.toString(result.level) + NL);
out.writeUTF(BSSID_KEY_END + NL);
}
}
if (config.lastFailure != null) {
out.writeUTF(FAILURE_KEY + SEPARATOR + config.lastFailure + NL);
}
out.writeUTF(HAS_EVER_CONNECTED_KEY + SEPARATOR
+ Boolean.toString(status.getHasEverConnected()) + NL);
out.writeUTF(NL);
// Add extra blank lines for clarity
out.writeUTF(NL);
out.writeUTF(NL);
}
if (deletedEphemeralSSIDs != null && deletedEphemeralSSIDs.size() > 0) {
for (String ssid : deletedEphemeralSSIDs) {
out.writeUTF(DELETED_EPHEMERAL_KEY);
out.writeUTF(ssid);
out.writeUTF(NL);
}
}
}
});
}
/**
* Adds information stored in networkHistory.txt to the given configs. The configs are provided
* as a mapping from configKey to WifiConfiguration, because the WifiConfigurations themselves
* do not contain sufficient information to compute their configKeys until after the information
* that is stored in networkHistory.txt has been added to them.
*
* @param configs mapping from configKey to a WifiConfiguration that contains the information
* information read from wpa_supplicant.conf
*/
public void readNetworkHistory(Map<String, WifiConfiguration> configs,
ConcurrentHashMap<Integer, ScanDetailCache> scanDetailCaches,
Set<String> deletedEphemeralSSIDs) {
localLog("readNetworkHistory() path:" + NETWORK_HISTORY_CONFIG_FILE);
try (DataInputStream in =
new DataInputStream(new BufferedInputStream(
new FileInputStream(NETWORK_HISTORY_CONFIG_FILE)))) {
String bssid = null;
String ssid = null;
int freq = 0;
int status = 0;
long seen = 0;
int rssi = WifiConfiguration.INVALID_RSSI;
String caps = null;
WifiConfiguration config = null;
while (true) {
String line = in.readUTF();
if (line == null) {
break;
}
int colon = line.indexOf(':');
if (colon < 0) {
continue;
}
String key = line.substring(0, colon).trim();
String value = line.substring(colon + 1).trim();
if (key.equals(CONFIG_KEY)) {
config = configs.get(value);
// skip reading that configuration data
// since we don't have a corresponding network ID
if (config == null) {
localLog("readNetworkHistory didnt find netid for hash="
+ Integer.toString(value.hashCode())
+ " key: " + value);
mLostConfigsDbg.add(value);
continue;
} else {
// After an upgrade count old connections as owned by system
if (config.creatorName == null || config.lastUpdateName == null) {
config.creatorName =
mContext.getPackageManager().getNameForUid(Process.SYSTEM_UID);
config.lastUpdateName = config.creatorName;
if (DBG) {
Log.w(TAG, "Upgrading network " + config.networkId
+ " to " + config.creatorName);
}
}
}
} else if (config != null) {
NetworkSelectionStatus networkStatus = config.getNetworkSelectionStatus();
switch (key) {
case SSID_KEY:
if (config.isPasspoint()) {
break;
}
ssid = value;
if (config.SSID != null && !config.SSID.equals(ssid)) {
loge("Error parsing network history file, mismatched SSIDs");
config = null; //error
ssid = null;
} else {
config.SSID = ssid;
}
break;
case CONFIG_BSSID_KEY:
config.BSSID = value.equals("null") ? null : value;
break;
case FQDN_KEY:
// Check for literal 'null' to be backwards compatible.
config.FQDN = value.equals("null") ? null : value;
break;
case DEFAULT_GW_KEY:
config.defaultGwMacAddress = value;
break;
case SELF_ADDED_KEY:
config.selfAdded = Boolean.parseBoolean(value);
break;
case DID_SELF_ADD_KEY:
config.didSelfAdd = Boolean.parseBoolean(value);
break;
case NO_INTERNET_ACCESS_REPORTS_KEY:
config.numNoInternetAccessReports = Integer.parseInt(value);
break;
case VALIDATED_INTERNET_ACCESS_KEY:
config.validatedInternetAccess = Boolean.parseBoolean(value);
break;
case CREATION_TIME_KEY:
config.creationTime = value;
break;
case UPDATE_TIME_KEY:
config.updateTime = value;
break;
case EPHEMERAL_KEY:
config.ephemeral = Boolean.parseBoolean(value);
break;
case METERED_HINT_KEY:
config.meteredHint = Boolean.parseBoolean(value);
break;
case USE_EXTERNAL_SCORES_KEY:
config.useExternalScores = Boolean.parseBoolean(value);
break;
case CREATOR_UID_KEY:
config.creatorUid = Integer.parseInt(value);
break;
case SCORER_OVERRIDE_KEY:
config.numScorerOverride = Integer.parseInt(value);
break;
case SCORER_OVERRIDE_AND_SWITCH_KEY:
config.numScorerOverrideAndSwitchedNetwork = Integer.parseInt(value);
break;
case NUM_ASSOCIATION_KEY:
config.numAssociation = Integer.parseInt(value);
break;
case CONNECT_UID_KEY:
config.lastConnectUid = Integer.parseInt(value);
break;
case UPDATE_UID_KEY:
config.lastUpdateUid = Integer.parseInt(value);
break;
case FAILURE_KEY:
config.lastFailure = value;
break;
case PEER_CONFIGURATION_KEY:
config.peerWifiConfiguration = value;
break;
case NETWORK_SELECTION_STATUS_KEY:
int networkStatusValue = Integer.parseInt(value);
// Reset temporarily disabled network status
if (networkStatusValue ==
NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED) {
networkStatusValue =
NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
}
networkStatus.setNetworkSelectionStatus(networkStatusValue);
break;
case NETWORK_SELECTION_DISABLE_REASON_KEY:
networkStatus.setNetworkSelectionDisableReason(Integer.parseInt(value));
break;
case CHOICE_KEY:
networkStatus.setConnectChoice(value);
break;
case CHOICE_TIME_KEY:
networkStatus.setConnectChoiceTimestamp(Long.parseLong(value));
break;
case LINK_KEY:
if (config.linkedConfigurations == null) {
config.linkedConfigurations = new HashMap<>();
} else {
config.linkedConfigurations.put(value, -1);
}
break;
case BSSID_KEY:
status = 0;
ssid = null;
bssid = null;
freq = 0;
seen = 0;
rssi = WifiConfiguration.INVALID_RSSI;
caps = "";
break;
case RSSI_KEY:
rssi = Integer.parseInt(value);
break;
case FREQ_KEY:
freq = Integer.parseInt(value);
break;
case DATE_KEY:
/*
* when reading the configuration from file we don't update the date
* so as to avoid reading back stale or non-sensical data that would
* depend on network time.
* The date of a WifiConfiguration should only come from actual scan
* result.
*
String s = key.replace(FREQ_KEY, "");
seen = Integer.getInteger(s);
*/
break;
case BSSID_KEY_END:
if ((bssid != null) && (ssid != null)) {
if (getScanDetailCache(config, scanDetailCaches) != null) {
WifiSsid wssid = WifiSsid.createFromAsciiEncoded(ssid);
ScanDetail scanDetail = new ScanDetail(wssid, bssid,
caps, rssi, freq, (long) 0, seen);
getScanDetailCache(config, scanDetailCaches).put(scanDetail);
}
}
break;
case DELETED_EPHEMERAL_KEY:
if (!TextUtils.isEmpty(value)) {
deletedEphemeralSSIDs.add(value);
}
break;
case CREATOR_NAME_KEY:
config.creatorName = value;
break;
case UPDATE_NAME_KEY:
config.lastUpdateName = value;
break;
case USER_APPROVED_KEY:
config.userApproved = Integer.parseInt(value);
break;
case SHARED_KEY:
config.shared = Boolean.parseBoolean(value);
break;
case HAS_EVER_CONNECTED_KEY:
networkStatus.setHasEverConnected(Boolean.parseBoolean(value));
break;
}
}
}
} catch (NumberFormatException e) {
Log.e(TAG, "readNetworkHistory: failed to read, revert to default, " + e, e);
} catch (EOFException e) {
// do nothing
} catch (IOException e) {
Log.e(TAG, "readNetworkHistory: No config file, revert to default, " + e, e);
}
}
/**
* Ported this out of WifiServiceImpl, I have no idea what it's doing
* <TODO> figure out what/why this is doing
* <TODO> Port it into WifiConfiguration, then remove all the silly business from ServiceImpl
*/
public boolean isValid(WifiConfiguration config) {
if (config.allowedKeyManagement == null) {
return false;
}
if (config.allowedKeyManagement.cardinality() > 1) {
if (config.allowedKeyManagement.cardinality() != 2) {
return false;
}
if (!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
return false;
}
if ((!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X))
&& (!config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK))) {
return false;
}
}
return true;
}
private static String makeString(BitSet set, String[] strings) {
StringBuffer buf = new StringBuffer();
int nextSetBit = -1;
/* Make sure all set bits are in [0, strings.length) to avoid
* going out of bounds on strings. (Shouldn't happen, but...) */
set = set.get(0, strings.length);
while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
}
// remove trailing space
if (set.cardinality() > 0) {
buf.setLength(buf.length() - 1);
}
return buf.toString();
}
protected void logv(String s) {
Log.v(TAG, s);
}
protected void logd(String s) {
Log.d(TAG, s);
}
protected void log(String s) {
Log.d(TAG, s);
}
protected void loge(String s) {
loge(s, false);
}
protected void loge(String s, boolean stack) {
if (stack) {
Log.e(TAG, s + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
+ " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
+ " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
+ " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
} else {
Log.e(TAG, s);
}
}
private void localLog(String s) {
if (mLocalLog != null) {
mLocalLog.log(s);
}
}
private ScanDetailCache getScanDetailCache(WifiConfiguration config,
ConcurrentHashMap<Integer, ScanDetailCache> scanDetailCaches) {
if (config == null || scanDetailCaches == null) return null;
ScanDetailCache cache = scanDetailCaches.get(config.networkId);
if (cache == null && config.networkId != WifiConfiguration.INVALID_NETWORK_ID) {
cache = new ScanDetailCache(config);
scanDetailCaches.put(config.networkId, cache);
}
return cache;
}
}