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 com.android.settings.R;
20
21import android.content.Context;
22import android.net.NetworkInfo.DetailedState;
23import android.net.wifi.ScanResult;
24import android.net.wifi.WifiConfiguration;
25import android.net.wifi.WifiConfiguration.KeyMgmt;
26import android.net.wifi.WifiInfo;
27import android.net.wifi.WifiManager;
28import android.os.Bundle;
29import android.preference.Preference;
30import android.util.Log;
31import android.view.View;
32import android.widget.ImageView;
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.setImageDrawable(getContext().getTheme().obtainStyledAttributes(
215                    new int[] {R.attr.wifi_signal}).getDrawable(0));
216            signal.setImageState((security != SECURITY_NONE) ?
217                    STATE_SECURED : STATE_NONE, true);
218        }
219    }
220
221    @Override
222    public int compareTo(Preference preference) {
223        if (!(preference instanceof AccessPoint)) {
224            return 1;
225        }
226        AccessPoint other = (AccessPoint) preference;
227        // Active one goes first.
228        if (mInfo != null && other.mInfo == null) return -1;
229        if (mInfo == null && other.mInfo != null) return 1;
230
231        // Reachable one goes before unreachable one.
232        if (mRssi != Integer.MAX_VALUE && other.mRssi == Integer.MAX_VALUE) return -1;
233        if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1;
234
235        // Configured one goes before unconfigured one.
236        if (networkId != WifiConfiguration.INVALID_NETWORK_ID
237                && other.networkId == WifiConfiguration.INVALID_NETWORK_ID) return -1;
238        if (networkId == WifiConfiguration.INVALID_NETWORK_ID
239                && other.networkId != WifiConfiguration.INVALID_NETWORK_ID) return 1;
240
241        // Sort by signal strength.
242        int difference = WifiManager.compareSignalLevel(other.mRssi, mRssi);
243        if (difference != 0) {
244            return difference;
245        }
246        // Sort by ssid.
247        return ssid.compareToIgnoreCase(other.ssid);
248    }
249
250    @Override
251    public boolean equals(Object other) {
252        if (!(other instanceof AccessPoint)) return false;
253        return (this.compareTo((AccessPoint) other) == 0);
254    }
255
256    @Override
257    public int hashCode() {
258        int result = 0;
259        if (mInfo != null) result += 13 * mInfo.hashCode();
260        result += 19 * mRssi;
261        result += 23 * networkId;
262        result += 29 * ssid.hashCode();
263        return result;
264    }
265
266    boolean update(ScanResult result) {
267        if (ssid.equals(result.SSID) && security == getSecurity(result)) {
268            if (WifiManager.compareSignalLevel(result.level, mRssi) > 0) {
269                int oldLevel = getLevel();
270                mRssi = result.level;
271                if (getLevel() != oldLevel) {
272                    notifyChanged();
273                }
274            }
275            // This flag only comes from scans, is not easily saved in config
276            if (security == SECURITY_PSK) {
277                pskType = getPskType(result);
278            }
279            refresh();
280            return true;
281        }
282        return false;
283    }
284
285    void update(WifiInfo info, DetailedState state) {
286        boolean reorder = false;
287        if (info != null && networkId != WifiConfiguration.INVALID_NETWORK_ID
288                && networkId == info.getNetworkId()) {
289            reorder = (mInfo == null);
290            mRssi = info.getRssi();
291            mInfo = info;
292            mState = state;
293            refresh();
294        } else if (mInfo != null) {
295            reorder = true;
296            mInfo = null;
297            mState = null;
298            refresh();
299        }
300        if (reorder) {
301            notifyHierarchyChanged();
302        }
303    }
304
305    int getLevel() {
306        if (mRssi == Integer.MAX_VALUE) {
307            return -1;
308        }
309        return WifiManager.calculateSignalLevel(mRssi, 4);
310    }
311
312    WifiConfiguration getConfig() {
313        return mConfig;
314    }
315
316    WifiInfo getInfo() {
317        return mInfo;
318    }
319
320    DetailedState getState() {
321        return mState;
322    }
323
324    static String removeDoubleQuotes(String string) {
325        int length = string.length();
326        if ((length > 1) && (string.charAt(0) == '"')
327                && (string.charAt(length - 1) == '"')) {
328            return string.substring(1, length - 1);
329        }
330        return string;
331    }
332
333    static String convertToQuotedString(String string) {
334        return "\"" + string + "\"";
335    }
336
337    /** Updates the title and summary; may indirectly call notifyChanged()  */
338    private void refresh() {
339        setTitle(ssid);
340
341        Context context = getContext();
342        if (mConfig != null && mConfig.status == WifiConfiguration.Status.DISABLED) {
343            switch (mConfig.disableReason) {
344                case WifiConfiguration.DISABLED_AUTH_FAILURE:
345                    setSummary(context.getString(R.string.wifi_disabled_password_failure));
346                    break;
347                case WifiConfiguration.DISABLED_DHCP_FAILURE:
348                case WifiConfiguration.DISABLED_DNS_FAILURE:
349                    setSummary(context.getString(R.string.wifi_disabled_network_failure));
350                    break;
351                case WifiConfiguration.DISABLED_UNKNOWN_REASON:
352                    setSummary(context.getString(R.string.wifi_disabled_generic));
353            }
354        } else if (mRssi == Integer.MAX_VALUE) { // Wifi out of range
355            setSummary(context.getString(R.string.wifi_not_in_range));
356        } else if (mState != null) { // This is the active connection
357            setSummary(Summary.get(context, mState));
358        } else { // In range, not disabled.
359            StringBuilder summary = new StringBuilder();
360            if (mConfig != null) { // Is saved network
361                summary.append(context.getString(R.string.wifi_remembered));
362            }
363
364            if (security != SECURITY_NONE) {
365                String securityStrFormat;
366                if (summary.length() == 0) {
367                    securityStrFormat = context.getString(R.string.wifi_secured_first_item);
368                } else {
369                    securityStrFormat = context.getString(R.string.wifi_secured_second_item);
370                }
371                summary.append(String.format(securityStrFormat, getSecurityString(true)));
372            }
373
374            if (mConfig == null && wpsAvailable) { // Only list WPS available for unsaved networks
375                if (summary.length() == 0) {
376                    summary.append(context.getString(R.string.wifi_wps_available_first_item));
377                } else {
378                    summary.append(context.getString(R.string.wifi_wps_available_second_item));
379                }
380            }
381            setSummary(summary.toString());
382        }
383    }
384
385    /**
386     * Generate and save a default wifiConfiguration with common values.
387     * Can only be called for unsecured networks.
388     * @hide
389     */
390    protected void generateOpenNetworkConfig() {
391        if (security != SECURITY_NONE)
392            throw new IllegalStateException();
393        if (mConfig != null)
394            return;
395        mConfig = new WifiConfiguration();
396        mConfig.SSID = AccessPoint.convertToQuotedString(ssid);
397        mConfig.allowedKeyManagement.set(KeyMgmt.NONE);
398    }
399}
400