1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.tv.settings.connectivity;
18
19import com.android.tv.settings.R;
20
21import android.content.Context;
22import android.net.wifi.ScanResult;
23import android.net.wifi.WifiConfiguration;
24import android.net.wifi.WifiInfo;
25import android.net.wifi.WifiManager;
26import android.net.wifi.WifiConfiguration.AuthAlgorithm;
27import android.net.wifi.WifiConfiguration.KeyMgmt;
28import android.text.TextUtils;
29import android.util.Log;
30
31import java.util.List;
32import java.util.regex.Matcher;
33import java.util.regex.Pattern;
34
35/**
36 * Helper class that deals with Wi-fi configuration
37 */
38public final class WifiConfigHelper {
39
40    private static final String TAG = "WifiConfigHelper";
41    private static final boolean DEBUG = false;
42
43    /**
44     * If there are exactly 12 hex digits, this looks like a BSSID
45     */
46    private static final String REGEX_HEX_BSSID = "[a-fA-F0-9]{12}";
47
48    // Allows underscore char to supports proxies that do not
49    // follow the spec
50    private static final String HC = "a-zA-Z0-9\\_";
51
52    // Matches blank input, ips, and domain names
53    private static final String HOSTNAME_REGEXP =
54            "^$|^[" + HC + "]+(\\-[" + HC + "]+)*(\\.[" + HC + "]+(\\-[" + HC + "]+)*)*$";
55    private static final Pattern HOSTNAME_PATTERN;
56    private static final String EXCLUSION_REGEXP =
57            "$|^(\\*)?\\.?[" + HC + "]+(\\-[" + HC + "]+)*(\\.[" + HC + "]+(\\-[" + HC + "]+)*)*$";
58    private static final Pattern EXCLUSION_PATTERN;
59    static {
60        HOSTNAME_PATTERN = Pattern.compile(HOSTNAME_REGEXP);
61        EXCLUSION_PATTERN = Pattern.compile(EXCLUSION_REGEXP);
62    }
63
64    public static void setConfigSsid(WifiConfiguration config, String ssid) {
65        // if this looks like a BSSID, don't quote it
66        if (!Pattern.matches(REGEX_HEX_BSSID, ssid)) {
67            config.SSID = enquoteSsid(ssid);
68        } else {
69            config.SSID = ssid;
70        }
71    }
72
73    public static void setConfigSsid(WifiConfiguration config, ScanResult scanResult) {
74        // just enquote the SSID, if taken from a scan result, we assume that
75        // there is no possibility this is a BSSID in disguise.
76        config.SSID = enquoteSsid(scanResult.SSID);
77    }
78
79    public static void setConfigKeyManagementBySecurity(
80            WifiConfiguration config, WifiSecurity security) {
81        config.allowedKeyManagement.clear();
82        config.allowedAuthAlgorithms.clear();
83        switch (security) {
84            case NONE:
85                config.allowedKeyManagement.set(KeyMgmt.NONE);
86                break;
87            case WEP:
88                config.allowedKeyManagement.set(KeyMgmt.NONE);
89                config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
90                config.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED);
91                break;
92            case PSK:
93                config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
94                break;
95            case EAP:
96                config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
97                config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
98                break;
99        }
100    }
101
102    public static boolean areSameNetwork(WifiManager wifiManager, ScanResult scanResult,
103            WifiInfo wifiInfo) {
104        if (scanResult == null || wifiInfo == null) {
105            return false;
106        }
107        if (scanResult.SSID == null || wifiInfo.getSSID() == null) {
108            return false;
109        }
110        if (scanResult.BSSID == null || wifiInfo.getBSSID() == null) {
111            return false;
112        }
113        String wifiInfoSSID = WifiInfo.removeDoubleQuotes(wifiInfo.getSSID());
114        String wifiInfoBSSID = wifiInfo.getBSSID();
115        WifiSecurity scanResultSecurity = WifiSecurity.getSecurity(scanResult);
116        WifiSecurity wifiInfoSecurity = getCurrentConnectionSecurity(wifiManager, wifiInfo);
117
118        return (TextUtils.equals(scanResult.SSID, wifiInfoSSID) &&
119                TextUtils.equals(scanResult.BSSID, wifiInfoBSSID) &&
120                scanResultSecurity.equals(wifiInfoSecurity));
121    }
122
123    public static WifiSecurity getCurrentConnectionSecurity(WifiManager wifiManager,
124            WifiInfo currentWifiInfo) {
125        if (currentWifiInfo != null) {
126            WifiConfiguration wifiConfiguration = getWifiConfiguration(wifiManager,
127                    currentWifiInfo.getNetworkId());
128            if (wifiConfiguration != null) {
129                return WifiSecurity.getSecurity(wifiConfiguration);
130            }
131        }
132        return WifiSecurity.NONE;
133    }
134
135    /**
136     * validate syntax of hostname and port entries
137     * @return 0 on success, string resource ID on failure
138     */
139    public static int validate(String hostname, String port, String exclList) {
140        Matcher match = HOSTNAME_PATTERN.matcher(hostname);
141        String exclListArray[] = exclList.split(",");
142
143        if (!match.matches()) return R.string.proxy_error_invalid_host;
144
145        for (String excl : exclListArray) {
146            Matcher m = EXCLUSION_PATTERN.matcher(excl);
147            if (!m.matches()) return R.string.proxy_error_invalid_exclusion_list;
148        }
149
150        if (hostname.length() > 0 && port.length() == 0) {
151            return R.string.proxy_error_empty_port;
152        }
153
154        if (port.length() > 0) {
155            if (hostname.length() == 0) {
156                return R.string.proxy_error_empty_host_set_port;
157            }
158            int portVal = -1;
159            try {
160                portVal = Integer.parseInt(port);
161            } catch (NumberFormatException ex) {
162                return R.string.proxy_error_invalid_port;
163            }
164            if (portVal <= 0 || portVal > 0xFFFF) {
165                return R.string.proxy_error_invalid_port;
166            }
167        }
168        return 0;
169    }
170
171    public static WifiConfiguration getWifiConfiguration(WifiManager wifiManager, int networkId) {
172        List<WifiConfiguration> configuredNetworks = wifiManager.getConfiguredNetworks();
173        if (configuredNetworks != null) {
174            for (WifiConfiguration configuredNetwork : configuredNetworks) {
175                if (configuredNetwork.networkId == networkId) {
176                    return configuredNetwork;
177                }
178            }
179        }
180        return null;
181    }
182
183    /**
184     * Did this config come out of the supplicant?  NOT "Is the config currently in the supplicant?"
185     */
186    public static boolean isNetworkSaved(WifiConfiguration config) {
187        return config != null && config.networkId > -1;
188    }
189
190    /**
191     * Return the configured network that matches the ScanResult, or create one.
192     */
193    public static WifiConfiguration getConfigurationForNetwork(Context context,
194            ScanResult network) {
195        WifiConfiguration config = getFromConfiguredNetworks(context, network.SSID,
196                WifiSecurity.getSecurity(network));
197        if (config == null) {
198            config = new WifiConfiguration();
199            setConfigSsid(config, network);
200            setConfigKeyManagementBySecurity(config, WifiSecurity.getSecurity(network));
201        }
202        return config;
203    }
204
205    /**
206     * Return the configured network that matches the ssid/security pair, or create one.
207     */
208    public static WifiConfiguration getConfiguration(Context context, String ssid,
209            WifiSecurity security) {
210        WifiConfiguration config = getFromConfiguredNetworks(context, ssid, security);
211
212        if (config == null) {
213            // No configured network found; populate a new one with the provided ssid / security.
214            config = new WifiConfiguration();
215            setConfigSsid(config, ssid);
216            setConfigKeyManagementBySecurity(config, security);
217        }
218        return config;
219    }
220
221    /**
222     * Save a wifi configuration.
223     */
224    public static boolean saveConfiguration(Context context, WifiConfiguration config) {
225        if (config == null) {
226            return false;
227        }
228
229        WifiManager wifiMan = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
230        int networkId = wifiMan.addNetwork(config);
231        if (networkId == -1) {
232          if (DEBUG) Log.e(TAG, "failed to add network: " + config.toString());
233          return false;
234        }
235
236        if (!wifiMan.enableNetwork(networkId, false)) {
237          if (DEBUG) Log.e(TAG, "enable network failed: " + networkId + "; " + config.toString());
238          return false;
239        }
240
241        if (!wifiMan.saveConfiguration()) {
242          if (DEBUG) Log.e(TAG, "failed to save: " + config.toString());
243          return false;
244        }
245
246        if (DEBUG) Log.d(TAG, "saved network: " + config.toString());
247        return true;
248    }
249
250    /**
251     * Forget a wifi configuration.
252     */
253    public static void forgetConfiguration(Context context, WifiConfiguration config) {
254        if (config == null) {
255            return;
256        }
257
258        WifiManager wifiMan = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
259        List<WifiConfiguration> configuredNetworks = wifiMan.getConfiguredNetworks();
260        if (configuredNetworks == null) {
261            if (DEBUG) Log.e(TAG, "failed to get configured networks");
262            return;
263        }
264
265        for (WifiConfiguration wc : configuredNetworks) {
266            if (wc != null && wc.SSID != null && TextUtils.equals(wc.SSID, config.SSID)) {
267                wifiMan.forget(wc.networkId, null);
268                if (DEBUG) Log.d(TAG, "forgot network config: " + wc.toString());
269                break;
270            }
271        }
272    }
273
274    private static String enquoteSsid(String ssid) {
275        return "\"".concat(ssid.replace("\"", "\\\"")).concat("\"");
276    }
277
278    /**
279     * @return A matching WifiConfiguration from the list of configured
280     * networks, or null if no matching network is found.
281     */
282    private static WifiConfiguration getFromConfiguredNetworks(Context context, String ssid,
283            WifiSecurity security) {
284        WifiManager wifiMan = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
285        List<WifiConfiguration> configuredNetworks = wifiMan.getConfiguredNetworks();
286        if (configuredNetworks != null) {
287            for (WifiConfiguration configuredNetwork : configuredNetworks) {
288                if (configuredNetwork == null || configuredNetwork.SSID == null) {
289                    continue;  // Does this ever really happen?
290                }
291
292                // If the SSID and the security match, that's our network.
293                String configuredSsid = WifiInfo.removeDoubleQuotes(configuredNetwork.SSID);
294                if (TextUtils.equals(configuredSsid, ssid)) {
295                    WifiSecurity configuredSecurity = WifiSecurity.getSecurity(configuredNetwork);
296                    if (configuredSecurity.equals(security)) {
297                        return configuredNetwork;
298                    }
299                }
300            }
301        }
302
303        return null;
304    }
305
306    private WifiConfigHelper() {
307    }
308}
309