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 com.android.settings.wifi;
18
19import android.content.Context;
20import android.net.NetworkInfo.DetailedState;
21import android.net.wifi.ScanResult;
22import android.net.wifi.WifiConfiguration;
23import android.net.wifi.WifiConfiguration.KeyMgmt;
24import android.net.wifi.WifiInfo;
25import android.net.wifi.WifiManager;
26import android.os.Bundle;
27import android.preference.Preference;
28import android.util.Log;
29import android.view.View;
30import android.widget.ImageView;
31
32import com.android.settings.R;
33
34class AccessPoint extends Preference {
35    static final String TAG = "Settings.AccessPoint";
36
37    private static final String KEY_DETAILEDSTATE = "key_detailedstate";
38    private static final String KEY_WIFIINFO = "key_wifiinfo";
39    private static final String KEY_SCANRESULT = "key_scanresult";
40    private static final String KEY_CONFIG = "key_config";
41
42    private static final int[] STATE_SECURED = {
43        R.attr.state_encrypted
44    };
45    private static final int[] STATE_NONE = {};
46
47    /** These values are matched in string arrays -- changes must be kept in sync */
48    static final int SECURITY_NONE = 0;
49    static final int SECURITY_WEP = 1;
50    static final int SECURITY_PSK = 2;
51    static final int SECURITY_EAP = 3;
52
53    enum PskType {
54        UNKNOWN,
55        WPA,
56        WPA2,
57        WPA_WPA2
58    }
59
60    String ssid;
61    String bssid;
62    int security;
63    int networkId;
64    boolean wpsAvailable = false;
65
66    PskType pskType = PskType.UNKNOWN;
67
68    private WifiConfiguration mConfig;
69    /* package */ScanResult mScanResult;
70
71    private int mRssi;
72    private WifiInfo mInfo;
73    private DetailedState mState;
74
75    static int getSecurity(WifiConfiguration config) {
76        if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) {
77            return SECURITY_PSK;
78        }
79        if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) ||
80                config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
81            return SECURITY_EAP;
82        }
83        return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
84    }
85
86    private static int getSecurity(ScanResult result) {
87        if (result.capabilities.contains("WEP")) {
88            return SECURITY_WEP;
89        } else if (result.capabilities.contains("PSK")) {
90            return SECURITY_PSK;
91        } else if (result.capabilities.contains("EAP")) {
92            return SECURITY_EAP;
93        }
94        return SECURITY_NONE;
95    }
96
97    public String getSecurityString(boolean concise) {
98        Context context = getContext();
99        switch(security) {
100            case SECURITY_EAP:
101                return concise ? context.getString(R.string.wifi_security_short_eap) :
102                    context.getString(R.string.wifi_security_eap);
103            case SECURITY_PSK:
104                switch (pskType) {
105                    case WPA:
106                        return concise ? context.getString(R.string.wifi_security_short_wpa) :
107                            context.getString(R.string.wifi_security_wpa);
108                    case WPA2:
109                        return concise ? context.getString(R.string.wifi_security_short_wpa2) :
110                            context.getString(R.string.wifi_security_wpa2);
111                    case WPA_WPA2:
112                        return concise ? context.getString(R.string.wifi_security_short_wpa_wpa2) :
113                            context.getString(R.string.wifi_security_wpa_wpa2);
114                    case UNKNOWN:
115                    default:
116                        return concise ? context.getString(R.string.wifi_security_short_psk_generic)
117                                : context.getString(R.string.wifi_security_psk_generic);
118                }
119            case SECURITY_WEP:
120                return concise ? context.getString(R.string.wifi_security_short_wep) :
121                    context.getString(R.string.wifi_security_wep);
122            case SECURITY_NONE:
123            default:
124                return concise ? "" : context.getString(R.string.wifi_security_none);
125        }
126    }
127
128    private static PskType getPskType(ScanResult result) {
129        boolean wpa = result.capabilities.contains("WPA-PSK");
130        boolean wpa2 = result.capabilities.contains("WPA2-PSK");
131        if (wpa2 && wpa) {
132            return PskType.WPA_WPA2;
133        } else if (wpa2) {
134            return PskType.WPA2;
135        } else if (wpa) {
136            return PskType.WPA;
137        } else {
138            Log.w(TAG, "Received abnormal flag string: " + result.capabilities);
139            return PskType.UNKNOWN;
140        }
141    }
142
143    AccessPoint(Context context, WifiConfiguration config) {
144        super(context);
145        setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
146        loadConfig(config);
147        refresh();
148    }
149
150    AccessPoint(Context context, ScanResult result) {
151        super(context);
152        setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
153        loadResult(result);
154        refresh();
155    }
156
157    AccessPoint(Context context, Bundle savedState) {
158        super(context);
159        setWidgetLayoutResource(R.layout.preference_widget_wifi_signal);
160
161        mConfig = savedState.getParcelable(KEY_CONFIG);
162        if (mConfig != null) {
163            loadConfig(mConfig);
164        }
165        mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT);
166        if (mScanResult != null) {
167            loadResult(mScanResult);
168        }
169        mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO);
170        if (savedState.containsKey(KEY_DETAILEDSTATE)) {
171            mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE));
172        }
173        update(mInfo, mState);
174    }
175
176    public void saveWifiState(Bundle savedState) {
177        savedState.putParcelable(KEY_CONFIG, mConfig);
178        savedState.putParcelable(KEY_SCANRESULT, mScanResult);
179        savedState.putParcelable(KEY_WIFIINFO, mInfo);
180        if (mState != null) {
181            savedState.putString(KEY_DETAILEDSTATE, mState.toString());
182        }
183    }
184
185    private void loadConfig(WifiConfiguration config) {
186        ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID));
187        bssid = config.BSSID;
188        security = getSecurity(config);
189        networkId = config.networkId;
190        mRssi = Integer.MAX_VALUE;
191        mConfig = config;
192    }
193
194    private void loadResult(ScanResult result) {
195        ssid = result.SSID;
196        bssid = result.BSSID;
197        security = getSecurity(result);
198        wpsAvailable = security != SECURITY_EAP && result.capabilities.contains("WPS");
199        if (security == SECURITY_PSK)
200            pskType = getPskType(result);
201        networkId = -1;
202        mRssi = result.level;
203        mScanResult = result;
204    }
205
206    @Override
207    protected void onBindView(View view) {
208        super.onBindView(view);
209        ImageView signal = (ImageView) view.findViewById(R.id.signal);
210        if (mRssi == Integer.MAX_VALUE) {
211            signal.setImageDrawable(null);
212        } else {
213            signal.setImageLevel(getLevel());
214            signal.setImageResource(R.drawable.wifi_signal);
215            signal.setImageState((security != SECURITY_NONE) ?
216                    STATE_SECURED : STATE_NONE, true);
217        }
218    }
219
220    @Override
221    public int compareTo(Preference preference) {
222        if (!(preference instanceof AccessPoint)) {
223            return 1;
224        }
225        AccessPoint other = (AccessPoint) preference;
226        // Active one goes first.
227        if (mInfo != null && other.mInfo == null) return -1;
228        if (mInfo == null && other.mInfo != null) return 1;
229
230        // Reachable one goes before unreachable one.
231        if (mRssi != Integer.MAX_VALUE && other.mRssi == Integer.MAX_VALUE) return -1;
232        if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1;
233
234        // Configured one goes before unconfigured one.
235        if (networkId != WifiConfiguration.INVALID_NETWORK_ID
236                && other.networkId == WifiConfiguration.INVALID_NETWORK_ID) return -1;
237        if (networkId == WifiConfiguration.INVALID_NETWORK_ID
238                && other.networkId != WifiConfiguration.INVALID_NETWORK_ID) return 1;
239
240        // Sort by signal strength.
241        int difference = WifiManager.compareSignalLevel(other.mRssi, mRssi);
242        if (difference != 0) {
243            return difference;
244        }
245        // Sort by ssid.
246        return ssid.compareToIgnoreCase(other.ssid);
247    }
248
249    @Override
250    public boolean equals(Object other) {
251        if (!(other instanceof AccessPoint)) return false;
252        return (this.compareTo((AccessPoint) other) == 0);
253    }
254
255    @Override
256    public int hashCode() {
257        int result = 0;
258        if (mInfo != null) result += 13 * mInfo.hashCode();
259        result += 19 * mRssi;
260        result += 23 * networkId;
261        result += 29 * ssid.hashCode();
262        return result;
263    }
264
265    boolean update(ScanResult result) {
266        if (ssid.equals(result.SSID) && security == getSecurity(result)) {
267            if (WifiManager.compareSignalLevel(result.level, mRssi) > 0) {
268                int oldLevel = getLevel();
269                mRssi = result.level;
270                if (getLevel() != oldLevel) {
271                    notifyChanged();
272                }
273            }
274            // This flag only comes from scans, is not easily saved in config
275            if (security == SECURITY_PSK) {
276                pskType = getPskType(result);
277            }
278            refresh();
279            return true;
280        }
281        return false;
282    }
283
284    void update(WifiInfo info, DetailedState state) {
285        boolean reorder = false;
286        if (info != null && networkId != WifiConfiguration.INVALID_NETWORK_ID
287                && networkId == info.getNetworkId()) {
288            reorder = (mInfo == null);
289            mRssi = info.getRssi();
290            mInfo = info;
291            mState = state;
292            refresh();
293        } else if (mInfo != null) {
294            reorder = true;
295            mInfo = null;
296            mState = null;
297            refresh();
298        }
299        if (reorder) {
300            notifyHierarchyChanged();
301        }
302    }
303
304    int getLevel() {
305        if (mRssi == Integer.MAX_VALUE) {
306            return -1;
307        }
308        return WifiManager.calculateSignalLevel(mRssi, 4);
309    }
310
311    WifiConfiguration getConfig() {
312        return mConfig;
313    }
314
315    WifiInfo getInfo() {
316        return mInfo;
317    }
318
319    DetailedState getState() {
320        return mState;
321    }
322
323    static String removeDoubleQuotes(String string) {
324        int length = string.length();
325        if ((length > 1) && (string.charAt(0) == '"')
326                && (string.charAt(length - 1) == '"')) {
327            return string.substring(1, length - 1);
328        }
329        return string;
330    }
331
332    static String convertToQuotedString(String string) {
333        return "\"" + string + "\"";
334    }
335
336    /** Updates the title and summary; may indirectly call notifyChanged()  */
337    private void refresh() {
338        setTitle(ssid);
339
340        Context context = getContext();
341        if (mConfig != null && mConfig.status == WifiConfiguration.Status.DISABLED) {
342            switch (mConfig.disableReason) {
343                case WifiConfiguration.DISABLED_AUTH_FAILURE:
344                    setSummary(context.getString(R.string.wifi_disabled_password_failure));
345                    break;
346                case WifiConfiguration.DISABLED_DHCP_FAILURE:
347                case WifiConfiguration.DISABLED_DNS_FAILURE:
348                    setSummary(context.getString(R.string.wifi_disabled_network_failure));
349                    break;
350                case WifiConfiguration.DISABLED_UNKNOWN_REASON:
351                    setSummary(context.getString(R.string.wifi_disabled_generic));
352            }
353        } else if (mRssi == Integer.MAX_VALUE) { // Wifi out of range
354            setSummary(context.getString(R.string.wifi_not_in_range));
355        } else if (mState != null) { // This is the active connection
356            setSummary(Summary.get(context, mState));
357        } else { // In range, not disabled.
358            StringBuilder summary = new StringBuilder();
359            if (mConfig != null) { // Is saved network
360                summary.append(context.getString(R.string.wifi_remembered));
361            }
362
363            if (security != SECURITY_NONE) {
364                String securityStrFormat;
365                if (summary.length() == 0) {
366                    securityStrFormat = context.getString(R.string.wifi_secured_first_item);
367                } else {
368                    securityStrFormat = context.getString(R.string.wifi_secured_second_item);
369                }
370                summary.append(String.format(securityStrFormat, getSecurityString(true)));
371            }
372
373            if (mConfig == null && wpsAvailable) { // Only list WPS available for unsaved networks
374                if (summary.length() == 0) {
375                    summary.append(context.getString(R.string.wifi_wps_available_first_item));
376                } else {
377                    summary.append(context.getString(R.string.wifi_wps_available_second_item));
378                }
379            }
380            setSummary(summary.toString());
381        }
382    }
383
384    /**
385     * Generate and save a default wifiConfiguration with common values.
386     * Can only be called for unsecured networks.
387     * @hide
388     */
389    protected void generateOpenNetworkConfig() {
390        if (security != SECURITY_NONE)
391            throw new IllegalStateException();
392        if (mConfig != null)
393            return;
394        mConfig = new WifiConfiguration();
395        mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
396        mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
397    }
398}
399