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