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 17 package com.android.settings.wifi; 18 19 import com.android.settings.R; 20 21 import android.content.Context; 22 import android.net.NetworkInfo.DetailedState; 23 import android.net.wifi.ScanResult; 24 import android.net.wifi.WifiConfiguration; 25 import android.net.wifi.WifiConfiguration.KeyMgmt; 26 import android.net.wifi.WifiInfo; 27 import android.net.wifi.WifiManager; 28 import android.os.Bundle; 29 import android.preference.Preference; 30 import android.util.Log; 31 import android.view.View; 32 import android.widget.ImageView; 33 34 class 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