WifiConfigStore.java revision 992564e481af13cbcb058ee801f9254a520c54a1
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(InetAddress.getByName(
690                                    in.readUTF()), in.readInt());
691                            linkProperties.addLinkAddress(linkAddr);
692                        } else if (key.equals(GATEWAY_KEY)) {
693                            linkProperties.addGateway(InetAddress.getByName(in.readUTF()));
694                        } else if (key.equals(DNS_KEY)) {
695                            linkProperties.addDns(InetAddress.getByName(in.readUTF()));
696                        } else if (key.equals(PROXY_SETTINGS_KEY)) {
697                            proxySettings = ProxySettings.valueOf(in.readUTF());
698                        } else if (key.equals(PROXY_HOST_KEY)) {
699                            proxyHost = in.readUTF();
700                        } else if (key.equals(PROXY_PORT_KEY)) {
701                            proxyPort = in.readInt();
702                        } else if (key.equals(EXCLUSION_LIST_KEY)) {
703                            exclusionList = in.readUTF();
704                        } else if (key.equals(EOS)) {
705                            break;
706                        } else {
707                            Log.e(TAG, "Ignore unknown key " + key + "while reading");
708                        }
709                    } catch (UnknownHostException e) {
710                        Log.e(TAG, "Ignore invalid address while reading" + e);
711                    }
712                } while (true);
713
714                if (id != -1) {
715                    synchronized (sConfiguredNetworks) {
716                        WifiConfiguration config = sConfiguredNetworks.get(
717                                sNetworkIds.get(id));
718
719                        if (config == null) {
720                            Log.e(TAG, "configuration found for missing network, ignored");
721                        } else {
722                            config.linkProperties = linkProperties;
723                            switch (ipAssignment) {
724                                case STATIC:
725                                case DHCP:
726                                    config.ipAssignment = ipAssignment;
727                                    break;
728                                case UNASSIGNED:
729                                    //Ignore
730                                    break;
731                                default:
732                                    Log.e(TAG, "Ignore invalid ip assignment while reading");
733                                    break;
734                            }
735
736                            switch (proxySettings) {
737                                case STATIC:
738                                    config.proxySettings = proxySettings;
739                                    ProxyProperties proxyProperties =
740                                        new ProxyProperties(proxyHost, proxyPort, exclusionList);
741                                    linkProperties.setHttpProxy(proxyProperties);
742                                    break;
743                                case NONE:
744                                    config.proxySettings = proxySettings;
745                                    break;
746                                case UNASSIGNED:
747                                    //Ignore
748                                    break;
749                                default:
750                                    Log.e(TAG, "Ignore invalid proxy settings while reading");
751                                    break;
752                            }
753                        }
754                    }
755                } else {
756                    Log.e(TAG, "Missing id while parsing configuration");
757                }
758            }
759        } catch (EOFException ignore) {
760        } catch (IOException e) {
761            Log.e(TAG, "Error parsing configuration" + e);
762        } finally {
763            if (in != null) {
764                try {
765                    in.close();
766                } catch (Exception e) {}
767            }
768        }
769    }
770
771    private static NetworkUpdateResult addOrUpdateNetworkNative(WifiConfiguration config) {
772        /*
773         * If the supplied networkId is INVALID_NETWORK_ID, we create a new empty
774         * network configuration. Otherwise, the networkId should
775         * refer to an existing configuration.
776         */
777        int netId = config.networkId;
778        boolean updateFailed = true;
779        // networkId of INVALID_NETWORK_ID means we want to create a new network
780        boolean newNetwork = (netId == INVALID_NETWORK_ID);
781
782        if (newNetwork) {
783            netId = WifiNative.addNetworkCommand();
784            if (netId < 0) {
785                Log.e(TAG, "Failed to add a network!");
786                return new NetworkUpdateResult(INVALID_NETWORK_ID);
787          }
788        }
789
790        setVariables: {
791
792            if (config.SSID != null &&
793                    !WifiNative.setNetworkVariableCommand(
794                        netId,
795                        WifiConfiguration.ssidVarName,
796                        config.SSID)) {
797                Log.d(TAG, "failed to set SSID: "+config.SSID);
798                break setVariables;
799            }
800
801            if (config.BSSID != null &&
802                    !WifiNative.setNetworkVariableCommand(
803                        netId,
804                        WifiConfiguration.bssidVarName,
805                        config.BSSID)) {
806                Log.d(TAG, "failed to set BSSID: "+config.BSSID);
807                break setVariables;
808            }
809
810            String allowedKeyManagementString =
811                makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
812            if (config.allowedKeyManagement.cardinality() != 0 &&
813                    !WifiNative.setNetworkVariableCommand(
814                        netId,
815                        WifiConfiguration.KeyMgmt.varName,
816                        allowedKeyManagementString)) {
817                Log.d(TAG, "failed to set key_mgmt: "+
818                        allowedKeyManagementString);
819                break setVariables;
820            }
821
822            String allowedProtocolsString =
823                makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
824            if (config.allowedProtocols.cardinality() != 0 &&
825                    !WifiNative.setNetworkVariableCommand(
826                        netId,
827                        WifiConfiguration.Protocol.varName,
828                        allowedProtocolsString)) {
829                Log.d(TAG, "failed to set proto: "+
830                        allowedProtocolsString);
831                break setVariables;
832            }
833
834            String allowedAuthAlgorithmsString =
835                makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
836            if (config.allowedAuthAlgorithms.cardinality() != 0 &&
837                    !WifiNative.setNetworkVariableCommand(
838                        netId,
839                        WifiConfiguration.AuthAlgorithm.varName,
840                        allowedAuthAlgorithmsString)) {
841                Log.d(TAG, "failed to set auth_alg: "+
842                        allowedAuthAlgorithmsString);
843                break setVariables;
844            }
845
846            String allowedPairwiseCiphersString =
847                    makeString(config.allowedPairwiseCiphers,
848                    WifiConfiguration.PairwiseCipher.strings);
849            if (config.allowedPairwiseCiphers.cardinality() != 0 &&
850                    !WifiNative.setNetworkVariableCommand(
851                        netId,
852                        WifiConfiguration.PairwiseCipher.varName,
853                        allowedPairwiseCiphersString)) {
854                Log.d(TAG, "failed to set pairwise: "+
855                        allowedPairwiseCiphersString);
856                break setVariables;
857            }
858
859            String allowedGroupCiphersString =
860                makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
861            if (config.allowedGroupCiphers.cardinality() != 0 &&
862                    !WifiNative.setNetworkVariableCommand(
863                        netId,
864                        WifiConfiguration.GroupCipher.varName,
865                        allowedGroupCiphersString)) {
866                Log.d(TAG, "failed to set group: "+
867                        allowedGroupCiphersString);
868                break setVariables;
869            }
870
871            // Prevent client screw-up by passing in a WifiConfiguration we gave it
872            // by preventing "*" as a key.
873            if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
874                    !WifiNative.setNetworkVariableCommand(
875                        netId,
876                        WifiConfiguration.pskVarName,
877                        config.preSharedKey)) {
878                Log.d(TAG, "failed to set psk: "+config.preSharedKey);
879                break setVariables;
880            }
881
882            boolean hasSetKey = false;
883            if (config.wepKeys != null) {
884                for (int i = 0; i < config.wepKeys.length; i++) {
885                    // Prevent client screw-up by passing in a WifiConfiguration we gave it
886                    // by preventing "*" as a key.
887                    if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
888                        if (!WifiNative.setNetworkVariableCommand(
889                                    netId,
890                                    WifiConfiguration.wepKeyVarNames[i],
891                                    config.wepKeys[i])) {
892                            Log.d(TAG,
893                                    "failed to set wep_key"+i+": " +
894                                    config.wepKeys[i]);
895                            break setVariables;
896                        }
897                        hasSetKey = true;
898                    }
899                }
900            }
901
902            if (hasSetKey) {
903                if (!WifiNative.setNetworkVariableCommand(
904                            netId,
905                            WifiConfiguration.wepTxKeyIdxVarName,
906                            Integer.toString(config.wepTxKeyIndex))) {
907                    Log.d(TAG,
908                            "failed to set wep_tx_keyidx: "+
909                            config.wepTxKeyIndex);
910                    break setVariables;
911                }
912            }
913
914            if (!WifiNative.setNetworkVariableCommand(
915                        netId,
916                        WifiConfiguration.priorityVarName,
917                        Integer.toString(config.priority))) {
918                Log.d(TAG, config.SSID + ": failed to set priority: "
919                        +config.priority);
920                break setVariables;
921            }
922
923            if (config.hiddenSSID && !WifiNative.setNetworkVariableCommand(
924                        netId,
925                        WifiConfiguration.hiddenSSIDVarName,
926                        Integer.toString(config.hiddenSSID ? 1 : 0))) {
927                Log.d(TAG, config.SSID + ": failed to set hiddenSSID: "+
928                        config.hiddenSSID);
929                break setVariables;
930            }
931
932            for (WifiConfiguration.EnterpriseField field
933                    : config.enterpriseFields) {
934                String varName = field.varName();
935                String value = field.value();
936                if (value != null) {
937                    if (field != config.eap) {
938                        value = (value.length() == 0) ? "NULL" : convertToQuotedString(value);
939                    }
940                    if (!WifiNative.setNetworkVariableCommand(
941                                netId,
942                                varName,
943                                value)) {
944                        Log.d(TAG, config.SSID + ": failed to set " + varName +
945                                ": " + value);
946                        break setVariables;
947                    }
948                }
949            }
950            updateFailed = false;
951        }
952
953        if (updateFailed) {
954            if (newNetwork) {
955                WifiNative.removeNetworkCommand(netId);
956                Log.d(TAG,
957                        "Failed to set a network variable, removed network: "
958                        + netId);
959            }
960            return new NetworkUpdateResult(INVALID_NETWORK_ID);
961        }
962
963        /* An update of the network variables requires reading them
964         * back from the supplicant to update sConfiguredNetworks.
965         * This is because some of the variables (SSID, wep keys &
966         * passphrases) reflect different values when read back than
967         * when written. For example, wep key is stored as * irrespective
968         * of the value sent to the supplicant
969         */
970        WifiConfiguration sConfig;
971        synchronized (sConfiguredNetworks) {
972            sConfig = sConfiguredNetworks.get(netId);
973        }
974        if (sConfig == null) {
975            sConfig = new WifiConfiguration();
976            sConfig.networkId = netId;
977            synchronized (sConfiguredNetworks) {
978                sConfiguredNetworks.put(netId, sConfig);
979            }
980        }
981        readNetworkVariables(sConfig);
982
983        NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(sConfig, config);
984        result.setNetworkId(netId);
985        return result;
986    }
987
988    /* Compare current and new configuration and write to file on change */
989    private static NetworkUpdateResult writeIpAndProxyConfigurationsOnChange(
990            WifiConfiguration currentConfig,
991            WifiConfiguration newConfig) {
992        boolean ipChanged = false;
993        boolean proxyChanged = false;
994        LinkProperties linkProperties = new LinkProperties();
995
996        switch (newConfig.ipAssignment) {
997            case STATIC:
998                Collection<LinkAddress> currentLinkAddresses = currentConfig.linkProperties
999                        .getLinkAddresses();
1000                Collection<LinkAddress> newLinkAddresses = newConfig.linkProperties
1001                        .getLinkAddresses();
1002                Collection<InetAddress> currentDnses = currentConfig.linkProperties.getDnses();
1003                Collection<InetAddress> newDnses = newConfig.linkProperties.getDnses();
1004                Collection<InetAddress> currentGateways =
1005                        currentConfig.linkProperties.getGateways();
1006                Collection<InetAddress> newGateways = newConfig.linkProperties.getGateways();
1007
1008                boolean linkAddressesDiffer =
1009                        (currentLinkAddresses.size() != newLinkAddresses.size()) ||
1010                        !currentLinkAddresses.containsAll(newLinkAddresses);
1011                boolean dnsesDiffer = (currentDnses.size() != newDnses.size()) ||
1012                        !currentDnses.containsAll(newDnses);
1013                boolean gatewaysDiffer = (currentGateways.size() != newGateways.size()) ||
1014                        !currentGateways.containsAll(newGateways);
1015
1016                if ((currentConfig.ipAssignment != newConfig.ipAssignment) ||
1017                        linkAddressesDiffer ||
1018                        dnsesDiffer ||
1019                        gatewaysDiffer) {
1020                    ipChanged = true;
1021                }
1022                break;
1023            case DHCP:
1024                if (currentConfig.ipAssignment != newConfig.ipAssignment) {
1025                    ipChanged = true;
1026                }
1027                break;
1028            case UNASSIGNED:
1029                /* Ignore */
1030                break;
1031            default:
1032                Log.e(TAG, "Ignore invalid ip assignment during write");
1033                break;
1034        }
1035
1036        switch (newConfig.proxySettings) {
1037            case STATIC:
1038                ProxyProperties newHttpProxy = newConfig.linkProperties.getHttpProxy();
1039                ProxyProperties currentHttpProxy = currentConfig.linkProperties.getHttpProxy();
1040
1041                if (newHttpProxy != null) {
1042                    proxyChanged = !newHttpProxy.equals(currentHttpProxy);
1043                } else {
1044                    proxyChanged = (currentHttpProxy != null);
1045                }
1046                break;
1047            case NONE:
1048                if (currentConfig.proxySettings != newConfig.proxySettings) {
1049                    proxyChanged = true;
1050                }
1051                break;
1052            case UNASSIGNED:
1053                /* Ignore */
1054                break;
1055            default:
1056                Log.e(TAG, "Ignore invalid proxy configuration during write");
1057                break;
1058        }
1059
1060        if (!ipChanged) {
1061            addIpSettingsFromConfig(linkProperties, currentConfig);
1062        } else {
1063            currentConfig.ipAssignment = newConfig.ipAssignment;
1064            addIpSettingsFromConfig(linkProperties, newConfig);
1065            Log.d(TAG, "IP config changed SSID = " + currentConfig.SSID + " linkProperties: " +
1066                    linkProperties.toString());
1067        }
1068
1069
1070        if (!proxyChanged) {
1071            linkProperties.setHttpProxy(currentConfig.linkProperties.getHttpProxy());
1072        } else {
1073            currentConfig.proxySettings = newConfig.proxySettings;
1074            linkProperties.setHttpProxy(newConfig.linkProperties.getHttpProxy());
1075            Log.d(TAG, "proxy changed SSID = " + currentConfig.SSID);
1076            if (linkProperties.getHttpProxy() != null) {
1077                Log.d(TAG, " proxyProperties: " + linkProperties.getHttpProxy().toString());
1078            }
1079        }
1080
1081        if (ipChanged || proxyChanged) {
1082            currentConfig.linkProperties = linkProperties;
1083            writeIpAndProxyConfigurations();
1084            sendConfiguredNetworksChangedBroadcast();
1085        }
1086        return new NetworkUpdateResult(ipChanged, proxyChanged);
1087    }
1088
1089    private static void addIpSettingsFromConfig(LinkProperties linkProperties,
1090            WifiConfiguration config) {
1091        for (LinkAddress linkAddr : config.linkProperties.getLinkAddresses()) {
1092            linkProperties.addLinkAddress(linkAddr);
1093        }
1094        for (InetAddress gateway : config.linkProperties.getGateways()) {
1095            linkProperties.addGateway(gateway);
1096        }
1097        for (InetAddress dns : config.linkProperties.getDnses()) {
1098            linkProperties.addDns(dns);
1099        }
1100    }
1101
1102    /**
1103     * Read the variables from the supplicant daemon that are needed to
1104     * fill in the WifiConfiguration object.
1105     *
1106     * @param config the {@link WifiConfiguration} object to be filled in.
1107     */
1108    private static void readNetworkVariables(WifiConfiguration config) {
1109
1110        int netId = config.networkId;
1111        if (netId < 0)
1112            return;
1113
1114        /*
1115         * TODO: maybe should have a native method that takes an array of
1116         * variable names and returns an array of values. But we'd still
1117         * be doing a round trip to the supplicant daemon for each variable.
1118         */
1119        String value;
1120
1121        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.ssidVarName);
1122        if (!TextUtils.isEmpty(value)) {
1123            config.SSID = value;
1124        } else {
1125            config.SSID = null;
1126        }
1127
1128        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.bssidVarName);
1129        if (!TextUtils.isEmpty(value)) {
1130            config.BSSID = value;
1131        } else {
1132            config.BSSID = null;
1133        }
1134
1135        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.priorityVarName);
1136        config.priority = -1;
1137        if (!TextUtils.isEmpty(value)) {
1138            try {
1139                config.priority = Integer.parseInt(value);
1140            } catch (NumberFormatException ignore) {
1141            }
1142        }
1143
1144        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.hiddenSSIDVarName);
1145        config.hiddenSSID = false;
1146        if (!TextUtils.isEmpty(value)) {
1147            try {
1148                config.hiddenSSID = Integer.parseInt(value) != 0;
1149            } catch (NumberFormatException ignore) {
1150            }
1151        }
1152
1153        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.wepTxKeyIdxVarName);
1154        config.wepTxKeyIndex = -1;
1155        if (!TextUtils.isEmpty(value)) {
1156            try {
1157                config.wepTxKeyIndex = Integer.parseInt(value);
1158            } catch (NumberFormatException ignore) {
1159            }
1160        }
1161
1162        for (int i = 0; i < 4; i++) {
1163            value = WifiNative.getNetworkVariableCommand(netId,
1164                    WifiConfiguration.wepKeyVarNames[i]);
1165            if (!TextUtils.isEmpty(value)) {
1166                config.wepKeys[i] = value;
1167            } else {
1168                config.wepKeys[i] = null;
1169            }
1170        }
1171
1172        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.pskVarName);
1173        if (!TextUtils.isEmpty(value)) {
1174            config.preSharedKey = value;
1175        } else {
1176            config.preSharedKey = null;
1177        }
1178
1179        value = WifiNative.getNetworkVariableCommand(config.networkId,
1180                WifiConfiguration.Protocol.varName);
1181        if (!TextUtils.isEmpty(value)) {
1182            String vals[] = value.split(" ");
1183            for (String val : vals) {
1184                int index =
1185                    lookupString(val, WifiConfiguration.Protocol.strings);
1186                if (0 <= index) {
1187                    config.allowedProtocols.set(index);
1188                }
1189            }
1190        }
1191
1192        value = WifiNative.getNetworkVariableCommand(config.networkId,
1193                WifiConfiguration.KeyMgmt.varName);
1194        if (!TextUtils.isEmpty(value)) {
1195            String vals[] = value.split(" ");
1196            for (String val : vals) {
1197                int index =
1198                    lookupString(val, WifiConfiguration.KeyMgmt.strings);
1199                if (0 <= index) {
1200                    config.allowedKeyManagement.set(index);
1201                }
1202            }
1203        }
1204
1205        value = WifiNative.getNetworkVariableCommand(config.networkId,
1206                WifiConfiguration.AuthAlgorithm.varName);
1207        if (!TextUtils.isEmpty(value)) {
1208            String vals[] = value.split(" ");
1209            for (String val : vals) {
1210                int index =
1211                    lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
1212                if (0 <= index) {
1213                    config.allowedAuthAlgorithms.set(index);
1214                }
1215            }
1216        }
1217
1218        value = WifiNative.getNetworkVariableCommand(config.networkId,
1219                WifiConfiguration.PairwiseCipher.varName);
1220        if (!TextUtils.isEmpty(value)) {
1221            String vals[] = value.split(" ");
1222            for (String val : vals) {
1223                int index =
1224                    lookupString(val, WifiConfiguration.PairwiseCipher.strings);
1225                if (0 <= index) {
1226                    config.allowedPairwiseCiphers.set(index);
1227                }
1228            }
1229        }
1230
1231        value = WifiNative.getNetworkVariableCommand(config.networkId,
1232                WifiConfiguration.GroupCipher.varName);
1233        if (!TextUtils.isEmpty(value)) {
1234            String vals[] = value.split(" ");
1235            for (String val : vals) {
1236                int index =
1237                    lookupString(val, WifiConfiguration.GroupCipher.strings);
1238                if (0 <= index) {
1239                    config.allowedGroupCiphers.set(index);
1240                }
1241            }
1242        }
1243
1244        for (WifiConfiguration.EnterpriseField field :
1245                config.enterpriseFields) {
1246            value = WifiNative.getNetworkVariableCommand(netId,
1247                    field.varName());
1248            if (!TextUtils.isEmpty(value)) {
1249                if (field != config.eap) value = removeDoubleQuotes(value);
1250                field.setValue(value);
1251            }
1252        }
1253    }
1254
1255    private static String removeDoubleQuotes(String string) {
1256        if (string.length() <= 2) return "";
1257        return string.substring(1, string.length() - 1);
1258    }
1259
1260    private static String convertToQuotedString(String string) {
1261        return "\"" + string + "\"";
1262    }
1263
1264    private static String makeString(BitSet set, String[] strings) {
1265        StringBuffer buf = new StringBuffer();
1266        int nextSetBit = -1;
1267
1268        /* Make sure all set bits are in [0, strings.length) to avoid
1269         * going out of bounds on strings.  (Shouldn't happen, but...) */
1270        set = set.get(0, strings.length);
1271
1272        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
1273            buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
1274        }
1275
1276        // remove trailing space
1277        if (set.cardinality() > 0) {
1278            buf.setLength(buf.length() - 1);
1279        }
1280
1281        return buf.toString();
1282    }
1283
1284    private static int lookupString(String string, String[] strings) {
1285        int size = strings.length;
1286
1287        string = string.replace('-', '_');
1288
1289        for (int i = 0; i < size; i++)
1290            if (string.equals(strings[i]))
1291                return i;
1292
1293        // if we ever get here, we should probably add the
1294        // value to WifiConfiguration to reflect that it's
1295        // supported by the WPA supplicant
1296        Log.w(TAG, "Failed to look-up a string: " + string);
1297
1298        return -1;
1299    }
1300
1301    /* Returns a unique for a given configuration */
1302    private static int configKey(WifiConfiguration config) {
1303        String key;
1304
1305        if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
1306            key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_PSK];
1307        } else if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
1308                config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
1309            key = config.SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
1310        } else if (config.wepKeys[0] != null) {
1311            key = config.SSID + "WEP";
1312        } else {
1313            key = config.SSID + KeyMgmt.strings[KeyMgmt.NONE];
1314        }
1315
1316        return key.hashCode();
1317    }
1318
1319    static String dump() {
1320        StringBuffer sb = new StringBuffer();
1321        String LS = System.getProperty("line.separator");
1322        sb.append("sLastPriority ").append(sLastPriority).append(LS);
1323        sb.append("Configured networks ").append(LS);
1324        for (WifiConfiguration conf : getConfiguredNetworks()) {
1325            sb.append(conf).append(LS);
1326        }
1327        return sb.toString();
1328    }
1329}
1330