WifiConfigStore.java revision e590373ea71251cfffc8f22f011e2e6335dce716
1/*
2 * Copyright (C) 2010 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 android.net.wifi;
18
19import android.content.Context;
20import android.content.Intent;
21import android.net.DhcpInfoInternal;
22import android.net.LinkAddress;
23import android.net.LinkProperties;
24import android.net.NetworkUtils;
25import android.net.ProxyProperties;
26import android.net.wifi.WifiConfiguration.IpAssignment;
27import android.net.wifi.WifiConfiguration.KeyMgmt;
28import android.net.wifi.WifiConfiguration.ProxySettings;
29import android.net.wifi.WifiConfiguration.Status;
30import android.net.wifi.NetworkUpdateResult;
31import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
32import android.os.Environment;
33import android.text.TextUtils;
34import android.util.Log;
35
36import java.io.BufferedInputStream;
37import java.io.BufferedOutputStream;
38import java.io.DataInputStream;
39import java.io.DataOutputStream;
40import java.io.EOFException;
41import java.io.FileInputStream;
42import java.io.FileOutputStream;
43import java.io.IOException;
44import java.net.InetAddress;
45import java.net.UnknownHostException;
46import java.util.ArrayList;
47import java.util.BitSet;
48import java.util.Collection;
49import java.util.HashMap;
50import java.util.Iterator;
51import java.util.List;
52
53/**
54 * This class provides the API to manage configured
55 * wifi networks. The API is not thread safe is being
56 * used only from WifiStateMachine.
57 *
58 * It deals with the following
59 * - Add/update/remove a WifiConfiguration
60 *   The configuration contains two types of information.
61 *     = IP and proxy configuration that is handled by WifiConfigStore and
62 *       is saved to disk on any change.
63 *
64 *       The format of configuration file is as follows:
65 *       <version>
66 *       <netA_key1><netA_value1><netA_key2><netA_value2>...<EOS>
67 *       <netB_key1><netB_value1><netB_key2><netB_value2>...<EOS>
68 *       ..
69 *
70 *       (key, value) pairs for a given network are grouped together and can
71 *       be in any order. A EOS at the end of a set of (key, value) pairs
72 *       indicates that the next set of (key, value) pairs are for a new
73 *       network. A network is identified by a unique ID_KEY. If there is no
74 *       ID_KEY in the (key, value) pairs, the data is discarded.
75 *
76 *       An invalid version on read would result in discarding the contents of
77 *       the file. On the next write, the latest version is written to file.
78 *
79 *       Any failures during read or write to the configuration file are ignored
80 *       without reporting to the user since the likelihood of these errors are
81 *       low and the impact on connectivity is low.
82 *
83 *     = SSID & security details that is pushed to the supplicant.
84 *       supplicant saves these details to the disk on calling
85 *       saveConfigCommand().
86 *
87 *       We have two kinds of APIs exposed:
88 *        > public API calls that provide fine grained control
89 *          - enableNetwork, disableNetwork, addOrUpdateNetwork(),
90 *          removeNetwork(). For these calls, the config is not persisted
91 *          to the disk. (TODO: deprecate these calls in WifiManager)
92 *        > The new API calls - selectNetwork(), saveNetwork() & forgetNetwork().
93 *          These calls persist the supplicant config to disk.
94 *
95 * - Maintain a list of configured networks for quick access
96 *
97 */
98class WifiConfigStore {
99
100    private static Context sContext;
101    private static final String TAG = "WifiConfigStore";
102
103    /* configured networks with network id as the key */
104    private static HashMap<Integer, WifiConfiguration> sConfiguredNetworks =
105            new HashMap<Integer, WifiConfiguration>();
106
107    /* A network id is a unique identifier for a network configured in the
108     * supplicant. Network ids are generated when the supplicant reads
109     * the configuration file at start and can thus change for networks.
110     * We store the IP configuration for networks along with a unique id
111     * that is generated from SSID and security type of the network. A mapping
112     * from the generated unique id to network id of the network is needed to
113     * map supplicant config to IP configuration. */
114    private static HashMap<Integer, Integer> sNetworkIds =
115            new HashMap<Integer, Integer>();
116
117    /* Tracks the highest priority of configured networks */
118    private static int sLastPriority = -1;
119
120    private static final String ipConfigFile = Environment.getDataDirectory() +
121            "/misc/wifi/ipconfig.txt";
122
123    private static final int IPCONFIG_FILE_VERSION = 1;
124
125    /* IP and proxy configuration keys */
126    private static final String ID_KEY = "id";
127    private static final String IP_ASSIGNMENT_KEY = "ipAssignment";
128    private static final String LINK_ADDRESS_KEY = "linkAddress";
129    private static final String GATEWAY_KEY = "gateway";
130    private static final String DNS_KEY = "dns";
131    private static final String PROXY_SETTINGS_KEY = "proxySettings";
132    private static final String PROXY_HOST_KEY = "proxyHost";
133    private static final String PROXY_PORT_KEY = "proxyPort";
134    private static final String EXCLUSION_LIST_KEY = "exclusionList";
135    private static final String EOS = "eos";
136
137    /**
138     * Initialize context, fetch the list of configured networks
139     * and enable all stored networks in supplicant.
140     */
141    static void initialize(Context context) {
142        Log.d(TAG, "Loading config and enabling all networks");
143        sContext = context;
144        loadConfiguredNetworks();
145        enableAllNetworks();
146    }
147
148    /**
149     * Fetch the list of currently configured networks
150     * @return List of networks
151     */
152    static List<WifiConfiguration> getConfiguredNetworks() {
153        List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
154        synchronized (sConfiguredNetworks) {
155            for(WifiConfiguration config : sConfiguredNetworks.values()) {
156                networks.add(new WifiConfiguration(config));
157            }
158        }
159        return networks;
160    }
161
162    /**
163     * enable all networks and save config. This will be a no-op if the list
164     * of configured networks indicates all networks as being enabled
165     */
166    static void enableAllNetworks() {
167        boolean networkEnabledStateChanged = false;
168        synchronized (sConfiguredNetworks) {
169            for(WifiConfiguration config : sConfiguredNetworks.values()) {
170                if(config != null && config.status == Status.DISABLED) {
171                    if(WifiNative.enableNetworkCommand(config.networkId, false)) {
172                        networkEnabledStateChanged = true;
173                        config.status = Status.ENABLED;
174                    } else {
175                        Log.e(TAG, "Enable network failed on " + config.networkId);
176                    }
177                }
178            }
179        }
180
181        if (networkEnabledStateChanged) {
182            WifiNative.saveConfigCommand();
183            sendConfiguredNetworksChangedBroadcast();
184        }
185    }
186
187    /**
188     * Selects the specified network config for connection. This involves
189     * addition/update of the specified config, updating the priority of
190     * all the networks and enabling the given network while disabling others.
191     *
192     * Selecting a network will leave the other networks disabled and
193     * a call to enableAllNetworks() needs to be issued upon a connection
194     * or a failure event from supplicant
195     *
196     * @param config The configuration details in WifiConfiguration
197     */
198    static void selectNetwork(WifiConfiguration config) {
199        if (config != null) {
200            NetworkUpdateResult result = addOrUpdateNetworkNative(config);
201            int netId = result.getNetworkId();
202            if (netId != INVALID_NETWORK_ID) {
203                selectNetwork(netId);
204            } else {
205                Log.e(TAG, "Failed to update network " + config);
206            }
207        }
208    }
209
210    /**
211     * Selects the specified network for connection. This involves
212     * updating the priority of all the networks and enabling the given
213     * network while disabling others.
214     *
215     * Selecting a network will leave the other networks disabled and
216     * a call to enableAllNetworks() needs to be issued upon a connection
217     * or a failure event from supplicant
218     *
219     * @param netId network to select for connection
220     */
221    static void selectNetwork(int netId) {
222        // Reset the priority of each network at start or if it goes too high.
223        if (sLastPriority == -1 || sLastPriority > 1000000) {
224            synchronized (sConfiguredNetworks) {
225                for(WifiConfiguration config : sConfiguredNetworks.values()) {
226                    if (config.networkId != INVALID_NETWORK_ID) {
227                        config.priority = 0;
228                        addOrUpdateNetworkNative(config);
229                    }
230                }
231            }
232            sLastPriority = 0;
233        }
234
235        // Set to the highest priority and save the configuration.
236        WifiConfiguration config = new WifiConfiguration();
237        config.networkId = netId;
238        config.priority = ++sLastPriority;
239
240        addOrUpdateNetworkNative(config);
241        WifiNative.saveConfigCommand();
242
243        /* Enable the given network while disabling all other networks */
244        enableNetworkWithoutBroadcast(netId, true);
245
246       /* Avoid saving the config & sending a broadcast to prevent settings
247        * from displaying a disabled list of networks */
248    }
249
250    /**
251     * Add/update the specified configuration and save config
252     *
253     * @param config WifiConfiguration to be saved
254     */
255    static NetworkUpdateResult saveNetwork(WifiConfiguration config) {
256        boolean newNetwork = (config.networkId == INVALID_NETWORK_ID);
257        NetworkUpdateResult result = addOrUpdateNetworkNative(config);
258        int netId = result.getNetworkId();
259        /* enable a new network */
260        if (newNetwork && netId != INVALID_NETWORK_ID) {
261            WifiNative.enableNetworkCommand(netId, false);
262            synchronized (sConfiguredNetworks) {
263                sConfiguredNetworks.get(netId).status = Status.ENABLED;
264            }
265        }
266        WifiNative.saveConfigCommand();
267        sendConfiguredNetworksChangedBroadcast();
268        return result;
269    }
270
271    /**
272     * Forget the specified network and save config
273     *
274     * @param netId network to forget
275     */
276    static void forgetNetwork(int netId) {
277        if (WifiNative.removeNetworkCommand(netId)) {
278            WifiNative.saveConfigCommand();
279            synchronized (sConfiguredNetworks) {
280                sConfiguredNetworks.remove(netId);
281            }
282            writeIpAndProxyConfigurations();
283            sendConfiguredNetworksChangedBroadcast();
284        } else {
285            Log.e(TAG, "Failed to remove network " + netId);
286        }
287    }
288
289    /**
290     * Add/update a network. Note that there is no saveConfig operation.
291     * This function is retained for compatibility with the public
292     * API. The more powerful saveNetwork() is used by the
293     * state machine
294     *
295     * @param config wifi configuration to add/update
296     */
297    static int addOrUpdateNetwork(WifiConfiguration config) {
298        NetworkUpdateResult result = addOrUpdateNetworkNative(config);
299        sendConfiguredNetworksChangedBroadcast();
300        return result.getNetworkId();
301    }
302
303    /**
304     * Remove a network. Note that there is no saveConfig operation.
305     * This function is retained for compatibility with the public
306     * API. The more powerful forgetNetwork() is used by the
307     * state machine for network removal
308     *
309     * @param netId network to be removed
310     */
311    static boolean removeNetwork(int netId) {
312        boolean ret = WifiNative.removeNetworkCommand(netId);
313        synchronized (sConfiguredNetworks) {
314            if (ret) sConfiguredNetworks.remove(netId);
315        }
316        sendConfiguredNetworksChangedBroadcast();
317        return ret;
318    }
319
320    /**
321     * Enable a network. Note that there is no saveConfig operation.
322     * This function is retained for compatibility with the public
323     * API. The more powerful selectNetwork()/saveNetwork() is used by the
324     * state machine for connecting to a network
325     *
326     * @param netId network to be removed
327     */
328    static boolean enableNetwork(int netId, boolean disableOthers) {
329        boolean ret = enableNetworkWithoutBroadcast(netId, disableOthers);
330        sendConfiguredNetworksChangedBroadcast();
331        return ret;
332    }
333
334    static boolean enableNetworkWithoutBroadcast(int netId, boolean disableOthers) {
335        boolean ret = WifiNative.enableNetworkCommand(netId, disableOthers);
336
337        synchronized (sConfiguredNetworks) {
338            WifiConfiguration config = sConfiguredNetworks.get(netId);
339            if (config != null) config.status = Status.ENABLED;
340        }
341
342        if (disableOthers) {
343            markAllNetworksDisabledExcept(netId);
344        }
345        return ret;
346    }
347
348    /**
349     * Disable a network. Note that there is no saveConfig operation.
350     * @param netId network to be disabled
351     */
352    static boolean disableNetwork(int netId) {
353        boolean ret = WifiNative.disableNetworkCommand(netId);
354        synchronized (sConfiguredNetworks) {
355            WifiConfiguration config = sConfiguredNetworks.get(netId);
356            if (config != null) config.status = Status.DISABLED;
357        }
358        sendConfiguredNetworksChangedBroadcast();
359        return ret;
360    }
361
362    /**
363     * Save the configured networks in supplicant to disk
364     */
365    static boolean saveConfig() {
366        return WifiNative.saveConfigCommand();
367    }
368
369    /**
370     * Start WPS pin method configuration with pin obtained
371     * from the access point
372     */
373    static WpsResult startWpsWithPinFromAccessPoint(WpsConfiguration config) {
374        WpsResult result = new WpsResult();
375        if (WifiNative.startWpsWithPinFromAccessPointCommand(config.BSSID, config.pin)) {
376            /* WPS leaves all networks disabled */
377            markAllNetworksDisabled();
378            result.status = WpsResult.Status.SUCCESS;
379        } else {
380            Log.e(TAG, "Failed to start WPS pin method configuration");
381            result.status = WpsResult.Status.FAILURE;
382        }
383        return result;
384    }
385
386    /**
387     * Start WPS pin method configuration with pin obtained
388     * from the device
389     * @return WpsResult indicating status and pin
390     */
391    static WpsResult startWpsWithPinFromDevice(WpsConfiguration config) {
392        WpsResult result = new WpsResult();
393        result.pin = WifiNative.startWpsWithPinFromDeviceCommand(config.BSSID);
394        /* WPS leaves all networks disabled */
395        if (!TextUtils.isEmpty(result.pin)) {
396            markAllNetworksDisabled();
397            result.status = WpsResult.Status.SUCCESS;
398        } else {
399            Log.e(TAG, "Failed to start WPS pin method configuration");
400            result.status = WpsResult.Status.FAILURE;
401        }
402        return result;
403    }
404
405    /**
406     * Start WPS push button configuration
407     */
408    static WpsResult startWpsPbc(WpsConfiguration config) {
409        WpsResult result = new WpsResult();
410        if (WifiNative.startWpsPbcCommand(config.BSSID)) {
411            /* WPS leaves all networks disabled */
412            markAllNetworksDisabled();
413            result.status = WpsResult.Status.SUCCESS;
414        } else {
415            Log.e(TAG, "Failed to start WPS push button configuration");
416            result.status = WpsResult.Status.FAILURE;
417        }
418        return result;
419    }
420
421    /**
422     * Fetch the link properties for a given network id
423     */
424    static LinkProperties getLinkProperties(int netId) {
425        synchronized (sConfiguredNetworks) {
426            WifiConfiguration config = sConfiguredNetworks.get(netId);
427            if (config != null) return new LinkProperties(config.linkProperties);
428        }
429        return null;
430    }
431
432    /**
433     * get IP configuration for a given network id
434     * TODO: We cannot handle IPv6 addresses for configuration
435     *       right now until NetworkUtils is fixed. When we do
436     *       that, we should remove handling DhcpInfo and move
437     *       to using LinkProperties
438     */
439    static DhcpInfoInternal getIpConfiguration(int netId) {
440        DhcpInfoInternal dhcpInfoInternal = new DhcpInfoInternal();
441        LinkProperties linkProperties = getLinkProperties(netId);
442
443        if (linkProperties != null) {
444            Iterator<LinkAddress> iter = linkProperties.getLinkAddresses().iterator();
445            if (iter.hasNext()) {
446                LinkAddress linkAddress = iter.next();
447                dhcpInfoInternal.ipAddress = linkAddress.getAddress().getHostAddress();
448                Iterator<InetAddress>gateways = linkProperties.getGateways().iterator();
449                if (gateways.hasNext()) {
450                    dhcpInfoInternal.gateway = gateways.next().getHostAddress();
451                }
452                dhcpInfoInternal.prefixLength = linkAddress.getNetworkPrefixLength();
453                Iterator<InetAddress> dnsIterator = linkProperties.getDnses().iterator();
454                dhcpInfoInternal.dns1 = dnsIterator.next().getHostAddress();
455                if (dnsIterator.hasNext()) {
456                    dhcpInfoInternal.dns2 = dnsIterator.next().getHostAddress();
457                }
458            }
459        }
460        return dhcpInfoInternal;
461    }
462
463    /**
464     * Fetch the proxy properties for a given network id
465     */
466    static ProxyProperties getProxyProperties(int netId) {
467        LinkProperties linkProperties = getLinkProperties(netId);
468        if (linkProperties != null) {
469            return new ProxyProperties(linkProperties.getHttpProxy());
470        }
471        return null;
472    }
473
474    /**
475     * Return if the specified network is using static IP
476     */
477    static boolean isUsingStaticIp(int netId) {
478        synchronized (sConfiguredNetworks) {
479            WifiConfiguration config = sConfiguredNetworks.get(netId);
480            if (config != null && config.ipAssignment == IpAssignment.STATIC) {
481                return true;
482            }
483        }
484        return false;
485    }
486
487    private static void sendConfiguredNetworksChangedBroadcast() {
488        Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
489        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
490        sContext.sendBroadcast(intent);
491    }
492
493    static void loadConfiguredNetworks() {
494        String listStr = WifiNative.listNetworksCommand();
495        sLastPriority = 0;
496
497        synchronized (sConfiguredNetworks) {
498            sConfiguredNetworks.clear();
499            sNetworkIds.clear();
500
501            if (listStr == null)
502                return;
503
504            String[] lines = listStr.split("\n");
505            // Skip the first line, which is a header
506            for (int i = 1; i < lines.length; i++) {
507                String[] result = lines[i].split("\t");
508                // network-id | ssid | bssid | flags
509                WifiConfiguration config = new WifiConfiguration();
510                try {
511                    config.networkId = Integer.parseInt(result[0]);
512                } catch(NumberFormatException e) {
513                    continue;
514                }
515                if (result.length > 3) {
516                    if (result[3].indexOf("[CURRENT]") != -1)
517                        config.status = WifiConfiguration.Status.CURRENT;
518                    else if (result[3].indexOf("[DISABLED]") != -1)
519                        config.status = WifiConfiguration.Status.DISABLED;
520                    else
521                        config.status = WifiConfiguration.Status.ENABLED;
522                } else {
523                    config.status = WifiConfiguration.Status.ENABLED;
524                }
525                readNetworkVariables(config);
526                if (config.priority > sLastPriority) {
527                    sLastPriority = config.priority;
528                }
529                sConfiguredNetworks.put(config.networkId, config);
530                sNetworkIds.put(configKey(config), config.networkId);
531            }
532        }
533        readIpAndProxyConfigurations();
534        sendConfiguredNetworksChangedBroadcast();
535    }
536
537    static void updateIpAndProxyFromWpsConfig(int netId, WpsConfiguration wpsConfig) {
538        synchronized (sConfiguredNetworks) {
539            WifiConfiguration config = sConfiguredNetworks.get(netId);
540            if (config != null) {
541                config.ipAssignment = wpsConfig.ipAssignment;
542                config.proxySettings = wpsConfig.proxySettings;
543                config.linkProperties = wpsConfig.linkProperties;
544                writeIpAndProxyConfigurations();
545            }
546        }
547    }
548
549    /* Mark all networks except specified netId as disabled */
550    private static void markAllNetworksDisabledExcept(int netId) {
551        synchronized (sConfiguredNetworks) {
552            for(WifiConfiguration config : sConfiguredNetworks.values()) {
553                if(config != null && config.networkId != netId) {
554                    config.status = Status.DISABLED;
555                }
556            }
557        }
558    }
559
560    private static void markAllNetworksDisabled() {
561        markAllNetworksDisabledExcept(INVALID_NETWORK_ID);
562    }
563
564    private static void writeIpAndProxyConfigurations() {
565
566        DataOutputStream out = null;
567        try {
568            out = new DataOutputStream(new BufferedOutputStream(
569                    new FileOutputStream(ipConfigFile)));
570
571            out.writeInt(IPCONFIG_FILE_VERSION);
572
573            synchronized (sConfiguredNetworks) {
574                for(WifiConfiguration config : sConfiguredNetworks.values()) {
575                    boolean writeToFile = false;
576
577                    try {
578                        LinkProperties linkProperties = config.linkProperties;
579                        switch (config.ipAssignment) {
580                            case STATIC:
581                                out.writeUTF(IP_ASSIGNMENT_KEY);
582                                out.writeUTF(config.ipAssignment.toString());
583                                for (LinkAddress linkAddr : linkProperties.getLinkAddresses()) {
584                                    out.writeUTF(LINK_ADDRESS_KEY);
585                                    out.writeUTF(linkAddr.getAddress().getHostAddress());
586                                    out.writeInt(linkAddr.getNetworkPrefixLength());
587                                }
588                                for (InetAddress gateway : linkProperties.getGateways()) {
589                                    out.writeUTF(GATEWAY_KEY);
590                                    out.writeUTF(gateway.getHostAddress());
591                                }
592                                for (InetAddress inetAddr : linkProperties.getDnses()) {
593                                    out.writeUTF(DNS_KEY);
594                                    out.writeUTF(inetAddr.getHostAddress());
595                                }
596                                writeToFile = true;
597                                break;
598                            case DHCP:
599                                out.writeUTF(IP_ASSIGNMENT_KEY);
600                                out.writeUTF(config.ipAssignment.toString());
601                                writeToFile = true;
602                                break;
603                            case UNASSIGNED:
604                                /* Ignore */
605                                break;
606                            default:
607                                Log.e(TAG, "Ignore invalid ip assignment while writing");
608                                break;
609                        }
610
611                        switch (config.proxySettings) {
612                            case STATIC:
613                                ProxyProperties proxyProperties = linkProperties.getHttpProxy();
614                                String exclusionList = proxyProperties.getExclusionList();
615                                out.writeUTF(PROXY_SETTINGS_KEY);
616                                out.writeUTF(config.proxySettings.toString());
617                                out.writeUTF(PROXY_HOST_KEY);
618                                out.writeUTF(proxyProperties.getHost());
619                                out.writeUTF(PROXY_PORT_KEY);
620                                out.writeInt(proxyProperties.getPort());
621                                out.writeUTF(EXCLUSION_LIST_KEY);
622                                out.writeUTF(exclusionList);
623                                writeToFile = true;
624                                break;
625                            case NONE:
626                                out.writeUTF(PROXY_SETTINGS_KEY);
627                                out.writeUTF(config.proxySettings.toString());
628                                writeToFile = true;
629                                break;
630                            case UNASSIGNED:
631                                /* Ignore */
632                                break;
633                            default:
634                                Log.e(TAG, "Ignore invalid proxy settings while writing");
635                                break;
636                        }
637                        if (writeToFile) {
638                            out.writeUTF(ID_KEY);
639                            out.writeInt(configKey(config));
640                        }
641                    } catch (NullPointerException e) {
642                        Log.e(TAG, "Failure in writing " + config.linkProperties + e);
643                    }
644                    out.writeUTF(EOS);
645                }
646            }
647
648        } catch (IOException e) {
649            Log.e(TAG, "Error writing data file");
650        } finally {
651            if (out != null) {
652                try {
653                    out.close();
654                } catch (Exception e) {}
655            }
656        }
657    }
658
659    private static void readIpAndProxyConfigurations() {
660
661        DataInputStream in = null;
662        try {
663            in = new DataInputStream(new BufferedInputStream(new FileInputStream(
664                    ipConfigFile)));
665
666            if (in.readInt() != IPCONFIG_FILE_VERSION) {
667                Log.e(TAG, "Bad version on IP configuration file, ignore read");
668                return;
669            }
670
671            while (true) {
672                int id = -1;
673                IpAssignment ipAssignment = IpAssignment.UNASSIGNED;
674                ProxySettings proxySettings = ProxySettings.UNASSIGNED;
675                LinkProperties linkProperties = new LinkProperties();
676                String proxyHost = null;
677                int proxyPort = -1;
678                String exclusionList = null;
679                String key;
680
681                do {
682                    key = in.readUTF();
683                    try {
684                        if (key.equals(ID_KEY)) {
685                            id = in.readInt();
686                        } else if (key.equals(IP_ASSIGNMENT_KEY)) {
687                            ipAssignment = IpAssignment.valueOf(in.readUTF());
688                        } else if (key.equals(LINK_ADDRESS_KEY)) {
689                            LinkAddress linkAddr = new LinkAddress(
690                                    NetworkUtils.numericToInetAddress(in.readUTF()), in.readInt());
691                            linkProperties.addLinkAddress(linkAddr);
692                        } else if (key.equals(GATEWAY_KEY)) {
693                            linkProperties.addGateway(
694                                    NetworkUtils.numericToInetAddress(in.readUTF()));
695                        } else if (key.equals(DNS_KEY)) {
696                            linkProperties.addDns(
697                                    NetworkUtils.numericToInetAddress(in.readUTF()));
698                        } else if (key.equals(PROXY_SETTINGS_KEY)) {
699                            proxySettings = ProxySettings.valueOf(in.readUTF());
700                        } else if (key.equals(PROXY_HOST_KEY)) {
701                            proxyHost = in.readUTF();
702                        } else if (key.equals(PROXY_PORT_KEY)) {
703                            proxyPort = in.readInt();
704                        } else if (key.equals(EXCLUSION_LIST_KEY)) {
705                            exclusionList = in.readUTF();
706                        } else if (key.equals(EOS)) {
707                            break;
708                        } else {
709                            Log.e(TAG, "Ignore unknown key " + key + "while reading");
710                        }
711                    } catch (IllegalArgumentException e) {
712                        Log.e(TAG, "Ignore invalid address while reading" + e);
713                    }
714                } while (true);
715
716                if (id != -1) {
717                    synchronized (sConfiguredNetworks) {
718                        WifiConfiguration config = sConfiguredNetworks.get(
719                                sNetworkIds.get(id));
720
721                        if (config == null) {
722                            Log.e(TAG, "configuration found for missing network, ignored");
723                        } else {
724                            config.linkProperties = linkProperties;
725                            switch (ipAssignment) {
726                                case STATIC:
727                                case DHCP:
728                                    config.ipAssignment = ipAssignment;
729                                    break;
730                                case UNASSIGNED:
731                                    //Ignore
732                                    break;
733                                default:
734                                    Log.e(TAG, "Ignore invalid ip assignment while reading");
735                                    break;
736                            }
737
738                            switch (proxySettings) {
739                                case STATIC:
740                                    config.proxySettings = proxySettings;
741                                    ProxyProperties proxyProperties =
742                                        new ProxyProperties(proxyHost, proxyPort, exclusionList);
743                                    linkProperties.setHttpProxy(proxyProperties);
744                                    break;
745                                case NONE:
746                                    config.proxySettings = proxySettings;
747                                    break;
748                                case UNASSIGNED:
749                                    //Ignore
750                                    break;
751                                default:
752                                    Log.e(TAG, "Ignore invalid proxy settings while reading");
753                                    break;
754                            }
755                        }
756                    }
757                } else {
758                    Log.e(TAG, "Missing id while parsing configuration");
759                }
760            }
761        } catch (EOFException ignore) {
762        } catch (IOException e) {
763            Log.e(TAG, "Error parsing configuration" + e);
764        } finally {
765            if (in != null) {
766                try {
767                    in.close();
768                } catch (Exception e) {}
769            }
770        }
771    }
772
773    private static NetworkUpdateResult addOrUpdateNetworkNative(WifiConfiguration config) {
774        /*
775         * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
776         * network configuration. Otherwise, the networkId should
777         * refer to an existing configuration.
778         */
779        int netId = config.networkId;
780        boolean updateFailed = true;
781        // networkId of INVALID_NETWORK_ID means we want to create a new network
782        boolean newNetwork = (netId == INVALID_NETWORK_ID);
783
784        if (newNetwork) {
785            netId = WifiNative.addNetworkCommand();
786            if (netId < 0) {
787                Log.e(TAG, "Failed to add a network!");
788                return new NetworkUpdateResult(INVALID_NETWORK_ID);
789          }
790        }
791
792        setVariables: {
793
794            if (config.SSID != null &&
795                    !WifiNative.setNetworkVariableCommand(
796                        netId,
797                        WifiConfiguration.ssidVarName,
798                        config.SSID)) {
799                Log.d(TAG, "failed to set SSID: "+config.SSID);
800                break setVariables;
801            }
802
803            if (config.BSSID != null &&
804                    !WifiNative.setNetworkVariableCommand(
805                        netId,
806                        WifiConfiguration.bssidVarName,
807                        config.BSSID)) {
808                Log.d(TAG, "failed to set BSSID: "+config.BSSID);
809                break setVariables;
810            }
811
812            String allowedKeyManagementString =
813                makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
814            if (config.allowedKeyManagement.cardinality() != 0 &&
815                    !WifiNative.setNetworkVariableCommand(
816                        netId,
817                        WifiConfiguration.KeyMgmt.varName,
818                        allowedKeyManagementString)) {
819                Log.d(TAG, "failed to set key_mgmt: "+
820                        allowedKeyManagementString);
821                break setVariables;
822            }
823
824            String allowedProtocolsString =
825                makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
826            if (config.allowedProtocols.cardinality() != 0 &&
827                    !WifiNative.setNetworkVariableCommand(
828                        netId,
829                        WifiConfiguration.Protocol.varName,
830                        allowedProtocolsString)) {
831                Log.d(TAG, "failed to set proto: "+
832                        allowedProtocolsString);
833                break setVariables;
834            }
835
836            String allowedAuthAlgorithmsString =
837                makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
838            if (config.allowedAuthAlgorithms.cardinality() != 0 &&
839                    !WifiNative.setNetworkVariableCommand(
840                        netId,
841                        WifiConfiguration.AuthAlgorithm.varName,
842                        allowedAuthAlgorithmsString)) {
843                Log.d(TAG, "failed to set auth_alg: "+
844                        allowedAuthAlgorithmsString);
845                break setVariables;
846            }
847
848            String allowedPairwiseCiphersString =
849                    makeString(config.allowedPairwiseCiphers,
850                    WifiConfiguration.PairwiseCipher.strings);
851            if (config.allowedPairwiseCiphers.cardinality() != 0 &&
852                    !WifiNative.setNetworkVariableCommand(
853                        netId,
854                        WifiConfiguration.PairwiseCipher.varName,
855                        allowedPairwiseCiphersString)) {
856                Log.d(TAG, "failed to set pairwise: "+
857                        allowedPairwiseCiphersString);
858                break setVariables;
859            }
860
861            String allowedGroupCiphersString =
862                makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
863            if (config.allowedGroupCiphers.cardinality() != 0 &&
864                    !WifiNative.setNetworkVariableCommand(
865                        netId,
866                        WifiConfiguration.GroupCipher.varName,
867                        allowedGroupCiphersString)) {
868                Log.d(TAG, "failed to set group: "+
869                        allowedGroupCiphersString);
870                break setVariables;
871            }
872
873            // Prevent client screw-up by passing in a WifiConfiguration we gave it
874            // by preventing "*" as a key.
875            if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
876                    !WifiNative.setNetworkVariableCommand(
877                        netId,
878                        WifiConfiguration.pskVarName,
879                        config.preSharedKey)) {
880                Log.d(TAG, "failed to set psk: "+config.preSharedKey);
881                break setVariables;
882            }
883
884            boolean hasSetKey = false;
885            if (config.wepKeys != null) {
886                for (int i = 0; i < config.wepKeys.length; i++) {
887                    // Prevent client screw-up by passing in a WifiConfiguration we gave it
888                    // by preventing "*" as a key.
889                    if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
890                        if (!WifiNative.setNetworkVariableCommand(
891                                    netId,
892                                    WifiConfiguration.wepKeyVarNames[i],
893                                    config.wepKeys[i])) {
894                            Log.d(TAG,
895                                    "failed to set wep_key"+i+": " +
896                                    config.wepKeys[i]);
897                            break setVariables;
898                        }
899                        hasSetKey = true;
900                    }
901                }
902            }
903
904            if (hasSetKey) {
905                if (!WifiNative.setNetworkVariableCommand(
906                            netId,
907                            WifiConfiguration.wepTxKeyIdxVarName,
908                            Integer.toString(config.wepTxKeyIndex))) {
909                    Log.d(TAG,
910                            "failed to set wep_tx_keyidx: "+
911                            config.wepTxKeyIndex);
912                    break setVariables;
913                }
914            }
915
916            if (!WifiNative.setNetworkVariableCommand(
917                        netId,
918                        WifiConfiguration.priorityVarName,
919                        Integer.toString(config.priority))) {
920                Log.d(TAG, config.SSID + ": failed to set priority: "
921                        +config.priority);
922                break setVariables;
923            }
924
925            if (config.hiddenSSID && !WifiNative.setNetworkVariableCommand(
926                        netId,
927                        WifiConfiguration.hiddenSSIDVarName,
928                        Integer.toString(config.hiddenSSID ? 1 : 0))) {
929                Log.d(TAG, config.SSID + ": failed to set hiddenSSID: "+
930                        config.hiddenSSID);
931                break setVariables;
932            }
933
934            for (WifiConfiguration.EnterpriseField field
935                    : config.enterpriseFields) {
936                String varName = field.varName();
937                String value = field.value();
938                if (value != null) {
939                    if (field != config.eap) {
940                        value = (value.length() == 0) ? "NULL" : convertToQuotedString(value);
941                    }
942                    if (!WifiNative.setNetworkVariableCommand(
943                                netId,
944                                varName,
945                                value)) {
946                        Log.d(TAG, config.SSID + ": failed to set " + varName +
947                                ": " + value);
948                        break setVariables;
949                    }
950                }
951            }
952            updateFailed = false;
953        }
954
955        if (updateFailed) {
956            if (newNetwork) {
957                WifiNative.removeNetworkCommand(netId);
958                Log.d(TAG,
959                        "Failed to set a network variable, removed network: "
960                        + netId);
961            }
962            return new NetworkUpdateResult(INVALID_NETWORK_ID);
963        }
964
965        /* An update of the network variables requires reading them
966         * back from the supplicant to update sConfiguredNetworks.
967         * This is because some of the variables (SSID, wep keys &
968         * passphrases) reflect different values when read back than
969         * when written. For example, wep key is stored as * irrespective
970         * of the value sent to the supplicant
971         */
972        WifiConfiguration sConfig;
973        synchronized (sConfiguredNetworks) {
974            sConfig = sConfiguredNetworks.get(netId);
975        }
976        if (sConfig == null) {
977            sConfig = new WifiConfiguration();
978            sConfig.networkId = netId;
979            synchronized (sConfiguredNetworks) {
980                sConfiguredNetworks.put(netId, sConfig);
981            }
982        }
983        readNetworkVariables(sConfig);
984
985        NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(sConfig, config);
986        result.setNetworkId(netId);
987        return result;
988    }
989
990    /* Compare current and new configuration and write to file on change */
991    private static NetworkUpdateResult writeIpAndProxyConfigurationsOnChange(
992            WifiConfiguration currentConfig,
993            WifiConfiguration newConfig) {
994        boolean ipChanged = false;
995        boolean proxyChanged = false;
996        LinkProperties linkProperties = new LinkProperties();
997
998        switch (newConfig.ipAssignment) {
999            case STATIC:
1000                Collection<LinkAddress> currentLinkAddresses = currentConfig.linkProperties
1001                        .getLinkAddresses();
1002                Collection<LinkAddress> newLinkAddresses = newConfig.linkProperties
1003                        .getLinkAddresses();
1004                Collection<InetAddress> currentDnses = currentConfig.linkProperties.getDnses();
1005                Collection<InetAddress> newDnses = newConfig.linkProperties.getDnses();
1006                Collection<InetAddress> currentGateways =
1007                        currentConfig.linkProperties.getGateways();
1008                Collection<InetAddress> newGateways = newConfig.linkProperties.getGateways();
1009
1010                boolean linkAddressesDiffer =
1011                        (currentLinkAddresses.size() != newLinkAddresses.size()) ||
1012                        !currentLinkAddresses.containsAll(newLinkAddresses);
1013                boolean dnsesDiffer = (currentDnses.size() != newDnses.size()) ||
1014                        !currentDnses.containsAll(newDnses);
1015                boolean gatewaysDiffer = (currentGateways.size() != newGateways.size()) ||
1016                        !currentGateways.containsAll(newGateways);
1017
1018                if ((currentConfig.ipAssignment != newConfig.ipAssignment) ||
1019                        linkAddressesDiffer ||
1020                        dnsesDiffer ||
1021                        gatewaysDiffer) {
1022                    ipChanged = true;
1023                }
1024                break;
1025            case DHCP:
1026                if (currentConfig.ipAssignment != newConfig.ipAssignment) {
1027                    ipChanged = true;
1028                }
1029                break;
1030            case UNASSIGNED:
1031                /* Ignore */
1032                break;
1033            default:
1034                Log.e(TAG, "Ignore invalid ip assignment during write");
1035                break;
1036        }
1037
1038        switch (newConfig.proxySettings) {
1039            case STATIC:
1040                ProxyProperties newHttpProxy = newConfig.linkProperties.getHttpProxy();
1041                ProxyProperties currentHttpProxy = currentConfig.linkProperties.getHttpProxy();
1042
1043                if (newHttpProxy != null) {
1044                    proxyChanged = !newHttpProxy.equals(currentHttpProxy);
1045                } else {
1046                    proxyChanged = (currentHttpProxy != null);
1047                }
1048                break;
1049            case NONE:
1050                if (currentConfig.proxySettings != newConfig.proxySettings) {
1051                    proxyChanged = true;
1052                }
1053                break;
1054            case UNASSIGNED:
1055                /* Ignore */
1056                break;
1057            default:
1058                Log.e(TAG, "Ignore invalid proxy configuration during write");
1059                break;
1060        }
1061
1062        if (!ipChanged) {
1063            addIpSettingsFromConfig(linkProperties, currentConfig);
1064        } else {
1065            currentConfig.ipAssignment = newConfig.ipAssignment;
1066            addIpSettingsFromConfig(linkProperties, newConfig);
1067            Log.d(TAG, "IP config changed SSID = " + currentConfig.SSID + " linkProperties: " +
1068                    linkProperties.toString());
1069        }
1070
1071
1072        if (!proxyChanged) {
1073            linkProperties.setHttpProxy(currentConfig.linkProperties.getHttpProxy());
1074        } else {
1075            currentConfig.proxySettings = newConfig.proxySettings;
1076            linkProperties.setHttpProxy(newConfig.linkProperties.getHttpProxy());
1077            Log.d(TAG, "proxy changed SSID = " + currentConfig.SSID);
1078            if (linkProperties.getHttpProxy() != null) {
1079                Log.d(TAG, " proxyProperties: " + linkProperties.getHttpProxy().toString());
1080            }
1081        }
1082
1083        if (ipChanged || proxyChanged) {
1084            currentConfig.linkProperties = linkProperties;
1085            writeIpAndProxyConfigurations();
1086            sendConfiguredNetworksChangedBroadcast();
1087        }
1088        return new NetworkUpdateResult(ipChanged, proxyChanged);
1089    }
1090
1091    private static void addIpSettingsFromConfig(LinkProperties linkProperties,
1092            WifiConfiguration config) {
1093        for (LinkAddress linkAddr : config.linkProperties.getLinkAddresses()) {
1094            linkProperties.addLinkAddress(linkAddr);
1095        }
1096        for (InetAddress gateway : config.linkProperties.getGateways()) {
1097            linkProperties.addGateway(gateway);
1098        }
1099        for (InetAddress dns : config.linkProperties.getDnses()) {
1100            linkProperties.addDns(dns);
1101        }
1102    }
1103
1104    /**
1105     * Read the variables from the supplicant daemon that are needed to
1106     * fill in the WifiConfiguration object.
1107     *
1108     * @param config the {@link WifiConfiguration} object to be filled in.
1109     */
1110    private static void readNetworkVariables(WifiConfiguration config) {
1111
1112        int netId = config.networkId;
1113        if (netId < 0)
1114            return;
1115
1116        /*
1117         * TODO: maybe should have a native method that takes an array of
1118         * variable names and returns an array of values. But we'd still
1119         * be doing a round trip to the supplicant daemon for each variable.
1120         */
1121        String value;
1122
1123        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.ssidVarName);
1124        if (!TextUtils.isEmpty(value)) {
1125            config.SSID = value;
1126        } else {
1127            config.SSID = null;
1128        }
1129
1130        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.bssidVarName);
1131        if (!TextUtils.isEmpty(value)) {
1132            config.BSSID = value;
1133        } else {
1134            config.BSSID = null;
1135        }
1136
1137        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.priorityVarName);
1138        config.priority = -1;
1139        if (!TextUtils.isEmpty(value)) {
1140            try {
1141                config.priority = Integer.parseInt(value);
1142            } catch (NumberFormatException ignore) {
1143            }
1144        }
1145
1146        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.hiddenSSIDVarName);
1147        config.hiddenSSID = false;
1148        if (!TextUtils.isEmpty(value)) {
1149            try {
1150                config.hiddenSSID = Integer.parseInt(value) != 0;
1151            } catch (NumberFormatException ignore) {
1152            }
1153        }
1154
1155        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.wepTxKeyIdxVarName);
1156        config.wepTxKeyIndex = -1;
1157        if (!TextUtils.isEmpty(value)) {
1158            try {
1159                config.wepTxKeyIndex = Integer.parseInt(value);
1160            } catch (NumberFormatException ignore) {
1161            }
1162        }
1163
1164        for (int i = 0; i < 4; i++) {
1165            value = WifiNative.getNetworkVariableCommand(netId,
1166                    WifiConfiguration.wepKeyVarNames[i]);
1167            if (!TextUtils.isEmpty(value)) {
1168                config.wepKeys[i] = value;
1169            } else {
1170                config.wepKeys[i] = null;
1171            }
1172        }
1173
1174        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.pskVarName);
1175        if (!TextUtils.isEmpty(value)) {
1176            config.preSharedKey = value;
1177        } else {
1178            config.preSharedKey = null;
1179        }
1180
1181        value = WifiNative.getNetworkVariableCommand(config.networkId,
1182                WifiConfiguration.Protocol.varName);
1183        if (!TextUtils.isEmpty(value)) {
1184            String vals[] = value.split(" ");
1185            for (String val : vals) {
1186                int index =
1187                    lookupString(val, WifiConfiguration.Protocol.strings);
1188                if (0 <= index) {
1189                    config.allowedProtocols.set(index);
1190                }
1191            }
1192        }
1193
1194        value = WifiNative.getNetworkVariableCommand(config.networkId,
1195                WifiConfiguration.KeyMgmt.varName);
1196        if (!TextUtils.isEmpty(value)) {
1197            String vals[] = value.split(" ");
1198            for (String val : vals) {
1199                int index =
1200                    lookupString(val, WifiConfiguration.KeyMgmt.strings);
1201                if (0 <= index) {
1202                    config.allowedKeyManagement.set(index);
1203                }
1204            }
1205        }
1206
1207        value = WifiNative.getNetworkVariableCommand(config.networkId,
1208                WifiConfiguration.AuthAlgorithm.varName);
1209        if (!TextUtils.isEmpty(value)) {
1210            String vals[] = value.split(" ");
1211            for (String val : vals) {
1212                int index =
1213                    lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
1214                if (0 <= index) {
1215                    config.allowedAuthAlgorithms.set(index);
1216                }
1217            }
1218        }
1219
1220        value = WifiNative.getNetworkVariableCommand(config.networkId,
1221                WifiConfiguration.PairwiseCipher.varName);
1222        if (!TextUtils.isEmpty(value)) {
1223            String vals[] = value.split(" ");
1224            for (String val : vals) {
1225                int index =
1226                    lookupString(val, WifiConfiguration.PairwiseCipher.strings);
1227                if (0 <= index) {
1228                    config.allowedPairwiseCiphers.set(index);
1229                }
1230            }
1231        }
1232
1233        value = WifiNative.getNetworkVariableCommand(config.networkId,
1234                WifiConfiguration.GroupCipher.varName);
1235        if (!TextUtils.isEmpty(value)) {
1236            String vals[] = value.split(" ");
1237            for (String val : vals) {
1238                int index =
1239                    lookupString(val, WifiConfiguration.GroupCipher.strings);
1240                if (0 <= index) {
1241                    config.allowedGroupCiphers.set(index);
1242                }
1243            }
1244        }
1245
1246        for (WifiConfiguration.EnterpriseField field :
1247                config.enterpriseFields) {
1248            value = WifiNative.getNetworkVariableCommand(netId,
1249                    field.varName());
1250            if (!TextUtils.isEmpty(value)) {
1251                if (field != config.eap) value = removeDoubleQuotes(value);
1252                field.setValue(value);
1253            }
1254        }
1255    }
1256
1257    private static String removeDoubleQuotes(String string) {
1258        if (string.length() <= 2) return "";
1259        return string.substring(1, string.length() - 1);
1260    }
1261
1262    private static String convertToQuotedString(String string) {
1263        return "\"" + string + "\"";
1264    }
1265
1266    private static String makeString(BitSet set, String[] strings) {
1267        StringBuffer buf = new StringBuffer();
1268        int nextSetBit = -1;
1269
1270        /* Make sure all set bits are in [0, strings.length) to avoid
1271         * going out of bounds on strings.  (Shouldn't happen, but...) */
1272        set = set.get(0, strings.length);
1273
1274        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
1275            buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
1276        }
1277
1278        // remove trailing space
1279        if (set.cardinality() > 0) {
1280            buf.setLength(buf.length() - 1);
1281        }
1282
1283        return buf.toString();
1284    }
1285
1286    private static int lookupString(String string, String[] strings) {
1287        int size = strings.length;
1288
1289        string = string.replace('-', '_');
1290
1291        for (int i = 0; i < size; i++)
1292            if (string.equals(strings[i]))
1293                return i;
1294
1295        // if we ever get here, we should probably add the
1296        // value to WifiConfiguration to reflect that it's
1297        // supported by the WPA supplicant
1298        Log.w(TAG, "Failed to look-up a string: " + string);
1299
1300        return -1;
1301    }
1302
1303    /* Returns a unique for a given configuration */
1304    private static int configKey(WifiConfiguration config) {
1305        String key;
1306
1307        if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
1308            key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK];
1309        } else if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
1310                config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
1311            key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
1312        } else if (config.wepKeys[0] != null) {
1313            key = config.SSID + "WEP";
1314        } else {
1315            key = config.SSID + KeyMgmt.strings[KeyMgmt.NONE];
1316        }
1317
1318        return key.hashCode();
1319    }
1320
1321    static String dump() {
1322        StringBuffer sb = new StringBuffer();
1323        String LS = System.getProperty("line.separator");
1324        sb.append("sLastPriority ").append(sLastPriority).append(LS);
1325        sb.append("Configured networks ").append(LS);
1326        for (WifiConfiguration conf : getConfiguredNetworks()) {
1327            sb.append(conf).append(LS);
1328        }
1329        return sb.toString();
1330    }
1331}
1332