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