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