1 /* 2 * Copyright (C) 2007 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; 23 import android.net.wifi.ScanResult; 24 import android.net.wifi.WifiConfiguration; 25 import android.net.wifi.WifiInfo; 26 import android.net.wifi.WifiConfiguration.AuthAlgorithm; 27 import android.net.wifi.WifiConfiguration.GroupCipher; 28 import android.net.wifi.WifiConfiguration.KeyMgmt; 29 import android.net.wifi.WifiConfiguration.PairwiseCipher; 30 import android.net.wifi.WifiConfiguration.Protocol; 31 import android.os.Parcel; 32 import android.os.Parcelable; 33 import android.text.TextUtils; 34 import android.util.Log; 35 36 public final class AccessPointState implements Comparable<AccessPointState>, Parcelable { 37 38 private static final String TAG = "AccessPointState"; 39 40 // Constants used for different security types 41 public static final String PSK = "PSK"; 42 public static final String WEP = "WEP"; 43 public static final String EAP = "EAP"; 44 public static final String OPEN = "Open"; 45 46 public static final String[] EAP_METHOD = { "PEAP", "TLS", "TTLS" }; 47 48 /** String present in capabilities if the scan result is ad-hoc */ 49 private static final String ADHOC_CAPABILITY = "[IBSS]"; 50 /** String present in capabilities if the scan result is enterprise secured */ 51 private static final String ENTERPRISE_CAPABILITY = "-EAP-"; 52 53 public static final String BSSID_ANY = "any"; 54 public static final int NETWORK_ID_NOT_SET = -1; 55 /** This should be used with care! */ 56 static final int NETWORK_ID_ANY = -2; 57 58 public static final int MATCH_NONE = 0; 59 public static final int MATCH_WEAK = 1; 60 public static final int MATCH_STRONG = 2; 61 public static final int MATCH_EXACT = 3; 62 63 // Don't set these directly, use the setters. 64 public int networkId; 65 public int priority; 66 public boolean hiddenSsid; 67 public int linkSpeed; 68 public int ipAddress; 69 public String bssid; 70 public String ssid; 71 public int signal; 72 public boolean primary; 73 public boolean seen; 74 public boolean configured; 75 public NetworkInfo.DetailedState status; 76 public String security; 77 public boolean disabled; 78 79 /** 80 * Use this for sorting based on signal strength. It is a heavily-damped 81 * time-averaged weighted signal. 82 */ 83 private float signalForSorting = Float.MIN_VALUE; 84 85 private static final float DAMPING_FACTOR = 0.2f; 86 87 /** 88 * This will be a user entered password, and NOT taken from wpa_supplicant 89 * (since it would give us *) 90 */ 91 private String mPassword; 92 private boolean mConfigHadPassword; 93 94 public static final int WEP_PASSWORD_AUTO = 0; 95 public static final int WEP_PASSWORD_ASCII = 1; 96 public static final int WEP_PASSWORD_HEX = 2; 97 private int mWepPasswordType; 98 99 /* Enterprise Fields */ 100 public static final int IDENTITY = 0; 101 public static final int ANONYMOUS_IDENTITY = 1; 102 public static final int CLIENT_CERT = 2; 103 public static final int CA_CERT = 3; 104 public static final int PRIVATE_KEY = 4; 105 public static final int MAX_ENTRPRISE_FIELD = 5; 106 private String mEnterpriseFields[] = new String[MAX_ENTRPRISE_FIELD]; 107 private String mEap; 108 private String mPhase2; 109 110 private Context mContext; 111 112 /** 113 * If > 0, don't refresh (changes are being batched), use 114 * {@link #blockRefresh()} and {@link #unblockRefresh()} only. 115 */ 116 private int mBlockRefresh; 117 /** 118 * This will be set by {@link #requestRefresh} and shouldn't be written to 119 * elsewhere. 120 */ 121 private boolean mNeedsRefresh; 122 123 private AccessPointStateCallback mCallback; 124 125 private StringBuilder mSummaryBuilder = new StringBuilder(); 126 127 interface AccessPointStateCallback { refreshAccessPointState()128 void refreshAccessPointState(); 129 } 130 AccessPointState(Context context)131 public AccessPointState(Context context) { 132 this(); 133 134 setContext(context); 135 } 136 AccessPointState()137 private AccessPointState() { 138 bssid = BSSID_ANY; 139 ssid = ""; 140 networkId = NETWORK_ID_NOT_SET; 141 hiddenSsid = false; 142 } 143 setContext(Context context)144 void setContext(Context context) { 145 mContext = context; 146 } 147 setNetworkId(int networkId)148 public void setNetworkId(int networkId) { 149 this.networkId = networkId; 150 } 151 setBssid(String bssid)152 public void setBssid(String bssid) { 153 if (bssid != null) { 154 // If the BSSID is a wildcard, do NOT let a specific BSSID replace it 155 if (!this.bssid.equals(BSSID_ANY)) { 156 this.bssid = bssid; 157 } 158 } 159 } 160 getWpaSupplicantBssid()161 private String getWpaSupplicantBssid() { 162 return bssid.equals(BSSID_ANY) ? null : bssid; 163 } 164 convertToQuotedString(String string)165 public static String convertToQuotedString(String string) { 166 if (TextUtils.isEmpty(string)) { 167 return ""; 168 } 169 170 final int lastPos = string.length() - 1; 171 if (lastPos < 0 || (string.charAt(0) == '"' && string.charAt(lastPos) == '"')) { 172 return string; 173 } 174 175 return "\"" + string + "\""; 176 } 177 setPrimary(boolean primary)178 public void setPrimary(boolean primary) { 179 if (this.primary != primary) { 180 this.primary = primary; 181 requestRefresh(); 182 } 183 } 184 setSeen(boolean seen)185 public void setSeen(boolean seen) { 186 if (this.seen != seen) { 187 this.seen = seen; 188 requestRefresh(); 189 } 190 } 191 setDisabled(boolean disabled)192 public void setDisabled(boolean disabled) { 193 if (this.disabled != disabled) { 194 this.disabled = disabled; 195 requestRefresh(); 196 } 197 } 198 setSignal(int signal)199 public void setSignal(int signal) { 200 201 if (signalForSorting == Float.MIN_VALUE) { 202 signalForSorting = signal; 203 } else { 204 signalForSorting = (DAMPING_FACTOR * signal) + ((1-DAMPING_FACTOR) * signalForSorting); 205 } 206 207 if (this.signal != signal) { 208 this.signal = signal; 209 requestRefresh(); 210 } 211 } 212 getHumanReadableSsid()213 public String getHumanReadableSsid() { 214 if (TextUtils.isEmpty(ssid)) { 215 return ""; 216 } 217 218 final int lastPos = ssid.length() - 1; 219 if (ssid.charAt(0) == '"' && ssid.charAt(lastPos) == '"') { 220 return ssid.substring(1, lastPos); 221 } 222 223 return ssid; 224 } 225 setSsid(String ssid)226 public void setSsid(String ssid) { 227 if (ssid != null) { 228 this.ssid = convertToQuotedString(ssid); 229 requestRefresh(); 230 } 231 } 232 setPriority(int priority)233 public void setPriority(int priority) { 234 if (this.priority != priority) { 235 this.priority = priority; 236 requestRefresh(); 237 } 238 } 239 setHiddenSsid(boolean hiddenSsid)240 public void setHiddenSsid(boolean hiddenSsid) { 241 if (this.hiddenSsid != hiddenSsid) { 242 this.hiddenSsid = hiddenSsid; 243 requestRefresh(); 244 } 245 } 246 setLinkSpeed(int linkSpeed)247 public void setLinkSpeed(int linkSpeed) { 248 if (this.linkSpeed != linkSpeed) { 249 this.linkSpeed = linkSpeed; 250 requestRefresh(); 251 } 252 } 253 setIpAddress(int address)254 public void setIpAddress(int address) { 255 if (ipAddress != address) { 256 ipAddress = address; 257 requestRefresh(); 258 } 259 } 260 setConfigured(boolean configured)261 public void setConfigured(boolean configured) { 262 if (this.configured != configured) { 263 this.configured = configured; 264 requestRefresh(); 265 } 266 } 267 setStatus(NetworkInfo.DetailedState status)268 public void setStatus(NetworkInfo.DetailedState status) { 269 if (this.status != status) { 270 this.status = status; 271 requestRefresh(); 272 } 273 } 274 isEnterprise()275 public boolean isEnterprise() { 276 return (AccessPointState.EAP.equals(security)); 277 } 278 setSecurity(String security)279 public void setSecurity(String security) { 280 if (TextUtils.isEmpty(this.security) || !this.security.equals(security)) { 281 this.security = security; 282 requestRefresh(); 283 } 284 } 285 hasSecurity()286 public boolean hasSecurity() { 287 return security != null && !security.contains(OPEN); 288 } 289 getHumanReadableSecurity()290 public String getHumanReadableSecurity() { 291 if (security.equals(OPEN)) return mContext.getString(R.string.wifi_security_open); 292 else if (security.equals(WEP)) return mContext.getString(R.string.wifi_security_wep); 293 else if (security.equals(PSK)) return mContext.getString(R.string.wifi_security_psk); 294 else if (security.equals(EAP)) return mContext.getString(R.string.wifi_security_eap); 295 296 return mContext.getString(R.string.wifi_security_unknown); 297 } 298 updateFromScanResult(ScanResult scanResult)299 public void updateFromScanResult(ScanResult scanResult) { 300 blockRefresh(); 301 302 // We don't keep specific AP BSSIDs and instead leave that as wildcard 303 304 setSeen(true); 305 setSsid(scanResult.SSID); 306 if (networkId == NETWORK_ID_NOT_SET) { 307 // Since ScanResults don't cross-reference network ID, we set it as a wildcard 308 setNetworkId(NETWORK_ID_ANY); 309 } 310 setSignal(scanResult.level); 311 setSecurity(getScanResultSecurity(scanResult)); 312 unblockRefresh(); 313 } 314 315 /** 316 * @return The security of a given {@link ScanResult}. 317 */ getScanResultSecurity(ScanResult scanResult)318 public static String getScanResultSecurity(ScanResult scanResult) { 319 final String cap = scanResult.capabilities; 320 final String[] securityModes = { WEP, PSK, EAP }; 321 for (int i = securityModes.length - 1; i >= 0; i--) { 322 if (cap.contains(securityModes[i])) { 323 return securityModes[i]; 324 } 325 } 326 327 return OPEN; 328 } 329 330 /** 331 * @return Whether the given ScanResult represents an adhoc network. 332 */ isAdhoc(ScanResult scanResult)333 public static boolean isAdhoc(ScanResult scanResult) { 334 return scanResult.capabilities.contains(ADHOC_CAPABILITY); 335 } 336 337 /** 338 * @return Whether the given ScanResult has enterprise security. 339 */ isEnterprise(ScanResult scanResult)340 public static boolean isEnterprise(ScanResult scanResult) { 341 return scanResult.capabilities.contains(ENTERPRISE_CAPABILITY); 342 } 343 updateFromWifiConfiguration(WifiConfiguration wifiConfig)344 public void updateFromWifiConfiguration(WifiConfiguration wifiConfig) { 345 if (wifiConfig != null) { 346 blockRefresh(); 347 setBssid(wifiConfig.BSSID); 348 setNetworkId(wifiConfig.networkId); 349 setPriority(wifiConfig.priority); 350 setHiddenSsid(wifiConfig.hiddenSSID); 351 setSsid(wifiConfig.SSID); 352 setConfigured(true); 353 setDisabled(wifiConfig.status == WifiConfiguration.Status.DISABLED); 354 parseWifiConfigurationSecurity(wifiConfig); 355 unblockRefresh(); 356 } 357 } 358 setPassword(String password)359 public void setPassword(String password) { 360 setPassword(password, WEP_PASSWORD_AUTO); 361 } 362 setPassword(String password, int wepPasswordType)363 public void setPassword(String password, int wepPasswordType) { 364 mPassword = password; 365 mWepPasswordType = wepPasswordType; 366 } 367 368 /* For Enterprise Fields */ setEnterpriseField(int field, String value)369 public void setEnterpriseField(int field, String value) { 370 if ((value != null) && (field >= 0) && (field < MAX_ENTRPRISE_FIELD)) { 371 this.mEnterpriseFields[field] = value; 372 requestRefresh(); 373 } 374 } 375 setPhase2(String phase2)376 public void setPhase2(String phase2) { 377 if (!TextUtils.isEmpty(phase2) && (!phase2.equals("None"))) { 378 mPhase2 = phase2; 379 } 380 } 381 getPhase2()382 public String getPhase2() { 383 return mPhase2; 384 } 385 setEap(int method)386 public void setEap(int method) { 387 mEap = EAP_METHOD[method]; 388 requestRefresh(); 389 } 390 getEap()391 public String getEap() { 392 return mEap; 393 } getEnterpriseField(int field)394 public String getEnterpriseField(int field) { 395 if(field >=0 && field < MAX_ENTRPRISE_FIELD) { 396 return mEnterpriseFields[field]; 397 } 398 return null; 399 } 400 hasPassword()401 public boolean hasPassword() { 402 return !TextUtils.isEmpty(mPassword) || mConfigHadPassword; 403 } 404 hasPassword(WifiConfiguration wifiConfig)405 private static boolean hasPassword(WifiConfiguration wifiConfig) { 406 return !TextUtils.isEmpty(wifiConfig.preSharedKey) 407 || !TextUtils.isEmpty(wifiConfig.wepKeys[0]) 408 || !TextUtils.isEmpty(wifiConfig.wepKeys[1]) 409 || !TextUtils.isEmpty(wifiConfig.wepKeys[2]) 410 || !TextUtils.isEmpty(wifiConfig.wepKeys[3]); 411 } 412 parseWifiConfigurationSecurity(WifiConfiguration wifiConfig)413 private void parseWifiConfigurationSecurity(WifiConfiguration wifiConfig) { 414 setSecurity(getWifiConfigurationSecurity(wifiConfig)); 415 mConfigHadPassword = hasPassword(wifiConfig); 416 } 417 418 /** 419 * @return The security of a given {@link WifiConfiguration}. 420 */ getWifiConfigurationSecurity(WifiConfiguration wifiConfig)421 public static String getWifiConfigurationSecurity(WifiConfiguration wifiConfig) { 422 if (!TextUtils.isEmpty(wifiConfig.eap.value())) { 423 return EAP; 424 } else if (!TextUtils.isEmpty(wifiConfig.preSharedKey)) { 425 return PSK; 426 } else if (!TextUtils.isEmpty(wifiConfig.wepKeys[0])) { 427 return WEP; 428 } 429 return OPEN; 430 } 431 updateFromWifiInfo(WifiInfo wifiInfo, NetworkInfo.DetailedState state)432 public void updateFromWifiInfo(WifiInfo wifiInfo, NetworkInfo.DetailedState state) { 433 if (wifiInfo != null) { 434 blockRefresh(); 435 setBssid(wifiInfo.getBSSID()); 436 setLinkSpeed(wifiInfo.getLinkSpeed()); 437 setNetworkId(wifiInfo.getNetworkId()); 438 setIpAddress(wifiInfo.getIpAddress()); 439 setSsid(wifiInfo.getSSID()); 440 if (state != null) { 441 setStatus(state); 442 } 443 setHiddenSsid(wifiInfo.getHiddenSSID()); 444 unblockRefresh(); 445 } 446 } 447 448 /** 449 * @return Whether this AP can be connected to at the moment. 450 */ isConnectable()451 public boolean isConnectable() { 452 return !primary && seen; 453 } 454 455 /** 456 * @return Whether this AP can be forgotten at the moment. 457 */ isForgetable()458 public boolean isForgetable() { 459 return configured; 460 } 461 462 /** 463 * Updates the state as if it were never configured. 464 * <p> 465 * Note: This will not pass the forget call to the Wi-Fi API. 466 */ forget()467 public void forget() { 468 blockRefresh(); 469 setConfigured(false); 470 setNetworkId(NETWORK_ID_NOT_SET); 471 setPrimary(false); 472 setStatus(null); 473 setDisabled(false); 474 unblockRefresh(); 475 } 476 updateWifiConfiguration(WifiConfiguration config)477 public void updateWifiConfiguration(WifiConfiguration config) { 478 config.BSSID = getWpaSupplicantBssid(); 479 config.priority = priority; 480 config.hiddenSSID = hiddenSsid; 481 config.SSID = convertToQuotedString(ssid); 482 config.eap.setValue(mEap); 483 484 if (!TextUtils.isEmpty(mPhase2)) { 485 config.phase2.setValue(convertToQuotedString("auth=" + mPhase2)); 486 } else { 487 config.phase2.setValue(null); 488 } 489 if (!TextUtils.isEmpty(mEnterpriseFields[IDENTITY])) { 490 config.identity.setValue( 491 convertToQuotedString(mEnterpriseFields[IDENTITY])); 492 } else { 493 config.identity.setValue(null); 494 } 495 if (!TextUtils.isEmpty(mEnterpriseFields[ANONYMOUS_IDENTITY])) { 496 config.anonymous_identity.setValue(convertToQuotedString( 497 mEnterpriseFields[ANONYMOUS_IDENTITY])); 498 } else { 499 config.anonymous_identity.setValue(null); 500 } 501 if (!TextUtils.isEmpty(mEnterpriseFields[CLIENT_CERT])) { 502 config.client_cert.setValue(convertToQuotedString( 503 mEnterpriseFields[CLIENT_CERT])); 504 } else { 505 config.client_cert.setValue(null); 506 } 507 if (!TextUtils.isEmpty(mEnterpriseFields[CA_CERT])) { 508 config.ca_cert.setValue(convertToQuotedString( 509 mEnterpriseFields[CA_CERT])); 510 } else { 511 config.ca_cert.setValue(null); 512 } 513 if (!TextUtils.isEmpty(mEnterpriseFields[PRIVATE_KEY])) { 514 config.private_key.setValue(convertToQuotedString( 515 mEnterpriseFields[PRIVATE_KEY])); 516 } else { 517 config.private_key.setValue(null); 518 } 519 setupSecurity(config); 520 } 521 setupSecurity(WifiConfiguration config)522 private void setupSecurity(WifiConfiguration config) { 523 config.allowedAuthAlgorithms.clear(); 524 config.allowedGroupCiphers.clear(); 525 config.allowedKeyManagement.clear(); 526 config.allowedPairwiseCiphers.clear(); 527 config.allowedProtocols.clear(); 528 529 if (TextUtils.isEmpty(security)) { 530 security = OPEN; 531 Log.w(TAG, "Empty security, assuming open"); 532 } 533 534 if (security.equals(WEP)) { 535 // If password is empty, it should be left untouched 536 if (!TextUtils.isEmpty(mPassword)) { 537 if (mWepPasswordType == WEP_PASSWORD_AUTO) { 538 if (isHexWepKey(mPassword)) { 539 config.wepKeys[0] = mPassword; 540 } else { 541 config.wepKeys[0] = convertToQuotedString(mPassword); 542 } 543 } else { 544 config.wepKeys[0] = mWepPasswordType == WEP_PASSWORD_ASCII 545 ? convertToQuotedString(mPassword) 546 : mPassword; 547 } 548 } 549 config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN); 550 config.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED); 551 config.allowedKeyManagement.set(KeyMgmt.NONE); 552 config.wepTxKeyIndex = 0; 553 } else if (security.equals(PSK)){ 554 // If password is empty, it should be left untouched 555 if (!TextUtils.isEmpty(mPassword)) { 556 if (mPassword.length() == 64 && isHex(mPassword)) { 557 // Goes unquoted as hex 558 config.preSharedKey = mPassword; 559 } else { 560 // Goes quoted as ASCII 561 config.preSharedKey = convertToQuotedString(mPassword); 562 } 563 } 564 } else if (security.equals(EAP)) { 565 config.allowedKeyManagement.set(KeyMgmt.WPA_EAP); 566 config.allowedKeyManagement.set(KeyMgmt.IEEE8021X); 567 if (!TextUtils.isEmpty(mPassword)) { 568 config.password.setValue(convertToQuotedString(mPassword)); 569 } 570 } else if (security.equals(OPEN)) { 571 config.allowedKeyManagement.set(KeyMgmt.NONE); 572 } 573 } 574 isHexWepKey(String wepKey)575 private static boolean isHexWepKey(String wepKey) { 576 final int len = wepKey.length(); 577 578 // WEP-40, WEP-104, and some vendors using 256-bit WEP (WEP-232?) 579 if (len != 10 && len != 26 && len != 58) { 580 return false; 581 } 582 583 return isHex(wepKey); 584 } 585 isHex(String key)586 private static boolean isHex(String key) { 587 for (int i = key.length() - 1; i >= 0; i--) { 588 final char c = key.charAt(i); 589 if (!(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f')) { 590 return false; 591 } 592 } 593 594 return true; 595 } 596 setCallback(AccessPointStateCallback callback)597 public void setCallback(AccessPointStateCallback callback) { 598 mCallback = callback; 599 } 600 blockRefresh()601 void blockRefresh() { 602 mBlockRefresh++; 603 } 604 unblockRefresh()605 void unblockRefresh() { 606 if (--mBlockRefresh == 0 && mNeedsRefresh) { 607 requestRefresh(); 608 } 609 } 610 requestRefresh()611 private void requestRefresh() { 612 if (mBlockRefresh > 0) { 613 mNeedsRefresh = true; 614 return; 615 } 616 617 if (mCallback != null) { 618 mCallback.refreshAccessPointState(); 619 } 620 621 mNeedsRefresh = false; 622 } 623 624 /** 625 * {@inheritDoc} 626 * @see #hashCode() 627 * @see #equals(Object) 628 */ matches(int otherNetworkId, String otherBssid, String otherSsid, String otherSecurity)629 public int matches(int otherNetworkId, String otherBssid, String otherSsid, 630 String otherSecurity) { 631 632 // Whenever this method is touched, please ensure #equals and #hashCode 633 // still work with the changes here! 634 635 if (otherSsid == null) { 636 if (WifiLayer.LOGV) { 637 Log.w(TAG, "BSSID: " + otherBssid + ", SSID: " + otherSsid); 638 } 639 return MATCH_NONE; 640 } 641 642 /* 643 * If we both have 'security' set, it must match (an open network still 644 * has 'security' set to OPEN) 645 */ 646 if (security != null && otherSecurity != null) { 647 if (!security.equals(otherSecurity)) { 648 return MATCH_NONE; 649 } 650 } 651 652 // WifiConfiguration gives an empty bssid as a BSSID wildcard 653 if (TextUtils.isEmpty(otherBssid)) { 654 otherBssid = AccessPointState.BSSID_ANY; 655 } 656 657 final boolean networkIdMatches = networkId == otherNetworkId; 658 if (!networkIdMatches && networkId != NETWORK_ID_ANY && otherNetworkId != NETWORK_ID_ANY) { 659 // Network IDs don't match (e.g., 1 & 2 or unset & 1) and neither is a wildcard 660 return MATCH_NONE; 661 } 662 663 if (networkIdMatches && otherNetworkId != NETWORK_ID_NOT_SET 664 && otherNetworkId != NETWORK_ID_ANY) { 665 // Network ID matches (they're set to the same ID) 666 return MATCH_EXACT; 667 } 668 669 // So now, network IDs aren't set or at least one is a wildcard 670 671 final boolean bssidMatches = bssid.equals(otherBssid); 672 final boolean otherBssidIsWildcard = otherBssid.equals(BSSID_ANY); 673 if (bssidMatches && !otherBssidIsWildcard) { 674 // BSSID matches (and neither is a wildcard) 675 return MATCH_STRONG; 676 } 677 678 if (!bssidMatches && !bssid.equals(BSSID_ANY) && !otherBssidIsWildcard) { 679 // BSSIDs don't match (e.g., 00:24:21:21:42:12 & 42:12:44:21:22:52) 680 // and neither is a wildcard 681 return MATCH_NONE; 682 } 683 684 // So now, BSSIDs are both wildcards 685 686 final boolean ssidMatches = ssid.equals(otherSsid); 687 if (ssidMatches) { 688 // SSID matches 689 return MATCH_WEAK; 690 } 691 692 return MATCH_NONE; 693 } 694 695 /** 696 * {@inheritDoc} 697 * @see #matches(int, String, String) 698 * @see #equals(Object) 699 */ 700 @Override hashCode()701 public int hashCode() { 702 // Two equal() objects must have same hashCode. 703 // With Wi-Fi, the broadest match is if two SSIDs are the same. The finer-grained matches 704 // imply this (for example, the same network IDs means the same WifiConfiguration which 705 // means the same SSID). 706 // See #matches for the exact matching algorithm we use. 707 return ssid != null ? ssid.hashCode() : 0; 708 } 709 710 /** 711 * {@inheritDoc} 712 * @see #matches(int, String, String) 713 * @see #hashCode() 714 */ 715 @Override equals(Object o)716 public boolean equals(Object o) { 717 if (!o.getClass().equals(getClass())) { 718 return false; 719 } 720 721 final AccessPointState other = (AccessPointState) o; 722 723 // To see which conditions cause two AccessPointStates to be equal, see 724 // where #matches returns MATCH_WEAK or greater. 725 726 return matches(other.networkId, other.bssid, other.ssid, other.security) >= MATCH_WEAK; 727 } 728 matchesWifiConfiguration(WifiConfiguration wifiConfig)729 public int matchesWifiConfiguration(WifiConfiguration wifiConfig) { 730 String security = getWifiConfigurationSecurity(wifiConfig); 731 return matches(wifiConfig.networkId, wifiConfig.BSSID, wifiConfig.SSID, security); 732 } 733 getSummarizedStatus()734 String getSummarizedStatus() { 735 StringBuilder sb = mSummaryBuilder; 736 sb.delete(0, sb.length()); 737 738 if (primary && status != null) { 739 buildSummary(sb, WifiStatus.getPrintable(mContext, status), true); 740 741 } else if (!seen) { 742 buildSummary(sb, mContext.getString(R.string.summary_not_in_range), true); 743 744 // Remembered comes second in this case 745 if (!primary && configured) { 746 buildSummary(sb, mContext.getString(R.string.summary_remembered), true); 747 } 748 749 } else { 750 if (configured && disabled) { 751 // The connection failure overrides all in this case 752 return mContext.getString(R.string.summary_connection_failed); 753 } 754 755 // Remembered comes first in this case 756 if (!primary && configured) { 757 buildSummary(sb, mContext.getString(R.string.summary_remembered), true); 758 } 759 760 // If it is seen (and not the primary), show the security type 761 String verboseSecurity = getVerboseSecurity(); 762 if (verboseSecurity != null) { 763 buildSummary(sb, verboseSecurity, true); 764 } 765 } 766 767 return sb.toString(); 768 } 769 getVerboseSecurity()770 private String getVerboseSecurity() { 771 if (WEP.equals(security)) { 772 return mContext.getString(R.string.wifi_security_verbose_wep); 773 } else if (PSK.equals(security)) { 774 return mContext.getString(R.string.wifi_security_verbose_psk); 775 } else if (EAP.equals(security)) { 776 return mContext.getString(R.string.wifi_security_verbose_eap); 777 } else { 778 return null; 779 } 780 } 781 buildSummary(StringBuilder sb, String string, boolean autoUpperCaseFirstLetter)782 private void buildSummary(StringBuilder sb, String string, boolean autoUpperCaseFirstLetter) { 783 if (sb.length() == 0) { 784 if (autoUpperCaseFirstLetter && string.length() > 1 785 && Character.isLowerCase(string.charAt(0)) 786 && !Character.isUpperCase(string.charAt(1))) { 787 sb.append(Character.toUpperCase(string.charAt(0))).append(string, 1, 788 string.length()); 789 } else { 790 sb.append(string); 791 } 792 } else { 793 sb.append(", "); 794 sb.append(string); 795 } 796 } 797 compareTo(AccessPointState other)798 public int compareTo(AccessPointState other) { 799 // This ranks the states for displaying in the AP list, not for 800 // connecting to (wpa_supplicant does that using the WifiConfiguration's 801 // priority field). 802 803 // Clarity > efficiency, of this logic: 804 int comparison; 805 806 // Primary 807 comparison = (other.primary ? 1 : 0) - (primary ? 1 : 0); 808 if (comparison != 0) return comparison; 809 810 // Currently seen (similar to, but not always the same as within range) 811 comparison = (other.seen ? 1 : 0) - (seen ? 1 : 0); 812 if (comparison != 0) return comparison; 813 814 // Configured 815 comparison = (other.configured ? 1 : 0) - (configured ? 1 : 0); 816 if (comparison != 0) return comparison; 817 818 if (!configured) { 819 // Neither are configured 820 821 // Open network 822 comparison = (hasSecurity() ? 1 : 0) - (other.hasSecurity() ? 1 : 0); 823 if (comparison != 0) return comparison; 824 } 825 826 // Signal strength 827 comparison = (int) (other.signalForSorting - signalForSorting); 828 if (comparison != 0) return comparison; 829 830 // Alphabetical 831 return ssid.compareToIgnoreCase(other.ssid); 832 } 833 toString()834 public String toString() { 835 return ssid + " (" + bssid + ", " + networkId + ", " + super.toString() + ")"; 836 } 837 838 /** Implement the Parcelable interface */ writeToParcel(Parcel dest, int flags)839 public void writeToParcel(Parcel dest, int flags) { 840 dest.writeString(bssid); 841 dest.writeInt(configured ? 1 : 0); 842 dest.writeInt(ipAddress); 843 dest.writeInt(linkSpeed); 844 dest.writeInt(networkId); 845 dest.writeInt(primary ? 1 : 0); 846 dest.writeInt(priority); 847 dest.writeInt(hiddenSsid ? 1 : 0); 848 dest.writeString(security); 849 dest.writeInt(seen ? 1 : 0); 850 dest.writeInt(disabled ? 1 : 0); 851 dest.writeInt(signal); 852 dest.writeString(ssid); 853 dest.writeString(status != null ? status.toString() : null); 854 dest.writeString(mPassword); 855 dest.writeInt(mConfigHadPassword ? 1 : 0); 856 dest.writeInt(mWepPasswordType); 857 } 858 859 /** Implement the Parcelable interface */ describeContents()860 public int describeContents() { 861 return 0; 862 } 863 864 /** Implement the Parcelable interface */ 865 public static final Creator<AccessPointState> CREATOR = 866 new Creator<AccessPointState>() { 867 public AccessPointState createFromParcel(Parcel in) { 868 AccessPointState state = new AccessPointState(); 869 state.bssid = in.readString(); 870 state.configured = in.readInt() == 1; 871 state.ipAddress = in.readInt(); 872 state.linkSpeed = in.readInt(); 873 state.networkId = in.readInt(); 874 state.primary = in.readInt() == 1; 875 state.priority = in.readInt(); 876 state.hiddenSsid = in.readInt() == 1; 877 state.security = in.readString(); 878 state.seen = in.readInt() == 1; 879 state.disabled = in.readInt() == 1; 880 state.signal = in.readInt(); 881 state.ssid = in.readString(); 882 String statusStr = in.readString(); 883 if (statusStr != null) { 884 state.status = NetworkInfo.DetailedState.valueOf(statusStr); 885 } 886 state.mPassword = in.readString(); 887 state.mConfigHadPassword = in.readInt() == 1; 888 state.mWepPasswordType = in.readInt(); 889 return state; 890 } 891 892 public AccessPointState[] newArray(int size) { 893 return new AccessPointState[size]; 894 } 895 }; 896 897 898 } 899