WifiConfigStore.java revision 04db1d5d3a51f9b710b707cfdc1c2f41ad948237
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.app.ActivityManagerNative;
20import android.content.Context;
21import android.content.Intent;
22import android.net.wifi.WifiConfiguration.Status;
23import android.text.TextUtils;
24import android.util.Log;
25
26import java.util.ArrayList;
27import java.util.BitSet;
28import java.util.List;
29
30/**
31 * This class provides the API to manage configured
32 * wifi networks. The API is not thread safe is being
33 * used only from WifiStateMachine.
34 *
35 * It deals with the following
36 * - Add/update/remove a WifiConfiguration
37 * - Maintain a list of configured networks for quick access
38 * TODO:
39 * - handle static IP per configuration
40 * - handle proxy per configuration
41 */
42class WifiConfigStore {
43
44    private static Context sContext;
45    private static final String TAG = "WifiConfigStore";
46
47    private static List<WifiConfiguration> sConfiguredNetworks = new ArrayList<WifiConfiguration>();
48    /* Tracks the highest priority of configured networks */
49    private static int sLastPriority = -1;
50
51    /**
52     * Initialize context, fetch the list of configured networks
53     * and enable all stored networks in supplicant.
54     */
55    static void initialize(Context context) {
56        Log.d(TAG, "Updating config and enabling all networks");
57        sContext = context;
58        updateConfiguredNetworks();
59        enableAllNetworks();
60    }
61
62    /**
63     * Fetch the list of currently configured networks
64     * @return List of networks
65     */
66    static List<WifiConfiguration> getConfiguredNetworks() {
67        List<WifiConfiguration> networks = new ArrayList<WifiConfiguration>();
68        synchronized (sConfiguredNetworks) {
69            for (WifiConfiguration config : sConfiguredNetworks) {
70                networks.add(config.clone());
71            }
72        }
73        return networks;
74    }
75
76    /**
77     * enable all networks and save config. This will be a no-op if the list
78     * of configured networks indicates all networks as being enabled
79     */
80    static void enableAllNetworks() {
81        for (WifiConfiguration config : sConfiguredNetworks) {
82            if(config != null && config.status == Status.DISABLED) {
83                WifiNative.enableNetworkCommand(config.networkId, false);
84            }
85        }
86
87        WifiNative.saveConfigCommand();
88        updateConfigAndSendChangeBroadcast();
89    }
90
91    /**
92     * Selects the specified network config for connection. This involves
93     * addition/update of the specified config, updating the priority of
94     * all the networks and enabling the given network while disabling others.
95     *
96     * Selecting a network will leave the other networks disabled and
97     * a call to enableAllNetworks() needs to be issued upon a connection
98     * or a failure event from supplicant
99     *
100     * @param config The configuration details in WifiConfiguration
101     */
102    static void selectNetwork(WifiConfiguration config) {
103        if (config != null) {
104            int netId = addOrUpdateNetworkNative(config);
105            selectNetwork(netId);
106        }
107    }
108
109    /**
110     * Selects the specified network for connection. This involves
111     * updating the priority of all the networks and enabling the given
112     * network while disabling others.
113     *
114     * Selecting a network will leave the other networks disabled and
115     * a call to enableAllNetworks() needs to be issued upon a connection
116     * or a failure event from supplicant
117     *
118     * @param netId network to select for connection
119     */
120    static void selectNetwork(int netId) {
121        // Reset the priority of each network at start or if it goes too high.
122        if (sLastPriority == -1 || sLastPriority > 1000000) {
123            for (WifiConfiguration conf : sConfiguredNetworks) {
124                if (conf.networkId != -1) {
125                    conf.priority = 0;
126                    addOrUpdateNetworkNative(conf);
127                }
128            }
129            sLastPriority = 0;
130        }
131
132        // Set to the highest priority and save the configuration.
133        WifiConfiguration config = new WifiConfiguration();
134        config.networkId = netId;
135        config.priority = ++sLastPriority;
136
137        addOrUpdateNetworkNative(config);
138        WifiNative.saveConfigCommand();
139
140        /* Enable the given network while disabling all other networks */
141        WifiNative.enableNetworkCommand(netId, true);
142
143        /* update the configured networks list but not send a
144         * broadcast to avoid a fetch from settings
145         * during this temporary disabling of networks
146         */
147        updateConfiguredNetworks();
148    }
149
150    /**
151     * Add/update the specified configuration and save config
152     *
153     * @param config WifiConfiguration to be saved
154     */
155    static void saveNetwork(WifiConfiguration config) {
156        int netId = addOrUpdateNetworkNative(config);
157        /* enable a new network */
158        if (config.networkId < 0) {
159            WifiNative.enableNetworkCommand(netId, false);
160        }
161        WifiNative.saveConfigCommand();
162        updateConfigAndSendChangeBroadcast();
163    }
164
165    /**
166     * Forget the specified network and save config
167     *
168     * @param netId network to forget
169     */
170    static void forgetNetwork(int netId) {
171        WifiNative.removeNetworkCommand(netId);
172        WifiNative.saveConfigCommand();
173        updateConfigAndSendChangeBroadcast();
174    }
175
176    /**
177     * Add/update a network. Note that there is no saveConfig operation.
178     * This function is retained for compatibility with the public
179     * API. The more powerful saveNetwork() is used by the
180     * state machine
181     *
182     * @param config wifi configuration to add/update
183     */
184    static int addOrUpdateNetwork(WifiConfiguration config) {
185        int ret = addOrUpdateNetworkNative(config);
186        updateConfigAndSendChangeBroadcast();
187        return ret;
188    }
189
190    /**
191     * Remove a network. Note that there is no saveConfig operation.
192     * This function is retained for compatibility with the public
193     * API. The more powerful forgetNetwork() is used by the
194     * state machine for network removal
195     *
196     * @param netId network to be removed
197     */
198    static boolean removeNetwork(int netId) {
199        boolean ret = WifiNative.removeNetworkCommand(netId);
200        updateConfigAndSendChangeBroadcast();
201        return ret;
202    }
203
204    /**
205     * Enable a network. Note that there is no saveConfig operation.
206     * This function is retained for compatibility with the public
207     * API. The more powerful selectNetwork()/saveNetwork() is used by the
208     * state machine for connecting to a network
209     *
210     * @param netId network to be removed
211     */
212    static boolean enableNetwork(int netId, boolean disableOthers) {
213        boolean ret = WifiNative.enableNetworkCommand(netId, disableOthers);
214        updateConfigAndSendChangeBroadcast();
215        return ret;
216    }
217
218    /**
219     * Disable a network. Note that there is no saveConfig operation.
220     * @param netId network to be disabled
221     */
222    static boolean disableNetwork(int netId) {
223        boolean ret = WifiNative.disableNetworkCommand(netId);
224        updateConfigAndSendChangeBroadcast();
225        return ret;
226    }
227
228    /**
229     * Save the configured networks in supplicant to disk
230     */
231    static boolean saveConfig() {
232        return WifiNative.saveConfigCommand();
233    }
234
235    private static void updateConfigAndSendChangeBroadcast() {
236        updateConfiguredNetworks();
237        if (!ActivityManagerNative.isSystemReady()) return;
238        Intent intent = new Intent(WifiManager.SUPPLICANT_CONFIG_CHANGED_ACTION);
239        sContext.sendBroadcast(intent);
240    }
241
242    private static void updateConfiguredNetworks() {
243        String listStr = WifiNative.listNetworksCommand();
244        sLastPriority = 0;
245
246        synchronized (sConfiguredNetworks) {
247            sConfiguredNetworks.clear();
248
249            if (listStr == null)
250                return;
251
252            String[] lines = listStr.split("\n");
253            // Skip the first line, which is a header
254            for (int i = 1; i < lines.length; i++) {
255                String[] result = lines[i].split("\t");
256                // network-id | ssid | bssid | flags
257                WifiConfiguration config = new WifiConfiguration();
258                try {
259                    config.networkId = Integer.parseInt(result[0]);
260                } catch(NumberFormatException e) {
261                    continue;
262                }
263                if (result.length > 3) {
264                    if (result[3].indexOf("[CURRENT]") != -1)
265                        config.status = WifiConfiguration.Status.CURRENT;
266                    else if (result[3].indexOf("[DISABLED]") != -1)
267                        config.status = WifiConfiguration.Status.DISABLED;
268                    else
269                        config.status = WifiConfiguration.Status.ENABLED;
270                } else {
271                    config.status = WifiConfiguration.Status.ENABLED;
272                }
273                readNetworkVariables(config);
274                if (config.priority > sLastPriority) {
275                    sLastPriority = config.priority;
276                }
277                sConfiguredNetworks.add(config);
278            }
279        }
280    }
281
282    private static int addOrUpdateNetworkNative(WifiConfiguration config) {
283        /*
284         * If the supplied networkId is -1, we create a new empty
285         * network configuration. Otherwise, the networkId should
286         * refer to an existing configuration.
287         */
288        int netId = config.networkId;
289        boolean newNetwork = netId == -1;
290        // networkId of -1 means we want to create a new network
291
292        if (newNetwork) {
293            netId = WifiNative.addNetworkCommand();
294            if (netId < 0) {
295                Log.e(TAG, "Failed to add a network!");
296                return -1;
297          }
298        }
299
300        setVariables: {
301
302            if (config.SSID != null &&
303                    !WifiNative.setNetworkVariableCommand(
304                        netId,
305                        WifiConfiguration.ssidVarName,
306                        config.SSID)) {
307                Log.d(TAG, "failed to set SSID: "+config.SSID);
308                break setVariables;
309            }
310
311            if (config.BSSID != null &&
312                    !WifiNative.setNetworkVariableCommand(
313                        netId,
314                        WifiConfiguration.bssidVarName,
315                        config.BSSID)) {
316                Log.d(TAG, "failed to set BSSID: "+config.BSSID);
317                break setVariables;
318            }
319
320            String allowedKeyManagementString =
321                makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
322            if (config.allowedKeyManagement.cardinality() != 0 &&
323                    !WifiNative.setNetworkVariableCommand(
324                        netId,
325                        WifiConfiguration.KeyMgmt.varName,
326                        allowedKeyManagementString)) {
327                Log.d(TAG, "failed to set key_mgmt: "+
328                        allowedKeyManagementString);
329                break setVariables;
330            }
331
332            String allowedProtocolsString =
333                makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
334            if (config.allowedProtocols.cardinality() != 0 &&
335                    !WifiNative.setNetworkVariableCommand(
336                        netId,
337                        WifiConfiguration.Protocol.varName,
338                        allowedProtocolsString)) {
339                Log.d(TAG, "failed to set proto: "+
340                        allowedProtocolsString);
341                break setVariables;
342            }
343
344            String allowedAuthAlgorithmsString =
345                makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
346            if (config.allowedAuthAlgorithms.cardinality() != 0 &&
347                    !WifiNative.setNetworkVariableCommand(
348                        netId,
349                        WifiConfiguration.AuthAlgorithm.varName,
350                        allowedAuthAlgorithmsString)) {
351                Log.d(TAG, "failed to set auth_alg: "+
352                        allowedAuthAlgorithmsString);
353                break setVariables;
354            }
355
356            String allowedPairwiseCiphersString =
357                    makeString(config.allowedPairwiseCiphers,
358                    WifiConfiguration.PairwiseCipher.strings);
359            if (config.allowedPairwiseCiphers.cardinality() != 0 &&
360                    !WifiNative.setNetworkVariableCommand(
361                        netId,
362                        WifiConfiguration.PairwiseCipher.varName,
363                        allowedPairwiseCiphersString)) {
364                Log.d(TAG, "failed to set pairwise: "+
365                        allowedPairwiseCiphersString);
366                break setVariables;
367            }
368
369            String allowedGroupCiphersString =
370                makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
371            if (config.allowedGroupCiphers.cardinality() != 0 &&
372                    !WifiNative.setNetworkVariableCommand(
373                        netId,
374                        WifiConfiguration.GroupCipher.varName,
375                        allowedGroupCiphersString)) {
376                Log.d(TAG, "failed to set group: "+
377                        allowedGroupCiphersString);
378                break setVariables;
379            }
380
381            // Prevent client screw-up by passing in a WifiConfiguration we gave it
382            // by preventing "*" as a key.
383            if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
384                    !WifiNative.setNetworkVariableCommand(
385                        netId,
386                        WifiConfiguration.pskVarName,
387                        config.preSharedKey)) {
388                Log.d(TAG, "failed to set psk: "+config.preSharedKey);
389                break setVariables;
390            }
391
392            boolean hasSetKey = false;
393            if (config.wepKeys != null) {
394                for (int i = 0; i < config.wepKeys.length; i++) {
395                    // Prevent client screw-up by passing in a WifiConfiguration we gave it
396                    // by preventing "*" as a key.
397                    if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
398                        if (!WifiNative.setNetworkVariableCommand(
399                                    netId,
400                                    WifiConfiguration.wepKeyVarNames[i],
401                                    config.wepKeys[i])) {
402                            Log.d(TAG,
403                                    "failed to set wep_key"+i+": " +
404                                    config.wepKeys[i]);
405                            break setVariables;
406                        }
407                        hasSetKey = true;
408                    }
409                }
410            }
411
412            if (hasSetKey) {
413                if (!WifiNative.setNetworkVariableCommand(
414                            netId,
415                            WifiConfiguration.wepTxKeyIdxVarName,
416                            Integer.toString(config.wepTxKeyIndex))) {
417                    Log.d(TAG,
418                            "failed to set wep_tx_keyidx: "+
419                            config.wepTxKeyIndex);
420                    break setVariables;
421                }
422            }
423
424            if (!WifiNative.setNetworkVariableCommand(
425                        netId,
426                        WifiConfiguration.priorityVarName,
427                        Integer.toString(config.priority))) {
428                Log.d(TAG, config.SSID + ": failed to set priority: "
429                        +config.priority);
430                break setVariables;
431            }
432
433            if (config.hiddenSSID && !WifiNative.setNetworkVariableCommand(
434                        netId,
435                        WifiConfiguration.hiddenSSIDVarName,
436                        Integer.toString(config.hiddenSSID ? 1 : 0))) {
437                Log.d(TAG, config.SSID + ": failed to set hiddenSSID: "+
438                        config.hiddenSSID);
439                break setVariables;
440            }
441
442            for (WifiConfiguration.EnterpriseField field
443                    : config.enterpriseFields) {
444                String varName = field.varName();
445                String value = field.value();
446                if (value != null) {
447                    if (field != config.eap) {
448                        value = (value.length() == 0) ? "NULL" : convertToQuotedString(value);
449                    }
450                    if (!WifiNative.setNetworkVariableCommand(
451                                netId,
452                                varName,
453                                value)) {
454                        Log.d(TAG, config.SSID + ": failed to set " + varName +
455                                ": " + value);
456                        break setVariables;
457                    }
458                }
459            }
460            return netId;
461        }
462
463        if (newNetwork) {
464            WifiNative.removeNetworkCommand(netId);
465            Log.d(TAG,
466                    "Failed to set a network variable, removed network: "
467                    + netId);
468        }
469
470        return -1;
471    }
472
473    /**
474     * Read the variables from the supplicant daemon that are needed to
475     * fill in the WifiConfiguration object.
476     *
477     * @param config the {@link WifiConfiguration} object to be filled in.
478     */
479    private static void readNetworkVariables(WifiConfiguration config) {
480
481        int netId = config.networkId;
482        if (netId < 0)
483            return;
484
485        /*
486         * TODO: maybe should have a native method that takes an array of
487         * variable names and returns an array of values. But we'd still
488         * be doing a round trip to the supplicant daemon for each variable.
489         */
490        String value;
491
492        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.ssidVarName);
493        if (!TextUtils.isEmpty(value)) {
494            config.SSID = removeDoubleQuotes(value);
495        } else {
496            config.SSID = null;
497        }
498
499        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.bssidVarName);
500        if (!TextUtils.isEmpty(value)) {
501            config.BSSID = value;
502        } else {
503            config.BSSID = null;
504        }
505
506        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.priorityVarName);
507        config.priority = -1;
508        if (!TextUtils.isEmpty(value)) {
509            try {
510                config.priority = Integer.parseInt(value);
511            } catch (NumberFormatException ignore) {
512            }
513        }
514
515        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.hiddenSSIDVarName);
516        config.hiddenSSID = false;
517        if (!TextUtils.isEmpty(value)) {
518            try {
519                config.hiddenSSID = Integer.parseInt(value) != 0;
520            } catch (NumberFormatException ignore) {
521            }
522        }
523
524        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.wepTxKeyIdxVarName);
525        config.wepTxKeyIndex = -1;
526        if (!TextUtils.isEmpty(value)) {
527            try {
528                config.wepTxKeyIndex = Integer.parseInt(value);
529            } catch (NumberFormatException ignore) {
530            }
531        }
532
533        for (int i = 0; i < 4; i++) {
534            value = WifiNative.getNetworkVariableCommand(netId,
535                    WifiConfiguration.wepKeyVarNames[i]);
536            if (!TextUtils.isEmpty(value)) {
537                config.wepKeys[i] = value;
538            } else {
539                config.wepKeys[i] = null;
540            }
541        }
542
543        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.pskVarName);
544        if (!TextUtils.isEmpty(value)) {
545            config.preSharedKey = value;
546        } else {
547            config.preSharedKey = null;
548        }
549
550        value = WifiNative.getNetworkVariableCommand(config.networkId,
551                WifiConfiguration.Protocol.varName);
552        if (!TextUtils.isEmpty(value)) {
553            String vals[] = value.split(" ");
554            for (String val : vals) {
555                int index =
556                    lookupString(val, WifiConfiguration.Protocol.strings);
557                if (0 <= index) {
558                    config.allowedProtocols.set(index);
559                }
560            }
561        }
562
563        value = WifiNative.getNetworkVariableCommand(config.networkId,
564                WifiConfiguration.KeyMgmt.varName);
565        if (!TextUtils.isEmpty(value)) {
566            String vals[] = value.split(" ");
567            for (String val : vals) {
568                int index =
569                    lookupString(val, WifiConfiguration.KeyMgmt.strings);
570                if (0 <= index) {
571                    config.allowedKeyManagement.set(index);
572                }
573            }
574        }
575
576        value = WifiNative.getNetworkVariableCommand(config.networkId,
577                WifiConfiguration.AuthAlgorithm.varName);
578        if (!TextUtils.isEmpty(value)) {
579            String vals[] = value.split(" ");
580            for (String val : vals) {
581                int index =
582                    lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
583                if (0 <= index) {
584                    config.allowedAuthAlgorithms.set(index);
585                }
586            }
587        }
588
589        value = WifiNative.getNetworkVariableCommand(config.networkId,
590                WifiConfiguration.PairwiseCipher.varName);
591        if (!TextUtils.isEmpty(value)) {
592            String vals[] = value.split(" ");
593            for (String val : vals) {
594                int index =
595                    lookupString(val, WifiConfiguration.PairwiseCipher.strings);
596                if (0 <= index) {
597                    config.allowedPairwiseCiphers.set(index);
598                }
599            }
600        }
601
602        value = WifiNative.getNetworkVariableCommand(config.networkId,
603                WifiConfiguration.GroupCipher.varName);
604        if (!TextUtils.isEmpty(value)) {
605            String vals[] = value.split(" ");
606            for (String val : vals) {
607                int index =
608                    lookupString(val, WifiConfiguration.GroupCipher.strings);
609                if (0 <= index) {
610                    config.allowedGroupCiphers.set(index);
611                }
612            }
613        }
614
615        for (WifiConfiguration.EnterpriseField field :
616                config.enterpriseFields) {
617            value = WifiNative.getNetworkVariableCommand(netId,
618                    field.varName());
619            if (!TextUtils.isEmpty(value)) {
620                if (field != config.eap) value = removeDoubleQuotes(value);
621                field.setValue(value);
622            }
623        }
624    }
625
626    private static String removeDoubleQuotes(String string) {
627        if (string.length() <= 2) return "";
628        return string.substring(1, string.length() - 1);
629    }
630
631    private static String convertToQuotedString(String string) {
632        return "\"" + string + "\"";
633    }
634
635    private static String makeString(BitSet set, String[] strings) {
636        StringBuffer buf = new StringBuffer();
637        int nextSetBit = -1;
638
639        /* Make sure all set bits are in [0, strings.length) to avoid
640         * going out of bounds on strings.  (Shouldn't happen, but...) */
641        set = set.get(0, strings.length);
642
643        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
644            buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
645        }
646
647        // remove trailing space
648        if (set.cardinality() > 0) {
649            buf.setLength(buf.length() - 1);
650        }
651
652        return buf.toString();
653    }
654
655    private static int lookupString(String string, String[] strings) {
656        int size = strings.length;
657
658        string = string.replace('-', '_');
659
660        for (int i = 0; i < size; i++)
661            if (string.equals(strings[i]))
662                return i;
663
664        // if we ever get here, we should probably add the
665        // value to WifiConfiguration to reflect that it's
666        // supported by the WPA supplicant
667        Log.w(TAG, "Failed to look-up a string: " + string);
668
669        return -1;
670    }
671
672    static String dump() {
673        StringBuffer sb = new StringBuffer();
674        String LS = System.getProperty("line.separator");
675        sb.append("sLastPriority ").append(sLastPriority).append(LS);
676        sb.append("Configured networks ").append(LS);
677        for (WifiConfiguration conf : getConfiguredNetworks()) {
678            sb.append(conf).append(LS);
679        }
680        return sb.toString();
681    }
682}
683