Home | History | Annotate | Download | only in wifi
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.wifi;
     18 
     19 import android.content.Context;
     20 import android.net.IpConfiguration.IpAssignment;
     21 import android.net.IpConfiguration.ProxySettings;
     22 import android.net.wifi.WifiConfiguration;
     23 import android.net.wifi.WifiConfiguration.Status;
     24 import android.net.wifi.WifiEnterpriseConfig;
     25 import android.net.wifi.WifiSsid;
     26 import android.net.wifi.WpsInfo;
     27 import android.net.wifi.WpsResult;
     28 import android.os.FileObserver;
     29 import android.os.Process;
     30 import android.security.Credentials;
     31 import android.security.KeyChain;
     32 import android.security.KeyStore;
     33 import android.text.TextUtils;
     34 import android.util.ArraySet;
     35 import android.util.LocalLog;
     36 import android.util.Log;
     37 import android.util.SparseArray;
     38 
     39 import com.android.server.wifi.hotspot2.Utils;
     40 import com.android.server.wifi.util.TelephonyUtil;
     41 
     42 import org.json.JSONException;
     43 import org.json.JSONObject;
     44 
     45 import java.io.BufferedReader;
     46 import java.io.File;
     47 import java.io.FileNotFoundException;
     48 import java.io.FileReader;
     49 import java.io.IOException;
     50 import java.net.URLDecoder;
     51 import java.nio.charset.StandardCharsets;
     52 import java.security.PrivateKey;
     53 import java.security.cert.Certificate;
     54 import java.security.cert.CertificateException;
     55 import java.security.cert.X509Certificate;
     56 import java.util.ArrayList;
     57 import java.util.Arrays;
     58 import java.util.BitSet;
     59 import java.util.Collection;
     60 import java.util.HashMap;
     61 import java.util.HashSet;
     62 import java.util.List;
     63 import java.util.Map;
     64 import java.util.Set;
     65 
     66 /**
     67  * This class provides the API's to save/load/modify network configurations from a persistent
     68  * config database.
     69  * We use wpa_supplicant as our config database currently, but will be migrating to a different
     70  * one sometime in the future.
     71  * We use keystore for certificate/key management operations.
     72  *
     73  * NOTE: This class should only be used from WifiConfigManager!!!
     74  */
     75 public class WifiConfigStore {
     76 
     77     public static final String TAG = "WifiConfigStore";
     78     // This is the only variable whose contents will not be interpreted by wpa_supplicant. We use it
     79     // to store metadata that allows us to correlate a wpa_supplicant.conf entry with additional
     80     // information about the same network stored in other files. The metadata is stored as a
     81     // serialized JSON dictionary.
     82     public static final String ID_STRING_VAR_NAME = "id_str";
     83     public static final String ID_STRING_KEY_FQDN = "fqdn";
     84     public static final String ID_STRING_KEY_CREATOR_UID = "creatorUid";
     85     public static final String ID_STRING_KEY_CONFIG_KEY = "configKey";
     86     public static final String SUPPLICANT_CONFIG_FILE = "/data/misc/wifi/wpa_supplicant.conf";
     87     public static final String SUPPLICANT_CONFIG_FILE_BACKUP = SUPPLICANT_CONFIG_FILE + ".tmp";
     88 
     89     // Value stored by supplicant to requirePMF
     90     public static final int STORED_VALUE_FOR_REQUIRE_PMF = 2;
     91 
     92     private static final boolean DBG = true;
     93     private static boolean VDBG = false;
     94 
     95     private final LocalLog mLocalLog;
     96     private final WpaConfigFileObserver mFileObserver;
     97     private final Context mContext;
     98     private final WifiNative mWifiNative;
     99     private final KeyStore mKeyStore;
    100     private final boolean mShowNetworks;
    101     private final HashSet<String> mBssidBlacklist = new HashSet<String>();
    102 
    103     private final BackupManagerProxy mBackupManagerProxy;
    104 
    105     WifiConfigStore(Context context, WifiNative wifiNative, KeyStore keyStore, LocalLog localLog,
    106             boolean showNetworks, boolean verboseDebug) {
    107         mContext = context;
    108         mWifiNative = wifiNative;
    109         mKeyStore = keyStore;
    110         mShowNetworks = showNetworks;
    111         mBackupManagerProxy = new BackupManagerProxy();
    112 
    113         if (mShowNetworks) {
    114             mLocalLog = localLog;
    115             mFileObserver = new WpaConfigFileObserver();
    116             mFileObserver.startWatching();
    117         } else {
    118             mLocalLog = null;
    119             mFileObserver = null;
    120         }
    121         VDBG = verboseDebug;
    122     }
    123 
    124     private static String removeDoubleQuotes(String string) {
    125         int length = string.length();
    126         if ((length > 1) && (string.charAt(0) == '"')
    127                 && (string.charAt(length - 1) == '"')) {
    128             return string.substring(1, length - 1);
    129         }
    130         return string;
    131     }
    132 
    133     /**
    134      * Generate a string to be used as a key value by wpa_supplicant from
    135      * 'set', within the set of strings from 'strings' for the variable concatenated.
    136      * Also transform the internal string format that uses _ (for bewildering
    137      * reasons) into a wpa_supplicant adjusted value, that uses - as a separator
    138      * (most of the time at least...).
    139      * @param set a bit set with a one for each corresponding string to be included from strings.
    140      * @param strings the set of string literals to concatenate strinfs from.
    141      * @return A wpa_supplicant formatted value.
    142      */
    143     private static String makeString(BitSet set, String[] strings) {
    144         return makeStringWithException(set, strings, null);
    145     }
    146 
    147     /**
    148      * Same as makeString with an exclusion parameter.
    149      * @param set a bit set with a one for each corresponding string to be included from strings.
    150      * @param strings the set of string literals to concatenate strinfs from.
    151      * @param exception literal string to be excluded from the _ to - transformation.
    152      * @return A wpa_supplicant formatted value.
    153      */
    154     private static String makeStringWithException(BitSet set, String[] strings, String exception) {
    155         StringBuilder result = new StringBuilder();
    156 
    157         /* Make sure all set bits are in [0, strings.length) to avoid
    158          * going out of bounds on strings.  (Shouldn't happen, but...) */
    159         BitSet trimmedSet = set.get(0, strings.length);
    160 
    161         List<String> valueSet = new ArrayList<>();
    162         for (int bit = trimmedSet.nextSetBit(0);
    163              bit >= 0;
    164              bit = trimmedSet.nextSetBit(bit+1)) {
    165             String currentName = strings[bit];
    166             if (exception != null && currentName.equals(exception)) {
    167                 valueSet.add(currentName);
    168             } else {
    169                 // Most wpa_supplicant strings use a dash whereas (for some bizarre
    170                 // reason) the strings are defined with underscore in the code...
    171                 valueSet.add(currentName.replace('_', '-'));
    172             }
    173         }
    174         return TextUtils.join(" ", valueSet);
    175     }
    176 
    177     /*
    178      * Convert string to Hexadecimal before passing to wifi native layer
    179      * In native function "doCommand()" have trouble in converting Unicode character string to UTF8
    180      * conversion to hex is required because SSIDs can have space characters in them;
    181      * and that can confuses the supplicant because it uses space charaters as delimiters
    182      */
    183     private static String encodeSSID(String str) {
    184         return Utils.toHex(removeDoubleQuotes(str).getBytes(StandardCharsets.UTF_8));
    185     }
    186 
    187     // Certificate and private key management for EnterpriseConfig
    188     private static boolean needsKeyStore(WifiEnterpriseConfig config) {
    189         return (!(config.getClientCertificate() == null && config.getCaCertificate() == null));
    190     }
    191 
    192     private static boolean isHardwareBackedKey(PrivateKey key) {
    193         return KeyChain.isBoundKeyAlgorithm(key.getAlgorithm());
    194     }
    195 
    196     private static boolean hasHardwareBackedKey(Certificate certificate) {
    197         return KeyChain.isBoundKeyAlgorithm(certificate.getPublicKey().getAlgorithm());
    198     }
    199 
    200     private static boolean needsSoftwareBackedKeyStore(WifiEnterpriseConfig config) {
    201         java.lang.String client = config.getClientCertificateAlias();
    202         if (!TextUtils.isEmpty(client)) {
    203             // a valid client certificate is configured
    204 
    205             // BUGBUG: keyStore.get() never returns certBytes; because it is not
    206             // taking WIFI_UID as a parameter. It always looks for certificate
    207             // with SYSTEM_UID, and never finds any Wifi certificates. Assuming that
    208             // all certificates need software keystore until we get the get() API
    209             // fixed.
    210             return true;
    211         }
    212         return false;
    213     }
    214 
    215     private int lookupString(String string, String[] strings) {
    216         int size = strings.length;
    217 
    218         string = string.replace('-', '_');
    219 
    220         for (int i = 0; i < size; i++) {
    221             if (string.equals(strings[i])) {
    222                 return i;
    223             }
    224         }
    225         loge("Failed to look-up a string: " + string);
    226         return -1;
    227     }
    228 
    229     private void readNetworkBitsetVariable(int netId, BitSet variable, String varName,
    230             String[] strings) {
    231         String value = mWifiNative.getNetworkVariable(netId, varName);
    232         if (!TextUtils.isEmpty(value)) {
    233             variable.clear();
    234             String[] vals = value.split(" ");
    235             for (String val : vals) {
    236                 int index = lookupString(val, strings);
    237                 if (0 <= index) {
    238                     variable.set(index);
    239                 }
    240             }
    241         }
    242     }
    243 
    244     /**
    245      * Read the variables from the supplicant daemon that are needed to
    246      * fill in the WifiConfiguration object.
    247      *
    248      * @param config the {@link WifiConfiguration} object to be filled in.
    249      */
    250     public void readNetworkVariables(WifiConfiguration config) {
    251         if (config == null) {
    252             return;
    253         }
    254         if (VDBG) localLog("readNetworkVariables: " + config.networkId);
    255         int netId = config.networkId;
    256         if (netId < 0) {
    257             return;
    258         }
    259         /*
    260          * TODO: maybe should have a native method that takes an array of
    261          * variable names and returns an array of values. But we'd still
    262          * be doing a round trip to the supplicant daemon for each variable.
    263          */
    264         String value;
    265 
    266         value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.ssidVarName);
    267         if (!TextUtils.isEmpty(value)) {
    268             if (value.charAt(0) != '"') {
    269                 config.SSID = "\"" + WifiSsid.createFromHex(value).toString() + "\"";
    270                 //TODO: convert a hex string that is not UTF-8 decodable to a P-formatted
    271                 //supplicant string
    272             } else {
    273                 config.SSID = value;
    274             }
    275         } else {
    276             config.SSID = null;
    277         }
    278 
    279         value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.bssidVarName);
    280         if (!TextUtils.isEmpty(value)) {
    281             config.getNetworkSelectionStatus().setNetworkSelectionBSSID(value);
    282         } else {
    283             config.getNetworkSelectionStatus().setNetworkSelectionBSSID(null);
    284         }
    285 
    286         value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.priorityVarName);
    287         config.priority = -1;
    288         if (!TextUtils.isEmpty(value)) {
    289             try {
    290                 config.priority = Integer.parseInt(value);
    291             } catch (NumberFormatException ignore) {
    292             }
    293         }
    294 
    295         value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName);
    296         config.hiddenSSID = false;
    297         if (!TextUtils.isEmpty(value)) {
    298             try {
    299                 config.hiddenSSID = Integer.parseInt(value) != 0;
    300             } catch (NumberFormatException ignore) {
    301             }
    302         }
    303 
    304         value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.pmfVarName);
    305         config.requirePMF = false;
    306         if (!TextUtils.isEmpty(value)) {
    307             try {
    308                 config.requirePMF = Integer.parseInt(value) == STORED_VALUE_FOR_REQUIRE_PMF;
    309             } catch (NumberFormatException ignore) {
    310             }
    311         }
    312 
    313         value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName);
    314         config.wepTxKeyIndex = -1;
    315         if (!TextUtils.isEmpty(value)) {
    316             try {
    317                 config.wepTxKeyIndex = Integer.parseInt(value);
    318             } catch (NumberFormatException ignore) {
    319             }
    320         }
    321 
    322         for (int i = 0; i < 4; i++) {
    323             value = mWifiNative.getNetworkVariable(netId,
    324                     WifiConfiguration.wepKeyVarNames[i]);
    325             if (!TextUtils.isEmpty(value)) {
    326                 config.wepKeys[i] = value;
    327             } else {
    328                 config.wepKeys[i] = null;
    329             }
    330         }
    331 
    332         value = mWifiNative.getNetworkVariable(netId, WifiConfiguration.pskVarName);
    333         if (!TextUtils.isEmpty(value)) {
    334             config.preSharedKey = value;
    335         } else {
    336             config.preSharedKey = null;
    337         }
    338 
    339         readNetworkBitsetVariable(config.networkId, config.allowedProtocols,
    340                 WifiConfiguration.Protocol.varName, WifiConfiguration.Protocol.strings);
    341 
    342         readNetworkBitsetVariable(config.networkId, config.allowedKeyManagement,
    343                 WifiConfiguration.KeyMgmt.varName, WifiConfiguration.KeyMgmt.strings);
    344 
    345         readNetworkBitsetVariable(config.networkId, config.allowedAuthAlgorithms,
    346                 WifiConfiguration.AuthAlgorithm.varName, WifiConfiguration.AuthAlgorithm.strings);
    347 
    348         readNetworkBitsetVariable(config.networkId, config.allowedPairwiseCiphers,
    349                 WifiConfiguration.PairwiseCipher.varName, WifiConfiguration.PairwiseCipher.strings);
    350 
    351         readNetworkBitsetVariable(config.networkId, config.allowedGroupCiphers,
    352                 WifiConfiguration.GroupCipher.varName, WifiConfiguration.GroupCipher.strings);
    353 
    354         if (config.enterpriseConfig == null) {
    355             config.enterpriseConfig = new WifiEnterpriseConfig();
    356         }
    357         config.enterpriseConfig.loadFromSupplicant(new SupplicantLoader(netId));
    358     }
    359 
    360     /**
    361      * Load all the configured networks from wpa_supplicant.
    362      *
    363      * @param configs       Map of configuration key to configuration objects corresponding to all
    364      *                      the networks.
    365      * @param networkExtras Map of extra configuration parameters stored in wpa_supplicant.conf
    366      * @return Max priority of all the configs.
    367      */
    368     public int loadNetworks(Map<String, WifiConfiguration> configs,
    369             SparseArray<Map<String, String>> networkExtras) {
    370         int lastPriority = 0;
    371         int last_id = -1;
    372         boolean done = false;
    373         while (!done) {
    374             String listStr = mWifiNative.listNetworks(last_id);
    375             if (listStr == null) {
    376                 return lastPriority;
    377             }
    378             String[] lines = listStr.split("\n");
    379             if (mShowNetworks) {
    380                 localLog("loadNetworks:  ");
    381                 for (String net : lines) {
    382                     localLog(net);
    383                 }
    384             }
    385             // Skip the first line, which is a header
    386             for (int i = 1; i < lines.length; i++) {
    387                 String[] result = lines[i].split("\t");
    388                 // network-id | ssid | bssid | flags
    389                 WifiConfiguration config = new WifiConfiguration();
    390                 try {
    391                     config.networkId = Integer.parseInt(result[0]);
    392                     last_id = config.networkId;
    393                 } catch (NumberFormatException e) {
    394                     loge("Failed to read network-id '" + result[0] + "'");
    395                     continue;
    396                 }
    397                 // Ignore the supplicant status, start all networks disabled.
    398                 config.status = WifiConfiguration.Status.DISABLED;
    399                 readNetworkVariables(config);
    400                 // Parse the serialized JSON dictionary in ID_STRING_VAR_NAME once and cache the
    401                 // result for efficiency.
    402                 Map<String, String> extras = mWifiNative.getNetworkExtra(config.networkId,
    403                         ID_STRING_VAR_NAME);
    404                 if (extras == null) {
    405                     extras = new HashMap<String, String>();
    406                     // If ID_STRING_VAR_NAME did not contain a dictionary, assume that it contains
    407                     // just a quoted FQDN. This is the legacy format that was used in Marshmallow.
    408                     final String fqdn = Utils.unquote(mWifiNative.getNetworkVariable(
    409                             config.networkId, ID_STRING_VAR_NAME));
    410                     if (fqdn != null) {
    411                         extras.put(ID_STRING_KEY_FQDN, fqdn);
    412                         config.FQDN = fqdn;
    413                         // Mark the configuration as a Hotspot 2.0 network.
    414                         config.providerFriendlyName = "";
    415                     }
    416                 }
    417                 networkExtras.put(config.networkId, extras);
    418 
    419                 if (config.priority > lastPriority) {
    420                     lastPriority = config.priority;
    421                 }
    422                 config.setIpAssignment(IpAssignment.DHCP);
    423                 config.setProxySettings(ProxySettings.NONE);
    424                 if (!WifiServiceImpl.isValid(config)) {
    425                     if (mShowNetworks) {
    426                         localLog("Ignoring network " + config.networkId + " because configuration "
    427                                 + "loaded from wpa_supplicant.conf is not valid.");
    428                     }
    429                     continue;
    430                 }
    431                 // The configKey is explicitly stored in wpa_supplicant.conf, because config does
    432                 // not contain sufficient information to compute it at this point.
    433                 String configKey = extras.get(ID_STRING_KEY_CONFIG_KEY);
    434                 if (configKey == null) {
    435                     // Handle the legacy case where the configKey is not stored in
    436                     // wpa_supplicant.conf but can be computed straight away.
    437                     // Force an update of this legacy network configuration by writing
    438                     // the configKey for this network into wpa_supplicant.conf.
    439                     configKey = config.configKey();
    440                     saveNetworkMetadata(config);
    441                 }
    442                 final WifiConfiguration duplicateConfig = configs.put(configKey, config);
    443                 if (duplicateConfig != null) {
    444                     // The network is already known. Overwrite the duplicate entry.
    445                     if (mShowNetworks) {
    446                         localLog("Replacing duplicate network " + duplicateConfig.networkId
    447                                 + " with " + config.networkId + ".");
    448                     }
    449                     // This can happen after the user manually connected to an AP and tried to use
    450                     // WPS to connect the AP later. In this case, the supplicant will create a new
    451                     // network for the AP although there is an existing network already.
    452                     mWifiNative.removeNetwork(duplicateConfig.networkId);
    453                 }
    454             }
    455             done = (lines.length == 1);
    456         }
    457         return lastPriority;
    458     }
    459 
    460     /**
    461      * Install keys for given enterprise network.
    462      *
    463      * @param existingConfig Existing config corresponding to the network already stored in our
    464      *                       database. This maybe null if it's a new network.
    465      * @param config         Config corresponding to the network.
    466      * @return true if successful, false otherwise.
    467      */
    468     private boolean installKeys(WifiEnterpriseConfig existingConfig, WifiEnterpriseConfig config,
    469             String name) {
    470         boolean ret = true;
    471         String privKeyName = Credentials.USER_PRIVATE_KEY + name;
    472         String userCertName = Credentials.USER_CERTIFICATE + name;
    473         if (config.getClientCertificate() != null) {
    474             byte[] privKeyData = config.getClientPrivateKey().getEncoded();
    475             if (DBG) {
    476                 if (isHardwareBackedKey(config.getClientPrivateKey())) {
    477                     Log.d(TAG, "importing keys " + name + " in hardware backed store");
    478                 } else {
    479                     Log.d(TAG, "importing keys " + name + " in software backed store");
    480                 }
    481             }
    482             ret = mKeyStore.importKey(privKeyName, privKeyData, Process.WIFI_UID,
    483                     KeyStore.FLAG_NONE);
    484 
    485             if (!ret) {
    486                 return ret;
    487             }
    488 
    489             ret = putCertInKeyStore(userCertName, config.getClientCertificate());
    490             if (!ret) {
    491                 // Remove private key installed
    492                 mKeyStore.delete(privKeyName, Process.WIFI_UID);
    493                 return ret;
    494             }
    495         }
    496 
    497         X509Certificate[] caCertificates = config.getCaCertificates();
    498         Set<String> oldCaCertificatesToRemove = new ArraySet<String>();
    499         if (existingConfig != null && existingConfig.getCaCertificateAliases() != null) {
    500             oldCaCertificatesToRemove.addAll(
    501                     Arrays.asList(existingConfig.getCaCertificateAliases()));
    502         }
    503         List<String> caCertificateAliases = null;
    504         if (caCertificates != null) {
    505             caCertificateAliases = new ArrayList<String>();
    506             for (int i = 0; i < caCertificates.length; i++) {
    507                 String alias = caCertificates.length == 1 ? name
    508                         : String.format("%s_%d", name, i);
    509 
    510                 oldCaCertificatesToRemove.remove(alias);
    511                 ret = putCertInKeyStore(Credentials.CA_CERTIFICATE + alias, caCertificates[i]);
    512                 if (!ret) {
    513                     // Remove client key+cert
    514                     if (config.getClientCertificate() != null) {
    515                         mKeyStore.delete(privKeyName, Process.WIFI_UID);
    516                         mKeyStore.delete(userCertName, Process.WIFI_UID);
    517                     }
    518                     // Remove added CA certs.
    519                     for (String addedAlias : caCertificateAliases) {
    520                         mKeyStore.delete(Credentials.CA_CERTIFICATE + addedAlias, Process.WIFI_UID);
    521                     }
    522                     return ret;
    523                 } else {
    524                     caCertificateAliases.add(alias);
    525                 }
    526             }
    527         }
    528         // Remove old CA certs.
    529         for (String oldAlias : oldCaCertificatesToRemove) {
    530             mKeyStore.delete(Credentials.CA_CERTIFICATE + oldAlias, Process.WIFI_UID);
    531         }
    532         // Set alias names
    533         if (config.getClientCertificate() != null) {
    534             config.setClientCertificateAlias(name);
    535             config.resetClientKeyEntry();
    536         }
    537 
    538         if (caCertificates != null) {
    539             config.setCaCertificateAliases(
    540                     caCertificateAliases.toArray(new String[caCertificateAliases.size()]));
    541             config.resetCaCertificate();
    542         }
    543         return ret;
    544     }
    545 
    546     private boolean putCertInKeyStore(String name, Certificate cert) {
    547         try {
    548             byte[] certData = Credentials.convertToPem(cert);
    549             if (DBG) Log.d(TAG, "putting certificate " + name + " in keystore");
    550             return mKeyStore.put(name, certData, Process.WIFI_UID, KeyStore.FLAG_NONE);
    551 
    552         } catch (IOException e1) {
    553             return false;
    554         } catch (CertificateException e2) {
    555             return false;
    556         }
    557     }
    558 
    559     /**
    560      * Remove enterprise keys from the network config.
    561      *
    562      * @param config Config corresponding to the network.
    563      */
    564     private void removeKeys(WifiEnterpriseConfig config) {
    565         String client = config.getClientCertificateAlias();
    566         // a valid client certificate is configured
    567         if (!TextUtils.isEmpty(client)) {
    568             if (DBG) Log.d(TAG, "removing client private key and user cert");
    569             mKeyStore.delete(Credentials.USER_PRIVATE_KEY + client, Process.WIFI_UID);
    570             mKeyStore.delete(Credentials.USER_CERTIFICATE + client, Process.WIFI_UID);
    571         }
    572 
    573         String[] aliases = config.getCaCertificateAliases();
    574         // a valid ca certificate is configured
    575         if (aliases != null) {
    576             for (String ca : aliases) {
    577                 if (!TextUtils.isEmpty(ca)) {
    578                     if (DBG) Log.d(TAG, "removing CA cert: " + ca);
    579                     mKeyStore.delete(Credentials.CA_CERTIFICATE + ca, Process.WIFI_UID);
    580                 }
    581             }
    582         }
    583     }
    584 
    585     /**
    586      * Update the network metadata info stored in wpa_supplicant network extra field.
    587      * @param config Config corresponding to the network.
    588      * @return true if successful, false otherwise.
    589      */
    590     public boolean saveNetworkMetadata(WifiConfiguration config) {
    591         final Map<String, String> metadata = new HashMap<String, String>();
    592         if (config.isPasspoint()) {
    593             metadata.put(ID_STRING_KEY_FQDN, config.FQDN);
    594         }
    595         metadata.put(ID_STRING_KEY_CONFIG_KEY, config.configKey());
    596         metadata.put(ID_STRING_KEY_CREATOR_UID, Integer.toString(config.creatorUid));
    597         if (!mWifiNative.setNetworkExtra(config.networkId, ID_STRING_VAR_NAME, metadata)) {
    598             loge("failed to set id_str: " + metadata.toString());
    599             return false;
    600         }
    601         return true;
    602     }
    603 
    604     /**
    605      * Save an entire network configuration to wpa_supplicant.
    606      *
    607      * @param config Config corresponding to the network.
    608      * @param netId  Net Id of the network.
    609      * @return true if successful, false otherwise.
    610      */
    611     private boolean saveNetwork(WifiConfiguration config, int netId) {
    612         if (config == null) {
    613             return false;
    614         }
    615         if (VDBG) localLog("saveNetwork: " + netId);
    616         if (config.SSID != null && !mWifiNative.setNetworkVariable(
    617                 netId,
    618                 WifiConfiguration.ssidVarName,
    619                 encodeSSID(config.SSID))) {
    620             loge("failed to set SSID: " + config.SSID);
    621             return false;
    622         }
    623         if (!saveNetworkMetadata(config)) {
    624             return false;
    625         }
    626         //set selected BSSID to supplicant
    627         if (config.getNetworkSelectionStatus().getNetworkSelectionBSSID() != null) {
    628             String bssid = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
    629             if (!mWifiNative.setNetworkVariable(netId, WifiConfiguration.bssidVarName, bssid)) {
    630                 loge("failed to set BSSID: " + bssid);
    631                 return false;
    632             }
    633         }
    634         String allowedKeyManagementString =
    635                 makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
    636         if (config.allowedKeyManagement.cardinality() != 0 && !mWifiNative.setNetworkVariable(
    637                 netId,
    638                 WifiConfiguration.KeyMgmt.varName,
    639                 allowedKeyManagementString)) {
    640             loge("failed to set key_mgmt: " + allowedKeyManagementString);
    641             return false;
    642         }
    643         String allowedProtocolsString =
    644                 makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
    645         if (config.allowedProtocols.cardinality() != 0 && !mWifiNative.setNetworkVariable(
    646                 netId,
    647                 WifiConfiguration.Protocol.varName,
    648                 allowedProtocolsString)) {
    649             loge("failed to set proto: " + allowedProtocolsString);
    650             return false;
    651         }
    652         String allowedAuthAlgorithmsString =
    653                 makeString(config.allowedAuthAlgorithms,
    654                         WifiConfiguration.AuthAlgorithm.strings);
    655         if (config.allowedAuthAlgorithms.cardinality() != 0 && !mWifiNative.setNetworkVariable(
    656                 netId,
    657                 WifiConfiguration.AuthAlgorithm.varName,
    658                 allowedAuthAlgorithmsString)) {
    659             loge("failed to set auth_alg: " + allowedAuthAlgorithmsString);
    660             return false;
    661         }
    662         String allowedPairwiseCiphersString = makeString(config.allowedPairwiseCiphers,
    663                 WifiConfiguration.PairwiseCipher.strings);
    664         if (config.allowedPairwiseCiphers.cardinality() != 0 && !mWifiNative.setNetworkVariable(
    665                 netId,
    666                 WifiConfiguration.PairwiseCipher.varName,
    667                 allowedPairwiseCiphersString)) {
    668             loge("failed to set pairwise: " + allowedPairwiseCiphersString);
    669             return false;
    670         }
    671         // Make sure that the string "GTK_NOT_USED" is /not/ transformed - wpa_supplicant
    672         // uses this literal value and not the 'dashed' version.
    673         String allowedGroupCiphersString =
    674                 makeStringWithException(config.allowedGroupCiphers,
    675                         WifiConfiguration.GroupCipher.strings,
    676                         WifiConfiguration.GroupCipher
    677                                 .strings[WifiConfiguration.GroupCipher.GTK_NOT_USED]);
    678         if (config.allowedGroupCiphers.cardinality() != 0 && !mWifiNative.setNetworkVariable(
    679                 netId,
    680                 WifiConfiguration.GroupCipher.varName,
    681                 allowedGroupCiphersString)) {
    682             loge("failed to set group: " + allowedGroupCiphersString);
    683             return false;
    684         }
    685         // Prevent client screw-up by passing in a WifiConfiguration we gave it
    686         // by preventing "*" as a key.
    687         if (config.preSharedKey != null && !config.preSharedKey.equals("*")
    688                 && !mWifiNative.setNetworkVariable(
    689                 netId,
    690                 WifiConfiguration.pskVarName,
    691                 config.preSharedKey)) {
    692             loge("failed to set psk");
    693             return false;
    694         }
    695         boolean hasSetKey = false;
    696         if (config.wepKeys != null) {
    697             for (int i = 0; i < config.wepKeys.length; i++) {
    698                 // Prevent client screw-up by passing in a WifiConfiguration we gave it
    699                 // by preventing "*" as a key.
    700                 if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
    701                     if (!mWifiNative.setNetworkVariable(
    702                             netId,
    703                             WifiConfiguration.wepKeyVarNames[i],
    704                             config.wepKeys[i])) {
    705                         loge("failed to set wep_key" + i + ": " + config.wepKeys[i]);
    706                         return false;
    707                     }
    708                     hasSetKey = true;
    709                 }
    710             }
    711         }
    712         if (hasSetKey) {
    713             if (!mWifiNative.setNetworkVariable(
    714                     netId,
    715                     WifiConfiguration.wepTxKeyIdxVarName,
    716                     Integer.toString(config.wepTxKeyIndex))) {
    717                 loge("failed to set wep_tx_keyidx: " + config.wepTxKeyIndex);
    718                 return false;
    719             }
    720         }
    721         if (!mWifiNative.setNetworkVariable(
    722                 netId,
    723                 WifiConfiguration.priorityVarName,
    724                 Integer.toString(config.priority))) {
    725             loge(config.SSID + ": failed to set priority: " + config.priority);
    726             return false;
    727         }
    728         if (config.hiddenSSID && !mWifiNative.setNetworkVariable(
    729                 netId,
    730                 WifiConfiguration.hiddenSSIDVarName,
    731                 Integer.toString(config.hiddenSSID ? 1 : 0))) {
    732             loge(config.SSID + ": failed to set hiddenSSID: " + config.hiddenSSID);
    733             return false;
    734         }
    735         if (config.requirePMF && !mWifiNative.setNetworkVariable(
    736                 netId,
    737                 WifiConfiguration.pmfVarName,
    738                 Integer.toString(STORED_VALUE_FOR_REQUIRE_PMF))) {
    739             loge(config.SSID + ": failed to set requirePMF: " + config.requirePMF);
    740             return false;
    741         }
    742         if (config.updateIdentifier != null && !mWifiNative.setNetworkVariable(
    743                 netId,
    744                 WifiConfiguration.updateIdentiferVarName,
    745                 config.updateIdentifier)) {
    746             loge(config.SSID + ": failed to set updateIdentifier: " + config.updateIdentifier);
    747             return false;
    748         }
    749         return true;
    750     }
    751 
    752     /**
    753      * Update/Install keys for given enterprise network.
    754      *
    755      * @param config         Config corresponding to the network.
    756      * @param existingConfig Existing config corresponding to the network already stored in our
    757      *                       database. This maybe null if it's a new network.
    758      * @return true if successful, false otherwise.
    759      */
    760     private boolean updateNetworkKeys(WifiConfiguration config, WifiConfiguration existingConfig) {
    761         WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
    762         if (needsKeyStore(enterpriseConfig)) {
    763             try {
    764                 /* config passed may include only fields being updated.
    765                  * In order to generate the key id, fetch uninitialized
    766                  * fields from the currently tracked configuration
    767                  */
    768                 String keyId = config.getKeyIdForCredentials(existingConfig);
    769 
    770                 if (!installKeys(existingConfig != null
    771                         ? existingConfig.enterpriseConfig : null, enterpriseConfig, keyId)) {
    772                     loge(config.SSID + ": failed to install keys");
    773                     return false;
    774                 }
    775             } catch (IllegalStateException e) {
    776                 loge(config.SSID + " invalid config for key installation: " + e.getMessage());
    777                 return false;
    778             }
    779         }
    780         if (!enterpriseConfig.saveToSupplicant(
    781                 new SupplicantSaver(config.networkId, config.SSID))) {
    782             removeKeys(enterpriseConfig);
    783             return false;
    784         }
    785         return true;
    786     }
    787 
    788     /**
    789      * Add or update a network configuration to wpa_supplicant.
    790      *
    791      * @param config         Config corresponding to the network.
    792      * @param existingConfig Existing config corresponding to the network saved in our database.
    793      * @return true if successful, false otherwise.
    794      */
    795     public boolean addOrUpdateNetwork(WifiConfiguration config, WifiConfiguration existingConfig) {
    796         if (config == null) {
    797             return false;
    798         }
    799         if (VDBG) localLog("addOrUpdateNetwork: " + config.networkId);
    800         int netId = config.networkId;
    801         boolean newNetwork = false;
    802         /*
    803          * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
    804          * network configuration. Otherwise, the networkId should
    805          * refer to an existing configuration.
    806          */
    807         if (netId == WifiConfiguration.INVALID_NETWORK_ID) {
    808             newNetwork = true;
    809             netId = mWifiNative.addNetwork();
    810             if (netId < 0) {
    811                 loge("Failed to add a network!");
    812                 return false;
    813             } else {
    814                 logi("addOrUpdateNetwork created netId=" + netId);
    815             }
    816             // Save the new network ID to the config
    817             config.networkId = netId;
    818         }
    819         if (!saveNetwork(config, netId)) {
    820             if (newNetwork) {
    821                 mWifiNative.removeNetwork(netId);
    822                 loge("Failed to set a network variable, removed network: " + netId);
    823             }
    824             return false;
    825         }
    826         if (config.enterpriseConfig != null
    827                 && config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
    828             return updateNetworkKeys(config, existingConfig);
    829         }
    830         // Stage the backup of the SettingsProvider package which backs this up
    831         mBackupManagerProxy.notifyDataChanged();
    832         return true;
    833     }
    834 
    835     /**
    836      * Remove the specified network and save config
    837      *
    838      * @param config Config corresponding to the network.
    839      * @return {@code true} if it succeeds, {@code false} otherwise
    840      */
    841     public boolean removeNetwork(WifiConfiguration config) {
    842         if (config == null) {
    843             return false;
    844         }
    845         if (VDBG) localLog("removeNetwork: " + config.networkId);
    846         if (!mWifiNative.removeNetwork(config.networkId)) {
    847             loge("Remove network in wpa_supplicant failed on " + config.networkId);
    848             return false;
    849         }
    850         // Remove any associated keys
    851         if (config.enterpriseConfig != null) {
    852             removeKeys(config.enterpriseConfig);
    853         }
    854         // Stage the backup of the SettingsProvider package which backs this up
    855         mBackupManagerProxy.notifyDataChanged();
    856         return true;
    857     }
    858 
    859     /**
    860      * Select a network in wpa_supplicant.
    861      *
    862      * @param config Config corresponding to the network.
    863      * @return true if successful, false otherwise.
    864      */
    865     public boolean selectNetwork(WifiConfiguration config, Collection<WifiConfiguration> configs) {
    866         if (config == null) {
    867             return false;
    868         }
    869         if (VDBG) localLog("selectNetwork: " + config.networkId);
    870         if (!mWifiNative.selectNetwork(config.networkId)) {
    871             loge("Select network in wpa_supplicant failed on " + config.networkId);
    872             return false;
    873         }
    874         config.status = Status.ENABLED;
    875         markAllNetworksDisabledExcept(config.networkId, configs);
    876         return true;
    877     }
    878 
    879     /**
    880      * Disable a network in wpa_supplicant.
    881      *
    882      * @param config Config corresponding to the network.
    883      * @return true if successful, false otherwise.
    884      */
    885     boolean disableNetwork(WifiConfiguration config) {
    886         if (config == null) {
    887             return false;
    888         }
    889         if (VDBG) localLog("disableNetwork: " + config.networkId);
    890         if (!mWifiNative.disableNetwork(config.networkId)) {
    891             loge("Disable network in wpa_supplicant failed on " + config.networkId);
    892             return false;
    893         }
    894         config.status = Status.DISABLED;
    895         return true;
    896     }
    897 
    898     /**
    899      * Set priority for a network in wpa_supplicant.
    900      *
    901      * @param config Config corresponding to the network.
    902      * @return true if successful, false otherwise.
    903      */
    904     public boolean setNetworkPriority(WifiConfiguration config, int priority) {
    905         if (config == null) {
    906             return false;
    907         }
    908         if (VDBG) localLog("setNetworkPriority: " + config.networkId);
    909         if (!mWifiNative.setNetworkVariable(config.networkId,
    910                 WifiConfiguration.priorityVarName, Integer.toString(priority))) {
    911             loge("Set priority of network in wpa_supplicant failed on " + config.networkId);
    912             return false;
    913         }
    914         config.priority = priority;
    915         return true;
    916     }
    917 
    918     /**
    919      * Set SSID for a network in wpa_supplicant.
    920      *
    921      * @param config Config corresponding to the network.
    922      * @return true if successful, false otherwise.
    923      */
    924     public boolean setNetworkSSID(WifiConfiguration config, String ssid) {
    925         if (config == null) {
    926             return false;
    927         }
    928         if (VDBG) localLog("setNetworkSSID: " + config.networkId);
    929         if (!mWifiNative.setNetworkVariable(config.networkId, WifiConfiguration.ssidVarName,
    930                 encodeSSID(ssid))) {
    931             loge("Set SSID of network in wpa_supplicant failed on " + config.networkId);
    932             return false;
    933         }
    934         config.SSID = ssid;
    935         return true;
    936     }
    937 
    938     /**
    939      * Set BSSID for a network in wpa_supplicant from network selection.
    940      *
    941      * @param config Config corresponding to the network.
    942      * @param bssid  BSSID to be set.
    943      * @return true if successful, false otherwise.
    944      */
    945     public boolean setNetworkBSSID(WifiConfiguration config, String bssid) {
    946         // Sanity check the config is valid
    947         if (config == null
    948                 || (config.networkId == WifiConfiguration.INVALID_NETWORK_ID
    949                 && config.SSID == null)) {
    950             return false;
    951         }
    952         if (VDBG) localLog("setNetworkBSSID: " + config.networkId);
    953         if (!mWifiNative.setNetworkVariable(config.networkId, WifiConfiguration.bssidVarName,
    954                 bssid)) {
    955             loge("Set BSSID of network in wpa_supplicant failed on " + config.networkId);
    956             return false;
    957         }
    958         config.getNetworkSelectionStatus().setNetworkSelectionBSSID(bssid);
    959         return true;
    960     }
    961 
    962     /**
    963      * Enable/Disable HS20 parameter in wpa_supplicant.
    964      *
    965      * @param enable Enable/Disable the parameter.
    966      */
    967     public void enableHS20(boolean enable) {
    968         mWifiNative.setHs20(enable);
    969     }
    970 
    971     /**
    972      * Disables all the networks in the provided list in wpa_supplicant.
    973      *
    974      * @param configs Collection of configs which needs to be enabled.
    975      * @return true if successful, false otherwise.
    976      */
    977     public boolean disableAllNetworks(Collection<WifiConfiguration> configs) {
    978         if (VDBG) localLog("disableAllNetworks");
    979         boolean networkDisabled = false;
    980         for (WifiConfiguration enabled : configs) {
    981             if (disableNetwork(enabled)) {
    982                 networkDisabled = true;
    983             }
    984         }
    985         saveConfig();
    986         return networkDisabled;
    987     }
    988 
    989     /**
    990      * Save the current configuration to wpa_supplicant.conf.
    991      */
    992     public boolean saveConfig() {
    993         return mWifiNative.saveConfig();
    994     }
    995 
    996     /**
    997      * Read network variables from wpa_supplicant.conf.
    998      *
    999      * @param key The parameter to be parsed.
   1000      * @return Map of corresponding configKey to the value of the param requested.
   1001      */
   1002     public Map<String, String> readNetworkVariablesFromSupplicantFile(String key) {
   1003         Map<String, String> result = new HashMap<>();
   1004         BufferedReader reader = null;
   1005         try {
   1006             reader = new BufferedReader(new FileReader(SUPPLICANT_CONFIG_FILE));
   1007             result = readNetworkVariablesFromReader(reader, key);
   1008         } catch (FileNotFoundException e) {
   1009             if (VDBG) loge("Could not open " + SUPPLICANT_CONFIG_FILE + ", " + e);
   1010         } catch (IOException e) {
   1011             if (VDBG) loge("Could not read " + SUPPLICANT_CONFIG_FILE + ", " + e);
   1012         } finally {
   1013             try {
   1014                 if (reader != null) {
   1015                     reader.close();
   1016                 }
   1017             } catch (IOException e) {
   1018                 if (VDBG) {
   1019                     loge("Could not close reader for " + SUPPLICANT_CONFIG_FILE + ", " + e);
   1020                 }
   1021             }
   1022         }
   1023         return result;
   1024     }
   1025 
   1026     /**
   1027      * Read network variables from a given reader. This method is separate from
   1028      * readNetworkVariablesFromSupplicantFile() for testing.
   1029      *
   1030      * @param reader The reader to read the network variables from.
   1031      * @param key The parameter to be parsed.
   1032      * @return Map of corresponding configKey to the value of the param requested.
   1033      */
   1034     public Map<String, String> readNetworkVariablesFromReader(BufferedReader reader, String key)
   1035             throws IOException {
   1036         Map<String, String> result = new HashMap<>();
   1037         if (VDBG) localLog("readNetworkVariablesFromReader key=" + key);
   1038         boolean found = false;
   1039         String configKey = null;
   1040         String value = null;
   1041         for (String line = reader.readLine(); line != null; line = reader.readLine()) {
   1042             if (line.matches("[ \\t]*network=\\{")) {
   1043                 found = true;
   1044                 configKey = null;
   1045                 value = null;
   1046             } else if (line.matches("[ \\t]*\\}")) {
   1047                 found = false;
   1048                 configKey = null;
   1049                 value = null;
   1050             }
   1051             if (found) {
   1052                 String trimmedLine = line.trim();
   1053                 if (trimmedLine.startsWith(ID_STRING_VAR_NAME + "=")) {
   1054                     try {
   1055                         // Trim the quotes wrapping the id_str value.
   1056                         final String encodedExtras = trimmedLine.substring(
   1057                                 8, trimmedLine.length() -1);
   1058                         final JSONObject json =
   1059                                 new JSONObject(URLDecoder.decode(encodedExtras, "UTF-8"));
   1060                         if (json.has(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY)) {
   1061                             final Object configKeyFromJson =
   1062                                     json.get(WifiConfigStore.ID_STRING_KEY_CONFIG_KEY);
   1063                             if (configKeyFromJson instanceof String) {
   1064                                 configKey = (String) configKeyFromJson;
   1065                             }
   1066                         }
   1067                     } catch (JSONException e) {
   1068                         if (VDBG) {
   1069                             loge("Could not get "+ WifiConfigStore.ID_STRING_KEY_CONFIG_KEY
   1070                                     + ", " + e);
   1071                         }
   1072                     }
   1073                 }
   1074                 if (trimmedLine.startsWith(key + "=")) {
   1075                     value = trimmedLine.substring(key.length() + 1);
   1076                 }
   1077                 if (configKey != null && value != null) {
   1078                     result.put(configKey, value);
   1079                 }
   1080             }
   1081         }
   1082         return result;
   1083     }
   1084 
   1085     /**
   1086      * Resets all sim networks from the provided network list.
   1087      *
   1088      * @param configs List of all the networks.
   1089      */
   1090     public void resetSimNetworks(Collection<WifiConfiguration> configs) {
   1091         if (VDBG) localLog("resetSimNetworks");
   1092         for (WifiConfiguration config : configs) {
   1093             if (TelephonyUtil.isSimConfig(config)) {
   1094                 String currentIdentity = TelephonyUtil.getSimIdentity(mContext,
   1095                         config.enterpriseConfig.getEapMethod());
   1096                 String supplicantIdentity =
   1097                         mWifiNative.getNetworkVariable(config.networkId, "identity");
   1098                 if(supplicantIdentity != null) {
   1099                     supplicantIdentity = removeDoubleQuotes(supplicantIdentity);
   1100                 }
   1101                 if (currentIdentity == null || !currentIdentity.equals(supplicantIdentity)) {
   1102                     // Identity differs so update the identity
   1103                     mWifiNative.setNetworkVariable(config.networkId,
   1104                             WifiEnterpriseConfig.IDENTITY_KEY, WifiEnterpriseConfig.EMPTY_VALUE);
   1105                     // This configuration may have cached Pseudonym IDs; lets remove them
   1106                     mWifiNative.setNetworkVariable(config.networkId,
   1107                             WifiEnterpriseConfig.ANON_IDENTITY_KEY,
   1108                             WifiEnterpriseConfig.EMPTY_VALUE);
   1109                 }
   1110                 // Update the loaded config
   1111                 config.enterpriseConfig.setIdentity(currentIdentity);
   1112                 config.enterpriseConfig.setAnonymousIdentity("");
   1113             }
   1114         }
   1115     }
   1116 
   1117     /**
   1118      * Clear BSSID blacklist in wpa_supplicant.
   1119      */
   1120     public void clearBssidBlacklist() {
   1121         if (VDBG) localLog("clearBlacklist");
   1122         mBssidBlacklist.clear();
   1123         mWifiNative.clearBlacklist();
   1124         mWifiNative.setBssidBlacklist(null);
   1125     }
   1126 
   1127     /**
   1128      * Add a BSSID to the blacklist.
   1129      *
   1130      * @param bssid bssid to be added.
   1131      */
   1132     public void blackListBssid(String bssid) {
   1133         if (bssid == null) {
   1134             return;
   1135         }
   1136         if (VDBG) localLog("blackListBssid: " + bssid);
   1137         mBssidBlacklist.add(bssid);
   1138         // Blacklist at wpa_supplicant
   1139         mWifiNative.addToBlacklist(bssid);
   1140         // Blacklist at firmware
   1141         String[] list = mBssidBlacklist.toArray(new String[mBssidBlacklist.size()]);
   1142         mWifiNative.setBssidBlacklist(list);
   1143     }
   1144 
   1145     /**
   1146      * Checks if the provided bssid is blacklisted or not.
   1147      *
   1148      * @param bssid bssid to be checked.
   1149      * @return true if present, false otherwise.
   1150      */
   1151     public boolean isBssidBlacklisted(String bssid) {
   1152         return mBssidBlacklist.contains(bssid);
   1153     }
   1154 
   1155     /* Mark all networks except specified netId as disabled */
   1156     private void markAllNetworksDisabledExcept(int netId, Collection<WifiConfiguration> configs) {
   1157         for (WifiConfiguration config : configs) {
   1158             if (config != null && config.networkId != netId) {
   1159                 if (config.status != Status.DISABLED) {
   1160                     config.status = Status.DISABLED;
   1161                 }
   1162             }
   1163         }
   1164     }
   1165 
   1166     private void markAllNetworksDisabled(Collection<WifiConfiguration> configs) {
   1167         markAllNetworksDisabledExcept(WifiConfiguration.INVALID_NETWORK_ID, configs);
   1168     }
   1169 
   1170     /**
   1171      * Start WPS pin method configuration with pin obtained
   1172      * from the access point
   1173      *
   1174      * @param config WPS configuration
   1175      * @return Wps result containing status and pin
   1176      */
   1177     public WpsResult startWpsWithPinFromAccessPoint(WpsInfo config,
   1178             Collection<WifiConfiguration> configs) {
   1179         WpsResult result = new WpsResult();
   1180         if (mWifiNative.startWpsRegistrar(config.BSSID, config.pin)) {
   1181             /* WPS leaves all networks disabled */
   1182             markAllNetworksDisabled(configs);
   1183             result.status = WpsResult.Status.SUCCESS;
   1184         } else {
   1185             loge("Failed to start WPS pin method configuration");
   1186             result.status = WpsResult.Status.FAILURE;
   1187         }
   1188         return result;
   1189     }
   1190 
   1191     /**
   1192      * Start WPS pin method configuration with obtained
   1193      * from the device
   1194      *
   1195      * @return WpsResult indicating status and pin
   1196      */
   1197     public WpsResult startWpsWithPinFromDevice(WpsInfo config,
   1198             Collection<WifiConfiguration> configs) {
   1199         WpsResult result = new WpsResult();
   1200         result.pin = mWifiNative.startWpsPinDisplay(config.BSSID);
   1201         /* WPS leaves all networks disabled */
   1202         if (!TextUtils.isEmpty(result.pin)) {
   1203             markAllNetworksDisabled(configs);
   1204             result.status = WpsResult.Status.SUCCESS;
   1205         } else {
   1206             loge("Failed to start WPS pin method configuration");
   1207             result.status = WpsResult.Status.FAILURE;
   1208         }
   1209         return result;
   1210     }
   1211 
   1212     /**
   1213      * Start WPS push button configuration
   1214      *
   1215      * @param config WPS configuration
   1216      * @return WpsResult indicating status and pin
   1217      */
   1218     public WpsResult startWpsPbc(WpsInfo config,
   1219             Collection<WifiConfiguration> configs) {
   1220         WpsResult result = new WpsResult();
   1221         if (mWifiNative.startWpsPbc(config.BSSID)) {
   1222             /* WPS leaves all networks disabled */
   1223             markAllNetworksDisabled(configs);
   1224             result.status = WpsResult.Status.SUCCESS;
   1225         } else {
   1226             loge("Failed to start WPS push button configuration");
   1227             result.status = WpsResult.Status.FAILURE;
   1228         }
   1229         return result;
   1230     }
   1231 
   1232     protected void logd(String s) {
   1233         Log.d(TAG, s);
   1234     }
   1235 
   1236     protected void logi(String s) {
   1237         Log.i(TAG, s);
   1238     }
   1239 
   1240     protected void loge(String s) {
   1241         loge(s, false);
   1242     }
   1243 
   1244     protected void loge(String s, boolean stack) {
   1245         if (stack) {
   1246             Log.e(TAG, s + " stack:" + Thread.currentThread().getStackTrace()[2].getMethodName()
   1247                     + " - " + Thread.currentThread().getStackTrace()[3].getMethodName()
   1248                     + " - " + Thread.currentThread().getStackTrace()[4].getMethodName()
   1249                     + " - " + Thread.currentThread().getStackTrace()[5].getMethodName());
   1250         } else {
   1251             Log.e(TAG, s);
   1252         }
   1253     }
   1254 
   1255     protected void log(String s) {
   1256         Log.d(TAG, s);
   1257     }
   1258 
   1259     private void localLog(String s) {
   1260         if (mLocalLog != null) {
   1261             mLocalLog.log(TAG + ": " + s);
   1262         }
   1263     }
   1264 
   1265     private void localLogAndLogcat(String s) {
   1266         localLog(s);
   1267         Log.d(TAG, s);
   1268     }
   1269 
   1270     private class SupplicantSaver implements WifiEnterpriseConfig.SupplicantSaver {
   1271         private final int mNetId;
   1272         private final String mSetterSSID;
   1273 
   1274         SupplicantSaver(int netId, String setterSSID) {
   1275             mNetId = netId;
   1276             mSetterSSID = setterSSID;
   1277         }
   1278 
   1279         @Override
   1280         public boolean saveValue(String key, String value) {
   1281             if (key.equals(WifiEnterpriseConfig.PASSWORD_KEY)
   1282                     && value != null && value.equals("*")) {
   1283                 // No need to try to set an obfuscated password, which will fail
   1284                 return true;
   1285             }
   1286             if (key.equals(WifiEnterpriseConfig.REALM_KEY)
   1287                     || key.equals(WifiEnterpriseConfig.PLMN_KEY)) {
   1288                 // No need to save realm or PLMN in supplicant
   1289                 return true;
   1290             }
   1291             // TODO: We need a way to clear values in wpa_supplicant as opposed to
   1292             // mapping unset values to empty strings.
   1293             if (value == null) {
   1294                 value = "\"\"";
   1295             }
   1296             if (!mWifiNative.setNetworkVariable(mNetId, key, value)) {
   1297                 loge(mSetterSSID + ": failed to set " + key + ": " + value);
   1298                 return false;
   1299             }
   1300             return true;
   1301         }
   1302     }
   1303 
   1304     private class SupplicantLoader implements WifiEnterpriseConfig.SupplicantLoader {
   1305         private final int mNetId;
   1306 
   1307         SupplicantLoader(int netId) {
   1308             mNetId = netId;
   1309         }
   1310 
   1311         @Override
   1312         public String loadValue(String key) {
   1313             String value = mWifiNative.getNetworkVariable(mNetId, key);
   1314             if (!TextUtils.isEmpty(value)) {
   1315                 if (!enterpriseConfigKeyShouldBeQuoted(key)) {
   1316                     value = removeDoubleQuotes(value);
   1317                 }
   1318                 return value;
   1319             } else {
   1320                 return null;
   1321             }
   1322         }
   1323 
   1324         /**
   1325          * Returns true if a particular config key needs to be quoted when passed to the supplicant.
   1326          */
   1327         private boolean enterpriseConfigKeyShouldBeQuoted(String key) {
   1328             switch (key) {
   1329                 case WifiEnterpriseConfig.EAP_KEY:
   1330                 case WifiEnterpriseConfig.ENGINE_KEY:
   1331                     return false;
   1332                 default:
   1333                     return true;
   1334             }
   1335         }
   1336     }
   1337 
   1338     // TODO(rpius): Remove this.
   1339     private class WpaConfigFileObserver extends FileObserver {
   1340 
   1341         WpaConfigFileObserver() {
   1342             super(SUPPLICANT_CONFIG_FILE, CLOSE_WRITE);
   1343         }
   1344 
   1345         @Override
   1346         public void onEvent(int event, String path) {
   1347             if (event == CLOSE_WRITE) {
   1348                 File file = new File(SUPPLICANT_CONFIG_FILE);
   1349                 if (VDBG) localLog("wpa_supplicant.conf changed; new size = " + file.length());
   1350             }
   1351         }
   1352     }
   1353 }
   1354