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.graphics.drawable.Drawable; 23 import android.graphics.drawable.StateListDrawable; 24 import android.net.NetworkInfo.DetailedState; 25 import android.net.wifi.ScanResult; 26 import android.net.wifi.WifiConfiguration; 27 import android.net.wifi.WifiConfiguration.KeyMgmt; 28 import android.net.wifi.WifiInfo; 29 import android.net.wifi.WifiManager; 30 import android.os.Bundle; 31 import android.preference.Preference; 32 import android.util.Log; 33 import android.util.LruCache; 34 import android.view.View; 35 import android.widget.ImageView; 36 import android.widget.TextView; 37 38 import java.util.Map; 39 40 41 class AccessPoint extends Preference { 42 static final String TAG = "Settings.AccessPoint"; 43 44 /** 45 * Lower bound on the 2.4 GHz (802.11b/g/n) WLAN channels 46 */ 47 public static final int LOWER_FREQ_24GHZ = 2400; 48 49 /** 50 * Upper bound on the 2.4 GHz (802.11b/g/n) WLAN channels 51 */ 52 public static final int HIGHER_FREQ_24GHZ = 2500; 53 54 /** 55 * Lower bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels 56 */ 57 public static final int LOWER_FREQ_5GHZ = 4900; 58 59 /** 60 * Upper bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels 61 */ 62 public static final int HIGHER_FREQ_5GHZ = 5900; 63 64 /** 65 * Experimental: we should be able to show the user the list of BSSIDs and bands 66 * for that SSID. 67 * For now this data is used only with Verbose Logging so as to show the band and number 68 * of BSSIDs on which that network is seen. 69 */ 70 public LruCache<String, ScanResult> mScanResultCache; 71 72 73 private static final String KEY_DETAILEDSTATE = "key_detailedstate"; 74 private static final String KEY_WIFIINFO = "key_wifiinfo"; 75 private static final String KEY_SCANRESULT = "key_scanresult"; 76 private static final String KEY_CONFIG = "key_config"; 77 78 private static final int[] STATE_SECURED = { 79 R.attr.state_encrypted 80 }; 81 private static final int[] STATE_NONE = {}; 82 83 private static int[] wifi_signal_attributes = { R.attr.wifi_signal }; 84 85 /** 86 * These values are matched in string arrays -- changes must be kept in sync 87 */ 88 static final int SECURITY_NONE = 0; 89 static final int SECURITY_WEP = 1; 90 static final int SECURITY_PSK = 2; 91 static final int SECURITY_EAP = 3; 92 93 enum PskType { 94 UNKNOWN, 95 WPA, 96 WPA2, 97 WPA_WPA2 98 } 99 100 String ssid; 101 String bssid; 102 int security; 103 int networkId = -1; 104 boolean wpsAvailable = false; 105 boolean showSummary = true; 106 107 PskType pskType = PskType.UNKNOWN; 108 109 private WifiConfiguration mConfig; 110 /* package */ScanResult mScanResult; 111 112 private int mRssi = Integer.MAX_VALUE; 113 private long mSeen = 0; 114 115 private WifiInfo mInfo; 116 private DetailedState mState; 117 118 private static final int VISIBILITY_MAX_AGE_IN_MILLI = 1000000; 119 private static final int VISIBILITY_OUTDATED_AGE_IN_MILLI = 20000; 120 private static final int SECOND_TO_MILLI = 1000; 121 122 static int getSecurity(WifiConfiguration config) { 123 if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { 124 return SECURITY_PSK; 125 } 126 if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) || 127 config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) { 128 return SECURITY_EAP; 129 } 130 return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE; 131 } 132 133 private static int getSecurity(ScanResult result) { 134 if (result.capabilities.contains("WEP")) { 135 return SECURITY_WEP; 136 } else if (result.capabilities.contains("PSK")) { 137 return SECURITY_PSK; 138 } else if (result.capabilities.contains("EAP")) { 139 return SECURITY_EAP; 140 } 141 return SECURITY_NONE; 142 } 143 144 public String getSecurityString(boolean concise) { 145 Context context = getContext(); 146 switch(security) { 147 case SECURITY_EAP: 148 return concise ? context.getString(R.string.wifi_security_short_eap) : 149 context.getString(R.string.wifi_security_eap); 150 case SECURITY_PSK: 151 switch (pskType) { 152 case WPA: 153 return concise ? context.getString(R.string.wifi_security_short_wpa) : 154 context.getString(R.string.wifi_security_wpa); 155 case WPA2: 156 return concise ? context.getString(R.string.wifi_security_short_wpa2) : 157 context.getString(R.string.wifi_security_wpa2); 158 case WPA_WPA2: 159 return concise ? context.getString(R.string.wifi_security_short_wpa_wpa2) : 160 context.getString(R.string.wifi_security_wpa_wpa2); 161 case UNKNOWN: 162 default: 163 return concise ? context.getString(R.string.wifi_security_short_psk_generic) 164 : context.getString(R.string.wifi_security_psk_generic); 165 } 166 case SECURITY_WEP: 167 return concise ? context.getString(R.string.wifi_security_short_wep) : 168 context.getString(R.string.wifi_security_wep); 169 case SECURITY_NONE: 170 default: 171 return concise ? "" : context.getString(R.string.wifi_security_none); 172 } 173 } 174 175 private static PskType getPskType(ScanResult result) { 176 boolean wpa = result.capabilities.contains("WPA-PSK"); 177 boolean wpa2 = result.capabilities.contains("WPA2-PSK"); 178 if (wpa2 && wpa) { 179 return PskType.WPA_WPA2; 180 } else if (wpa2) { 181 return PskType.WPA2; 182 } else if (wpa) { 183 return PskType.WPA; 184 } else { 185 Log.w(TAG, "Received abnormal flag string: " + result.capabilities); 186 return PskType.UNKNOWN; 187 } 188 } 189 190 AccessPoint(Context context, WifiConfiguration config) { 191 super(context); 192 loadConfig(config); 193 refresh(); 194 } 195 196 AccessPoint(Context context, ScanResult result) { 197 super(context); 198 loadResult(result); 199 refresh(); 200 } 201 202 AccessPoint(Context context, Bundle savedState) { 203 super(context); 204 205 mConfig = savedState.getParcelable(KEY_CONFIG); 206 if (mConfig != null) { 207 loadConfig(mConfig); 208 } 209 mScanResult = (ScanResult) savedState.getParcelable(KEY_SCANRESULT); 210 if (mScanResult != null) { 211 loadResult(mScanResult); 212 } 213 mInfo = (WifiInfo) savedState.getParcelable(KEY_WIFIINFO); 214 if (savedState.containsKey(KEY_DETAILEDSTATE)) { 215 mState = DetailedState.valueOf(savedState.getString(KEY_DETAILEDSTATE)); 216 } 217 update(mInfo, mState); 218 } 219 220 public void saveWifiState(Bundle savedState) { 221 savedState.putParcelable(KEY_CONFIG, mConfig); 222 savedState.putParcelable(KEY_SCANRESULT, mScanResult); 223 savedState.putParcelable(KEY_WIFIINFO, mInfo); 224 if (mState != null) { 225 savedState.putString(KEY_DETAILEDSTATE, mState.toString()); 226 } 227 } 228 229 private void loadConfig(WifiConfiguration config) { 230 ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID)); 231 bssid = config.BSSID; 232 security = getSecurity(config); 233 networkId = config.networkId; 234 mConfig = config; 235 } 236 237 private void loadResult(ScanResult result) { 238 ssid = result.SSID; 239 bssid = result.BSSID; 240 security = getSecurity(result); 241 wpsAvailable = security != SECURITY_EAP && result.capabilities.contains("WPS"); 242 if (security == SECURITY_PSK) 243 pskType = getPskType(result); 244 mRssi = result.level; 245 mScanResult = result; 246 if (result.seen > mSeen) { 247 mSeen = result.seen; 248 } 249 } 250 251 @Override 252 protected void onBindView(View view) { 253 super.onBindView(view); 254 updateIcon(getLevel(), getContext()); 255 256 final TextView summaryView = (TextView) view.findViewById( 257 com.android.internal.R.id.summary); 258 summaryView.setVisibility(showSummary ? View.VISIBLE : View.GONE); 259 260 notifyChanged(); 261 } 262 263 protected void updateIcon(int level, Context context) { 264 if (level == -1) { 265 setIcon(null); 266 } else { 267 Drawable drawable = getIcon(); 268 269 if (drawable == null) { 270 // To avoid a drawing race condition, we first set the state (SECURE/NONE) and then 271 // set the icon (drawable) to that state's drawable. 272 StateListDrawable sld = (StateListDrawable) context.getTheme() 273 .obtainStyledAttributes(wifi_signal_attributes).getDrawable(0); 274 // If sld is null then we are indexing and therefore do not have access to 275 // (nor need to display) the drawable. 276 if (sld != null) { 277 sld.setState((security != SECURITY_NONE) ? STATE_SECURED : STATE_NONE); 278 drawable = sld.getCurrent(); 279 setIcon(drawable); 280 } 281 } 282 283 if (drawable != null) { 284 drawable.setLevel(level); 285 } 286 } 287 } 288 289 @Override 290 public int compareTo(Preference preference) { 291 if (!(preference instanceof AccessPoint)) { 292 return 1; 293 } 294 AccessPoint other = (AccessPoint) preference; 295 // Active one goes first. 296 if (mInfo != null && other.mInfo == null) return -1; 297 if (mInfo == null && other.mInfo != null) return 1; 298 299 // Reachable one goes before unreachable one. 300 if (mRssi != Integer.MAX_VALUE && other.mRssi == Integer.MAX_VALUE) return -1; 301 if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1; 302 if (mRssi == Integer.MAX_VALUE && other.mRssi != Integer.MAX_VALUE) return 1; 303 304 // Configured one goes before unconfigured one. 305 if (networkId != WifiConfiguration.INVALID_NETWORK_ID 306 && other.networkId == WifiConfiguration.INVALID_NETWORK_ID) return -1; 307 if (networkId == WifiConfiguration.INVALID_NETWORK_ID 308 && other.networkId != WifiConfiguration.INVALID_NETWORK_ID) return 1; 309 310 // Sort by signal strength. 311 int difference = WifiManager.compareSignalLevel(other.mRssi, mRssi); 312 if (difference != 0) { 313 return difference; 314 } 315 // Sort by ssid. 316 return ssid.compareToIgnoreCase(other.ssid); 317 } 318 319 @Override 320 public boolean equals(Object other) { 321 if (!(other instanceof AccessPoint)) return false; 322 return (this.compareTo((AccessPoint) other) == 0); 323 } 324 325 @Override 326 public int hashCode() { 327 int result = 0; 328 if (mInfo != null) result += 13 * mInfo.hashCode(); 329 result += 19 * mRssi; 330 result += 23 * networkId; 331 result += 29 * ssid.hashCode(); 332 return result; 333 } 334 335 boolean update(ScanResult result) { 336 if (result.seen > mSeen) { 337 mSeen = result.seen; 338 } 339 if (WifiSettings.mVerboseLogging > 0) { 340 if (mScanResultCache == null) { 341 mScanResultCache = new LruCache<String, ScanResult>(32); 342 } 343 mScanResultCache.put(result.BSSID, result); 344 } 345 346 if (ssid.equals(result.SSID) && security == getSecurity(result)) { 347 if (WifiManager.compareSignalLevel(result.level, mRssi) > 0) { 348 int oldLevel = getLevel(); 349 mRssi = result.level; 350 if (getLevel() != oldLevel) { 351 notifyChanged(); 352 } 353 } 354 // This flag only comes from scans, is not easily saved in config 355 if (security == SECURITY_PSK) { 356 pskType = getPskType(result); 357 } 358 mScanResult = result; 359 refresh(); 360 return true; 361 } 362 return false; 363 } 364 365 void update(WifiInfo info, DetailedState state) { 366 boolean reorder = false; 367 if (info != null && networkId != WifiConfiguration.INVALID_NETWORK_ID 368 && networkId == info.getNetworkId()) { 369 reorder = (mInfo == null); 370 mRssi = info.getRssi(); 371 mInfo = info; 372 mState = state; 373 refresh(); 374 } else if (mInfo != null) { 375 reorder = true; 376 mInfo = null; 377 mState = null; 378 refresh(); 379 } 380 if (reorder) { 381 notifyHierarchyChanged(); 382 } 383 } 384 385 int getLevel() { 386 if (mRssi == Integer.MAX_VALUE) { 387 return -1; 388 } 389 return WifiManager.calculateSignalLevel(mRssi, 4); 390 } 391 392 WifiConfiguration getConfig() { 393 return mConfig; 394 } 395 396 WifiInfo getInfo() { 397 return mInfo; 398 } 399 400 DetailedState getState() { 401 return mState; 402 } 403 404 static String removeDoubleQuotes(String string) { 405 int length = string.length(); 406 if ((length > 1) && (string.charAt(0) == '"') 407 && (string.charAt(length - 1) == '"')) { 408 return string.substring(1, length - 1); 409 } 410 return string; 411 } 412 413 static String convertToQuotedString(String string) { 414 return "\"" + string + "\""; 415 } 416 417 /** 418 * Shows or Hides the Summary of an AccessPoint. 419 * 420 * @param showSummary true will show the summary, false will hide the summary 421 */ 422 public void setShowSummary(boolean showSummary){ 423 this.showSummary = showSummary; 424 } 425 426 /** 427 * Returns the visibility status of the WifiConfiguration. 428 * 429 * @return autojoin debugging information 430 * TODO: use a string formatter 431 * ["rssi 5Ghz", "num results on 5GHz" / "rssi 5Ghz", "num results on 5GHz"] 432 * For instance [-40,5/-30,2] 433 */ 434 private String getVisibilityStatus() { 435 StringBuilder visibility = new StringBuilder(); 436 StringBuilder scans24GHz = null; 437 StringBuilder scans5GHz = null; 438 String bssid = null; 439 440 long now = System.currentTimeMillis(); 441 442 if (mInfo != null) { 443 bssid = mInfo.getBSSID(); 444 if (bssid != null) { 445 visibility.append(" ").append(bssid); 446 } 447 visibility.append(" score=").append(mInfo.score); 448 visibility.append(" "); 449 visibility.append(String.format("tx=%.1f,", mInfo.txSuccessRate)); 450 visibility.append(String.format("%.1f,", mInfo.txRetriesRate)); 451 visibility.append(String.format("%.1f ", mInfo.txBadRate)); 452 visibility.append(String.format("rx=%.1f", mInfo.rxSuccessRate)); 453 } 454 455 if (mScanResultCache != null) { 456 int rssi5 = WifiConfiguration.INVALID_RSSI; 457 int rssi24 = WifiConfiguration.INVALID_RSSI; 458 int num5 = 0; 459 int num24 = 0; 460 int numBlackListed = 0; 461 int n24 = 0; // Number scan results we included in the string 462 int n5 = 0; // Number scan results we included in the string 463 Map<String, ScanResult> list = mScanResultCache.snapshot(); 464 // TODO: sort list by RSSI or age 465 for (ScanResult result : list.values()) { 466 if (result.seen == 0) 467 continue; 468 469 if (result.autoJoinStatus != ScanResult.ENABLED) numBlackListed++; 470 471 if (result.frequency >= LOWER_FREQ_5GHZ 472 && result.frequency <= HIGHER_FREQ_5GHZ) { 473 // Strictly speaking: [4915, 5825] 474 // number of known BSSID on 5GHz band 475 num5 = num5 + 1; 476 } else if (result.frequency >= LOWER_FREQ_24GHZ 477 && result.frequency <= HIGHER_FREQ_24GHZ) { 478 // Strictly speaking: [2412, 2482] 479 // number of known BSSID on 2.4Ghz band 480 num24 = num24 + 1; 481 } 482 483 // Ignore results seen, older than 20 seconds 484 if (now - result.seen > VISIBILITY_OUTDATED_AGE_IN_MILLI) continue; 485 486 if (result.frequency >= LOWER_FREQ_5GHZ 487 && result.frequency <= HIGHER_FREQ_5GHZ) { 488 if (result.level > rssi5) { 489 rssi5 = result.level; 490 } 491 if (n5 < 4) { 492 if (scans5GHz == null) scans5GHz = new StringBuilder(); 493 scans5GHz.append(" {").append(result.BSSID); 494 if (bssid != null && result.BSSID.equals(bssid)) scans5GHz.append("*"); 495 scans5GHz.append("=").append(result.frequency); 496 scans5GHz.append(",").append(result.level); 497 if (result.autoJoinStatus != 0) { 498 scans5GHz.append(",st=").append(result.autoJoinStatus); 499 } 500 if (result.numIpConfigFailures != 0) { 501 scans5GHz.append(",ipf=").append(result.numIpConfigFailures); 502 } 503 scans5GHz.append("}"); 504 n5++; 505 } 506 } else if (result.frequency >= LOWER_FREQ_24GHZ 507 && result.frequency <= HIGHER_FREQ_24GHZ) { 508 if (result.level > rssi24) { 509 rssi24 = result.level; 510 } 511 if (n24 < 4) { 512 if (scans24GHz == null) scans24GHz = new StringBuilder(); 513 scans24GHz.append(" {").append(result.BSSID); 514 if (bssid != null && result.BSSID.equals(bssid)) scans24GHz.append("*"); 515 scans24GHz.append("=").append(result.frequency); 516 scans24GHz.append(",").append(result.level); 517 if (result.autoJoinStatus != 0) { 518 scans24GHz.append(",st=").append(result.autoJoinStatus); 519 } 520 if (result.numIpConfigFailures != 0) { 521 scans24GHz.append(",ipf=").append(result.numIpConfigFailures); 522 } 523 scans24GHz.append("}"); 524 n24++; 525 } 526 } 527 } 528 visibility.append(" ["); 529 if (num24 > 0) { 530 visibility.append("(").append(num24).append(")"); 531 if (n24 <= 4) { 532 if (scans24GHz != null) { 533 visibility.append(scans24GHz.toString()); 534 } 535 } else { 536 visibility.append("max=").append(rssi24); 537 if (scans24GHz != null) { 538 visibility.append(",").append(scans24GHz.toString()); 539 } 540 } 541 } 542 visibility.append(";"); 543 if (num5 > 0) { 544 visibility.append("(").append(num5).append(")"); 545 if (n5 <= 4) { 546 if (scans5GHz != null) { 547 visibility.append(scans5GHz.toString()); 548 } 549 } else { 550 visibility.append("max=").append(rssi5); 551 if (scans5GHz != null) { 552 visibility.append(",").append(scans5GHz.toString()); 553 } 554 } 555 } 556 if (numBlackListed > 0) 557 visibility.append("!").append(numBlackListed); 558 visibility.append("]"); 559 } else { 560 if (mRssi != Integer.MAX_VALUE) { 561 visibility.append(" rssi="); 562 visibility.append(mRssi); 563 if (mScanResult != null) { 564 visibility.append(", f="); 565 visibility.append(mScanResult.frequency); 566 } 567 } 568 } 569 570 return visibility.toString(); 571 } 572 573 /** 574 * Updates the title and summary; may indirectly call notifyChanged(). 575 */ 576 private void refresh() { 577 setTitle(ssid); 578 579 final Context context = getContext(); 580 updateIcon(getLevel(), context); 581 582 // Force new summary 583 setSummary(null); 584 585 // Update to new summary 586 StringBuilder summary = new StringBuilder(); 587 588 if (mState != null) { // This is the active connection 589 summary.append(Summary.get(context, mState)); 590 } else if (mConfig != null && ((mConfig.status == WifiConfiguration.Status.DISABLED && 591 mConfig.disableReason != WifiConfiguration.DISABLED_UNKNOWN_REASON) 592 || mConfig.autoJoinStatus 593 >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE)) { 594 if (mConfig.autoJoinStatus 595 >= WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE) { 596 if (mConfig.disableReason == WifiConfiguration.DISABLED_DHCP_FAILURE) { 597 summary.append(context.getString(R.string.wifi_disabled_network_failure)); 598 } else { 599 summary.append(context.getString(R.string.wifi_disabled_password_failure)); 600 } 601 } else { 602 switch (mConfig.disableReason) { 603 case WifiConfiguration.DISABLED_AUTH_FAILURE: 604 summary.append(context.getString(R.string.wifi_disabled_password_failure)); 605 break; 606 case WifiConfiguration.DISABLED_DHCP_FAILURE: 607 case WifiConfiguration.DISABLED_DNS_FAILURE: 608 summary.append(context.getString(R.string.wifi_disabled_network_failure)); 609 break; 610 case WifiConfiguration.DISABLED_UNKNOWN_REASON: 611 case WifiConfiguration.DISABLED_ASSOCIATION_REJECT: 612 summary.append(context.getString(R.string.wifi_disabled_generic)); 613 break; 614 } 615 } 616 } else if (mRssi == Integer.MAX_VALUE) { // Wifi out of range 617 summary.append(context.getString(R.string.wifi_not_in_range)); 618 } else { // In range, not disabled. 619 if (mConfig != null) { // Is saved network 620 summary.append(context.getString(R.string.wifi_remembered)); 621 } 622 } 623 624 if (WifiSettings.mVerboseLogging > 0) { 625 //add RSSI/band information for this config, what was seen up to 6 seconds ago 626 //verbose WiFi Logging is only turned on thru developers settings 627 if (mInfo != null && mState != null) { // This is the active connection 628 summary.append(" (f=" + Integer.toString(mInfo.getFrequency()) + ")"); 629 } 630 summary.append(" " + getVisibilityStatus()); 631 if (mConfig != null && mConfig.autoJoinStatus > 0) { 632 summary.append(" (" + mConfig.autoJoinStatus); 633 if (mConfig.blackListTimestamp > 0) { 634 long now = System.currentTimeMillis(); 635 long diff = (now - mConfig.blackListTimestamp)/1000; 636 long sec = diff%60; //seconds 637 long min = (diff/60)%60; //minutes 638 long hour = (min/60)%60; //hours 639 summary.append(", "); 640 if (hour > 0) summary.append(Long.toString(hour) + "h "); 641 summary.append( Long.toString(min) + "m "); 642 summary.append( Long.toString(sec) + "s "); 643 } 644 summary.append(")"); 645 } 646 } 647 648 if (summary.length() > 0) { 649 setSummary(summary.toString()); 650 } else { 651 showSummary = false; 652 } 653 } 654 655 /** 656 * Generate and save a default wifiConfiguration with common values. 657 * Can only be called for unsecured networks. 658 * @hide 659 */ 660 protected void generateOpenNetworkConfig() { 661 if (security != SECURITY_NONE) 662 throw new IllegalStateException(); 663 if (mConfig != null) 664 return; 665 mConfig = new WifiConfiguration(); 666 mConfig.SSID = AccessPoint.convertToQuotedString(ssid); 667 mConfig.allowedKeyManagement.set(KeyMgmt.NONE); 668 } 669 } 670