1 /* 2 * Copyright (C) 2015 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.settingslib.wifi; 18 19 import android.annotation.IntDef; 20 import android.annotation.MainThread; 21 import android.annotation.Nullable; 22 import android.app.AppGlobals; 23 import android.content.Context; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.IPackageManager; 26 import android.content.pm.PackageManager; 27 import android.net.ConnectivityManager; 28 import android.net.NetworkCapabilities; 29 import android.net.NetworkInfo; 30 import android.net.NetworkInfo.DetailedState; 31 import android.net.NetworkInfo.State; 32 import android.net.NetworkKey; 33 import android.net.NetworkScoreManager; 34 import android.net.NetworkScorerAppData; 35 import android.net.ScoredNetwork; 36 import android.net.wifi.IWifiManager; 37 import android.net.wifi.ScanResult; 38 import android.net.wifi.WifiConfiguration; 39 import android.net.wifi.WifiConfiguration.KeyMgmt; 40 import android.net.wifi.WifiEnterpriseConfig; 41 import android.net.wifi.WifiInfo; 42 import android.net.wifi.WifiManager; 43 import android.net.wifi.WifiNetworkScoreCache; 44 import android.net.wifi.hotspot2.OsuProvider; 45 import android.net.wifi.hotspot2.PasspointConfiguration; 46 import android.net.wifi.hotspot2.ProvisioningCallback; 47 import android.os.Bundle; 48 import android.os.Parcelable; 49 import android.os.RemoteException; 50 import android.os.ServiceManager; 51 import android.os.SystemClock; 52 import android.os.UserHandle; 53 import android.provider.Settings; 54 import android.text.TextUtils; 55 import android.util.ArraySet; 56 import android.util.Log; 57 import android.util.Pair; 58 59 import androidx.annotation.GuardedBy; 60 import androidx.annotation.NonNull; 61 62 import com.android.internal.annotations.VisibleForTesting; 63 import com.android.internal.util.CollectionUtils; 64 import com.android.settingslib.R; 65 import com.android.settingslib.utils.ThreadUtils; 66 67 import java.lang.annotation.Retention; 68 import java.lang.annotation.RetentionPolicy; 69 import java.util.ArrayList; 70 import java.util.Collection; 71 import java.util.Collections; 72 import java.util.HashMap; 73 import java.util.Iterator; 74 import java.util.List; 75 import java.util.Map; 76 import java.util.Set; 77 import java.util.concurrent.atomic.AtomicInteger; 78 79 /** 80 * Represents a selectable Wifi Network for use in various wifi selection menus backed by 81 * {@link WifiTracker}. 82 * 83 * <p>An AccessPoint, which would be more fittingly named "WifiNetwork", is an aggregation of 84 * {@link ScanResult ScanResults} along with pertinent metadata (e.g. current connection info, 85 * network scores) required to successfully render the network to the user. 86 */ 87 public class AccessPoint implements Comparable<AccessPoint> { 88 static final String TAG = "SettingsLib.AccessPoint"; 89 90 /** 91 * Lower bound on the 2.4 GHz (802.11b/g/n) WLAN channels 92 */ 93 public static final int LOWER_FREQ_24GHZ = 2400; 94 95 /** 96 * Upper bound on the 2.4 GHz (802.11b/g/n) WLAN channels 97 */ 98 public static final int HIGHER_FREQ_24GHZ = 2500; 99 100 /** 101 * Lower bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels 102 */ 103 public static final int LOWER_FREQ_5GHZ = 4900; 104 105 /** 106 * Upper bound on the 5.0 GHz (802.11a/h/j/n/ac) WLAN channels 107 */ 108 public static final int HIGHER_FREQ_5GHZ = 5900; 109 110 /** The key which identifies this AccessPoint grouping. */ 111 private String mKey; 112 113 /** 114 * Synchronization lock for managing concurrency between main and worker threads. 115 * 116 * <p>This lock should be held for all modifications to {@link #mScanResults} and 117 * {@link #mExtraScanResults}. 118 */ 119 private final Object mLock = new Object(); 120 121 @IntDef({Speed.NONE, Speed.SLOW, Speed.MODERATE, Speed.FAST, Speed.VERY_FAST}) 122 @Retention(RetentionPolicy.SOURCE) 123 public @interface Speed { 124 /** 125 * Constant value representing an unlabeled / unscored network. 126 */ 127 int NONE = 0; 128 /** 129 * Constant value representing a slow speed network connection. 130 */ 131 int SLOW = 5; 132 /** 133 * Constant value representing a medium speed network connection. 134 */ 135 int MODERATE = 10; 136 /** 137 * Constant value representing a fast speed network connection. 138 */ 139 int FAST = 20; 140 /** 141 * Constant value representing a very fast speed network connection. 142 */ 143 int VERY_FAST = 30; 144 } 145 146 /** The underlying set of scan results comprising this AccessPoint. */ 147 @GuardedBy("mLock") 148 private final ArraySet<ScanResult> mScanResults = new ArraySet<>(); 149 150 /** 151 * Extra set of unused scan results corresponding to this AccessPoint for verbose logging 152 * purposes, such as a set of Passpoint roaming scan results when home scans are available. 153 */ 154 @GuardedBy("mLock") 155 private final ArraySet<ScanResult> mExtraScanResults = new ArraySet<>(); 156 157 /** 158 * Map of BSSIDs to scored networks for individual bssids. 159 * 160 * <p>This cache should not be evicted with scan results, as the values here are used to 161 * generate a fallback in the absence of scores for the visible APs. 162 */ 163 private final Map<String, TimestampedScoredNetwork> mScoredNetworkCache = new HashMap<>(); 164 165 static final String KEY_NETWORKINFO = "key_networkinfo"; 166 static final String KEY_WIFIINFO = "key_wifiinfo"; 167 static final String KEY_SSID = "key_ssid"; 168 static final String KEY_SECURITY = "key_security"; 169 static final String KEY_SPEED = "key_speed"; 170 static final String KEY_PSKTYPE = "key_psktype"; 171 static final String KEY_SCANRESULTS = "key_scanresults"; 172 static final String KEY_SCOREDNETWORKCACHE = "key_scorednetworkcache"; 173 static final String KEY_CONFIG = "key_config"; 174 static final String KEY_FQDN = "key_fqdn"; 175 static final String KEY_PROVIDER_FRIENDLY_NAME = "key_provider_friendly_name"; 176 static final String KEY_IS_CARRIER_AP = "key_is_carrier_ap"; 177 static final String KEY_CARRIER_AP_EAP_TYPE = "key_carrier_ap_eap_type"; 178 static final String KEY_CARRIER_NAME = "key_carrier_name"; 179 static final String KEY_EAPTYPE = "eap_psktype"; 180 static final String KEY_IS_PSK_SAE_TRANSITION_MODE = "key_is_psk_sae_transition_mode"; 181 static final String KEY_IS_OWE_TRANSITION_MODE = "key_is_owe_transition_mode"; 182 static final AtomicInteger sLastId = new AtomicInteger(0); 183 184 /* 185 * NOTE: These constants for security and PSK types are saved to the bundle in saveWifiState, 186 * and sent across IPC. The numeric values should remain stable, otherwise the changes will need 187 * to be synced with other unbundled users of this library. 188 */ 189 public static final int SECURITY_NONE = 0; 190 public static final int SECURITY_WEP = 1; 191 public static final int SECURITY_PSK = 2; 192 public static final int SECURITY_EAP = 3; 193 public static final int SECURITY_OWE = 4; 194 public static final int SECURITY_SAE = 5; 195 public static final int SECURITY_EAP_SUITE_B = 6; 196 public static final int SECURITY_MAX_VAL = 7; // Has to be the last 197 198 private static final int PSK_UNKNOWN = 0; 199 private static final int PSK_WPA = 1; 200 private static final int PSK_WPA2 = 2; 201 private static final int PSK_WPA_WPA2 = 3; 202 203 private static final int EAP_UNKNOWN = 0; 204 private static final int EAP_WPA = 1; // WPA-EAP 205 private static final int EAP_WPA2_WPA3 = 2; // RSN-EAP 206 207 /** 208 * The number of distinct wifi levels. 209 * 210 * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}. 211 */ 212 public static final int SIGNAL_LEVELS = 5; 213 214 public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE; 215 216 public static final String KEY_PREFIX_AP = "AP:"; 217 public static final String KEY_PREFIX_FQDN = "FQDN:"; 218 public static final String KEY_PREFIX_OSU = "OSU:"; 219 220 private final Context mContext; 221 222 private WifiManager mWifiManager; 223 private WifiManager.ActionListener mConnectListener; 224 225 private String ssid; 226 private String bssid; 227 private int security; 228 private int networkId = WifiConfiguration.INVALID_NETWORK_ID; 229 230 private int pskType = PSK_UNKNOWN; 231 private int mEapType = EAP_UNKNOWN; 232 233 private WifiConfiguration mConfig; 234 235 private int mRssi = UNREACHABLE_RSSI; 236 237 private WifiInfo mInfo; 238 private NetworkInfo mNetworkInfo; 239 AccessPointListener mAccessPointListener; 240 241 private Object mTag; 242 243 @Speed private int mSpeed = Speed.NONE; 244 private boolean mIsScoredNetworkMetered = false; 245 246 /** 247 * Information associated with the {@link PasspointConfiguration}. Only maintaining 248 * the relevant info to preserve spaces. 249 */ 250 private String mFqdn; 251 private String mProviderFriendlyName; 252 private boolean mIsRoaming = false; 253 254 private boolean mIsCarrierAp = false; 255 256 private OsuProvider mOsuProvider; 257 258 private String mOsuStatus; 259 private String mOsuFailure; 260 private boolean mOsuProvisioningComplete = false; 261 262 private boolean mIsPskSaeTransitionMode = false; 263 private boolean mIsOweTransitionMode = false; 264 265 /** 266 * The EAP type {@link WifiEnterpriseConfig.Eap} associated with this AP if it is a carrier AP. 267 */ 268 private int mCarrierApEapType = WifiEnterpriseConfig.Eap.NONE; 269 private String mCarrierName = null; 270 AccessPoint(Context context, Bundle savedState)271 public AccessPoint(Context context, Bundle savedState) { 272 mContext = context; 273 274 if (savedState.containsKey(KEY_CONFIG)) { 275 mConfig = savedState.getParcelable(KEY_CONFIG); 276 } 277 if (mConfig != null) { 278 loadConfig(mConfig); 279 } 280 if (savedState.containsKey(KEY_SSID)) { 281 ssid = savedState.getString(KEY_SSID); 282 } 283 if (savedState.containsKey(KEY_SECURITY)) { 284 security = savedState.getInt(KEY_SECURITY); 285 } 286 if (savedState.containsKey(KEY_SPEED)) { 287 mSpeed = savedState.getInt(KEY_SPEED); 288 } 289 if (savedState.containsKey(KEY_PSKTYPE)) { 290 pskType = savedState.getInt(KEY_PSKTYPE); 291 } 292 if (savedState.containsKey(KEY_EAPTYPE)) { 293 mEapType = savedState.getInt(KEY_EAPTYPE); 294 } 295 mInfo = savedState.getParcelable(KEY_WIFIINFO); 296 if (savedState.containsKey(KEY_NETWORKINFO)) { 297 mNetworkInfo = savedState.getParcelable(KEY_NETWORKINFO); 298 } 299 if (savedState.containsKey(KEY_SCANRESULTS)) { 300 Parcelable[] scanResults = savedState.getParcelableArray(KEY_SCANRESULTS); 301 mScanResults.clear(); 302 for (Parcelable result : scanResults) { 303 mScanResults.add((ScanResult) result); 304 } 305 } 306 if (savedState.containsKey(KEY_SCOREDNETWORKCACHE)) { 307 ArrayList<TimestampedScoredNetwork> scoredNetworkArrayList = 308 savedState.getParcelableArrayList(KEY_SCOREDNETWORKCACHE); 309 for (TimestampedScoredNetwork timedScore : scoredNetworkArrayList) { 310 mScoredNetworkCache.put(timedScore.getScore().networkKey.wifiKey.bssid, timedScore); 311 } 312 } 313 if (savedState.containsKey(KEY_FQDN)) { 314 mFqdn = savedState.getString(KEY_FQDN); 315 } 316 if (savedState.containsKey(KEY_PROVIDER_FRIENDLY_NAME)) { 317 mProviderFriendlyName = savedState.getString(KEY_PROVIDER_FRIENDLY_NAME); 318 } 319 if (savedState.containsKey(KEY_IS_CARRIER_AP)) { 320 mIsCarrierAp = savedState.getBoolean(KEY_IS_CARRIER_AP); 321 } 322 if (savedState.containsKey(KEY_CARRIER_AP_EAP_TYPE)) { 323 mCarrierApEapType = savedState.getInt(KEY_CARRIER_AP_EAP_TYPE); 324 } 325 if (savedState.containsKey(KEY_CARRIER_NAME)) { 326 mCarrierName = savedState.getString(KEY_CARRIER_NAME); 327 } 328 if (savedState.containsKey(KEY_IS_PSK_SAE_TRANSITION_MODE)) { 329 mIsPskSaeTransitionMode = savedState.getBoolean(KEY_IS_PSK_SAE_TRANSITION_MODE); 330 } 331 if (savedState.containsKey(KEY_IS_OWE_TRANSITION_MODE)) { 332 mIsOweTransitionMode = savedState.getBoolean(KEY_IS_OWE_TRANSITION_MODE); 333 } 334 335 update(mConfig, mInfo, mNetworkInfo); 336 337 // Calculate required fields 338 updateKey(); 339 updateBestRssiInfo(); 340 } 341 342 /** 343 * Creates an AccessPoint with only a WifiConfiguration. This is used for the saved networks 344 * page. 345 */ AccessPoint(Context context, WifiConfiguration config)346 public AccessPoint(Context context, WifiConfiguration config) { 347 mContext = context; 348 loadConfig(config); 349 updateKey(); 350 } 351 352 /** 353 * Initialize an AccessPoint object for a {@link PasspointConfiguration}. This is mainly 354 * used by "Saved Networks" page for managing the saved {@link PasspointConfiguration}. 355 */ AccessPoint(Context context, PasspointConfiguration config)356 public AccessPoint(Context context, PasspointConfiguration config) { 357 mContext = context; 358 mFqdn = config.getHomeSp().getFqdn(); 359 mProviderFriendlyName = config.getHomeSp().getFriendlyName(); 360 updateKey(); 361 } 362 363 /** 364 * Initialize an AccessPoint object for a Passpoint network. 365 */ AccessPoint(@onNull Context context, @NonNull WifiConfiguration config, @Nullable Collection<ScanResult> homeScans, @Nullable Collection<ScanResult> roamingScans)366 public AccessPoint(@NonNull Context context, @NonNull WifiConfiguration config, 367 @Nullable Collection<ScanResult> homeScans, 368 @Nullable Collection<ScanResult> roamingScans) { 369 mContext = context; 370 networkId = config.networkId; 371 mConfig = config; 372 mFqdn = config.FQDN; 373 setScanResultsPasspoint(homeScans, roamingScans); 374 updateKey(); 375 } 376 377 /** 378 * Initialize an AccessPoint object for a Passpoint OSU Provider. 379 */ AccessPoint(@onNull Context context, @NonNull OsuProvider provider, @NonNull Collection<ScanResult> results)380 public AccessPoint(@NonNull Context context, @NonNull OsuProvider provider, 381 @NonNull Collection<ScanResult> results) { 382 mContext = context; 383 mOsuProvider = provider; 384 setScanResults(results); 385 updateKey(); 386 } 387 AccessPoint(Context context, Collection<ScanResult> results)388 AccessPoint(Context context, Collection<ScanResult> results) { 389 mContext = context; 390 setScanResults(results); 391 updateKey(); 392 } 393 loadConfig(WifiConfiguration config)394 @VisibleForTesting void loadConfig(WifiConfiguration config) { 395 ssid = (config.SSID == null ? "" : removeDoubleQuotes(config.SSID)); 396 bssid = config.BSSID; 397 security = getSecurity(config); 398 networkId = config.networkId; 399 mConfig = config; 400 } 401 402 /** Updates {@link #mKey} and should only called upon object creation/initialization. */ updateKey()403 private void updateKey() { 404 // TODO(sghuman): Consolidate Key logic on ScanResultMatchInfo 405 if (isPasspoint()) { 406 mKey = getKey(mConfig); 407 } else if (isPasspointConfig()) { 408 mKey = getKey(mFqdn); 409 } else if (isOsuProvider()) { 410 mKey = getKey(mOsuProvider); 411 } else { // Non-Passpoint AP 412 mKey = getKey(getSsidStr(), getBssid(), getSecurity()); 413 } 414 } 415 416 /** 417 * Returns a negative integer, zero, or a positive integer if this AccessPoint is less than, 418 * equal to, or greater than the other AccessPoint. 419 * 420 * Sort order rules for AccessPoints: 421 * 1. Active before inactive 422 * 2. Reachable before unreachable 423 * 3. Saved before unsaved 424 * 4. Network speed value 425 * 5. Stronger signal before weaker signal 426 * 6. SSID alphabetically 427 * 428 * Note that AccessPoints with a signal are usually also Reachable, 429 * and will thus appear before unreachable saved AccessPoints. 430 */ 431 @Override compareTo(@onNull AccessPoint other)432 public int compareTo(@NonNull AccessPoint other) { 433 // Active one goes first. 434 if (isActive() && !other.isActive()) return -1; 435 if (!isActive() && other.isActive()) return 1; 436 437 // Reachable one goes before unreachable one. 438 if (isReachable() && !other.isReachable()) return -1; 439 if (!isReachable() && other.isReachable()) return 1; 440 441 // Configured (saved) one goes before unconfigured one. 442 if (isSaved() && !other.isSaved()) return -1; 443 if (!isSaved() && other.isSaved()) return 1; 444 445 // Faster speeds go before slower speeds - but only if visible change in speed label 446 if (getSpeed() != other.getSpeed()) { 447 return other.getSpeed() - getSpeed(); 448 } 449 450 // Sort by signal strength, bucketed by level 451 int difference = WifiManager.calculateSignalLevel(other.mRssi, SIGNAL_LEVELS) 452 - WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS); 453 if (difference != 0) { 454 return difference; 455 } 456 457 // Sort by title. 458 difference = getTitle().compareToIgnoreCase(other.getTitle()); 459 if (difference != 0) { 460 return difference; 461 } 462 463 // Do a case sensitive comparison to distinguish SSIDs that differ in case only 464 return getSsidStr().compareTo(other.getSsidStr()); 465 } 466 467 @Override equals(Object other)468 public boolean equals(Object other) { 469 if (!(other instanceof AccessPoint)) return false; 470 return (this.compareTo((AccessPoint) other) == 0); 471 } 472 473 @Override hashCode()474 public int hashCode() { 475 int result = 0; 476 if (mInfo != null) result += 13 * mInfo.hashCode(); 477 result += 19 * mRssi; 478 result += 23 * networkId; 479 result += 29 * ssid.hashCode(); 480 return result; 481 } 482 483 @Override toString()484 public String toString() { 485 StringBuilder builder = new StringBuilder().append("AccessPoint(") 486 .append(ssid); 487 if (bssid != null) { 488 builder.append(":").append(bssid); 489 } 490 if (isSaved()) { 491 builder.append(',').append("saved"); 492 } 493 if (isActive()) { 494 builder.append(',').append("active"); 495 } 496 if (isEphemeral()) { 497 builder.append(',').append("ephemeral"); 498 } 499 if (isConnectable()) { 500 builder.append(',').append("connectable"); 501 } 502 if ((security != SECURITY_NONE) && (security != SECURITY_OWE)) { 503 builder.append(',').append(securityToString(security, pskType)); 504 } 505 builder.append(",level=").append(getLevel()); 506 if (mSpeed != Speed.NONE) { 507 builder.append(",speed=").append(mSpeed); 508 } 509 builder.append(",metered=").append(isMetered()); 510 511 if (isVerboseLoggingEnabled()) { 512 builder.append(",rssi=").append(mRssi); 513 synchronized (mLock) { 514 builder.append(",scan cache size=").append(mScanResults.size() 515 + mExtraScanResults.size()); 516 } 517 } 518 519 return builder.append(')').toString(); 520 } 521 522 /** 523 * Updates the AccessPoint rankingScore, metering, and speed, returning true if the data has 524 * changed. 525 * 526 * @param scoreCache The score cache to use to retrieve scores 527 * @param scoringUiEnabled Whether to show scoring and badging UI 528 * @param maxScoreCacheAgeMillis the maximum age in milliseconds of scores to consider when 529 * generating speed labels 530 */ update( WifiNetworkScoreCache scoreCache, boolean scoringUiEnabled, long maxScoreCacheAgeMillis)531 boolean update( 532 WifiNetworkScoreCache scoreCache, 533 boolean scoringUiEnabled, 534 long maxScoreCacheAgeMillis) { 535 boolean scoreChanged = false; 536 if (scoringUiEnabled) { 537 scoreChanged = updateScores(scoreCache, maxScoreCacheAgeMillis); 538 } 539 return updateMetered(scoreCache) || scoreChanged; 540 } 541 542 /** 543 * Updates the AccessPoint rankingScore and speed, returning true if the data has changed. 544 * 545 * <p>Any cached {@link TimestampedScoredNetwork} objects older than the given max age in millis 546 * will be removed when this method is invoked. 547 * 548 * <p>Precondition: {@link #mRssi} is up to date before invoking this method. 549 * 550 * @param scoreCache The score cache to use to retrieve scores 551 * @param maxScoreCacheAgeMillis the maximum age in milliseconds of scores to consider when 552 * generating speed labels 553 * 554 * @return true if the set speed has changed 555 */ updateScores(WifiNetworkScoreCache scoreCache, long maxScoreCacheAgeMillis)556 private boolean updateScores(WifiNetworkScoreCache scoreCache, long maxScoreCacheAgeMillis) { 557 long nowMillis = SystemClock.elapsedRealtime(); 558 synchronized (mLock) { 559 for (ScanResult result : mScanResults) { 560 ScoredNetwork score = scoreCache.getScoredNetwork(result); 561 if (score == null) { 562 continue; 563 } 564 TimestampedScoredNetwork timedScore = mScoredNetworkCache.get(result.BSSID); 565 if (timedScore == null) { 566 mScoredNetworkCache.put( 567 result.BSSID, new TimestampedScoredNetwork(score, nowMillis)); 568 } else { 569 // Update data since the has been seen in the score cache 570 timedScore.update(score, nowMillis); 571 } 572 } 573 } 574 575 // Remove old cached networks 576 long evictionCutoff = nowMillis - maxScoreCacheAgeMillis; 577 Iterator<TimestampedScoredNetwork> iterator = mScoredNetworkCache.values().iterator(); 578 iterator.forEachRemaining(timestampedScoredNetwork -> { 579 if (timestampedScoredNetwork.getUpdatedTimestampMillis() < evictionCutoff) { 580 iterator.remove(); 581 } 582 }); 583 584 return updateSpeed(); 585 } 586 587 /** 588 * Updates the internal speed, returning true if the update resulted in a speed label change. 589 */ updateSpeed()590 private boolean updateSpeed() { 591 int oldSpeed = mSpeed; 592 mSpeed = generateAverageSpeedForSsid(); 593 594 boolean changed = oldSpeed != mSpeed; 595 if(isVerboseLoggingEnabled() && changed) { 596 Log.i(TAG, String.format("%s: Set speed to %d", ssid, mSpeed)); 597 } 598 return changed; 599 } 600 601 /** Creates a speed value for the current {@link #mRssi} by averaging all non zero badges. */ generateAverageSpeedForSsid()602 @Speed private int generateAverageSpeedForSsid() { 603 if (mScoredNetworkCache.isEmpty()) { 604 return Speed.NONE; 605 } 606 607 if (Log.isLoggable(TAG, Log.DEBUG)) { 608 Log.d(TAG, String.format("Generating fallbackspeed for %s using cache: %s", 609 getSsidStr(), mScoredNetworkCache)); 610 } 611 612 // TODO(b/63073866): If flickering issues persist, consider mapping using getLevel rather 613 // than specific rssi value so score doesn't change without a visible wifi bar change. This 614 // issue is likely to be more evident for the active AP whose RSSI value is not half-lifed. 615 616 int count = 0; 617 int totalSpeed = 0; 618 for (TimestampedScoredNetwork timedScore : mScoredNetworkCache.values()) { 619 int speed = timedScore.getScore().calculateBadge(mRssi); 620 if (speed != Speed.NONE) { 621 count++; 622 totalSpeed += speed; 623 } 624 } 625 int speed = count == 0 ? Speed.NONE : totalSpeed / count; 626 if (isVerboseLoggingEnabled()) { 627 Log.i(TAG, String.format("%s generated fallback speed is: %d", getSsidStr(), speed)); 628 } 629 return roundToClosestSpeedEnum(speed); 630 } 631 632 /** 633 * Updates the AccessPoint's metering based on {@link ScoredNetwork#meteredHint}, returning 634 * true if the metering changed. 635 */ updateMetered(WifiNetworkScoreCache scoreCache)636 private boolean updateMetered(WifiNetworkScoreCache scoreCache) { 637 boolean oldMetering = mIsScoredNetworkMetered; 638 mIsScoredNetworkMetered = false; 639 640 if (isActive() && mInfo != null) { 641 NetworkKey key = NetworkKey.createFromWifiInfo(mInfo); 642 ScoredNetwork score = scoreCache.getScoredNetwork(key); 643 if (score != null) { 644 mIsScoredNetworkMetered |= score.meteredHint; 645 } 646 } else { 647 synchronized (mLock) { 648 for (ScanResult result : mScanResults) { 649 ScoredNetwork score = scoreCache.getScoredNetwork(result); 650 if (score == null) { 651 continue; 652 } 653 mIsScoredNetworkMetered |= score.meteredHint; 654 } 655 } 656 } 657 return oldMetering == mIsScoredNetworkMetered; 658 } 659 660 /** 661 * Generates an AccessPoint key for a given scan result 662 * 663 * @param context 664 * @param result Scan result 665 * @return AccessPoint key 666 */ getKey(Context context, ScanResult result)667 public static String getKey(Context context, ScanResult result) { 668 return getKey(result.SSID, result.BSSID, getSecurity(context, result)); 669 } 670 671 /** 672 * Returns the AccessPoint key for a WifiConfiguration. 673 * This will return a special Passpoint key if the config is for Passpoint. 674 */ getKey(WifiConfiguration config)675 public static String getKey(WifiConfiguration config) { 676 if (config.isPasspoint()) { 677 return getKey(config.FQDN); 678 } else { 679 return getKey(removeDoubleQuotes(config.SSID), config.BSSID, getSecurity(config)); 680 } 681 } 682 683 /** 684 * Returns the AccessPoint key corresponding to a Passpoint network by its FQDN. 685 */ getKey(String fqdn)686 public static String getKey(String fqdn) { 687 return new StringBuilder() 688 .append(KEY_PREFIX_FQDN) 689 .append(fqdn).toString(); 690 } 691 692 /** 693 * Returns the AccessPoint key corresponding to the OsuProvider. 694 */ getKey(OsuProvider provider)695 public static String getKey(OsuProvider provider) { 696 return new StringBuilder() 697 .append(KEY_PREFIX_OSU) 698 .append(provider.getFriendlyName()) 699 .append(',') 700 .append(provider.getServerUri()).toString(); 701 } 702 703 /** 704 * Returns the AccessPoint key for a normal non-Passpoint network by ssid/bssid and security. 705 */ getKey(String ssid, String bssid, int security)706 private static String getKey(String ssid, String bssid, int security) { 707 StringBuilder builder = new StringBuilder(); 708 builder.append(KEY_PREFIX_AP); 709 if (TextUtils.isEmpty(ssid)) { 710 builder.append(bssid); 711 } else { 712 builder.append(ssid); 713 } 714 builder.append(',').append(security); 715 return builder.toString(); 716 } 717 getKey()718 public String getKey() { 719 return mKey; 720 } 721 722 /** 723 * Determines if the other AccessPoint represents the same network as this AccessPoint 724 */ matches(AccessPoint other)725 public boolean matches(AccessPoint other) { 726 if (isPasspoint() || isPasspointConfig() || isOsuProvider()) { 727 return getKey().equals(other.getKey()); 728 } 729 730 if (!isSameSsidOrBssid(other)) { 731 return false; 732 } 733 734 final int otherApSecurity = other.getSecurity(); 735 if (mIsPskSaeTransitionMode) { 736 if (otherApSecurity == SECURITY_SAE && getWifiManager().isWpa3SaeSupported()) { 737 return true; 738 } else if (otherApSecurity == SECURITY_PSK) { 739 return true; 740 } 741 } else { 742 if ((security == SECURITY_SAE || security == SECURITY_PSK) 743 && other.isPskSaeTransitionMode()) { 744 return true; 745 } 746 } 747 748 if (mIsOweTransitionMode) { 749 if (otherApSecurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) { 750 return true; 751 } else if (otherApSecurity == SECURITY_NONE) { 752 return true; 753 } 754 } else { 755 if ((security == SECURITY_OWE || security == SECURITY_NONE) 756 && other.isOweTransitionMode()) { 757 return true; 758 } 759 } 760 761 return security == other.getSecurity(); 762 } 763 matches(WifiConfiguration config)764 public boolean matches(WifiConfiguration config) { 765 if (config.isPasspoint()) { 766 return (isPasspoint() && config.FQDN.equals(mConfig.FQDN)); 767 } 768 769 if (!ssid.equals(removeDoubleQuotes(config.SSID)) 770 || (mConfig != null && mConfig.shared != config.shared)) { 771 return false; 772 } 773 774 final int configSecurity = getSecurity(config); 775 if (mIsPskSaeTransitionMode) { 776 if (configSecurity == SECURITY_SAE && getWifiManager().isWpa3SaeSupported()) { 777 return true; 778 } else if (configSecurity == SECURITY_PSK) { 779 return true; 780 } 781 } 782 783 if (mIsOweTransitionMode) { 784 if (configSecurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) { 785 return true; 786 } else if (configSecurity == SECURITY_NONE) { 787 return true; 788 } 789 } 790 791 return security == getSecurity(config); 792 } 793 matches(WifiConfiguration config, WifiInfo wifiInfo)794 private boolean matches(WifiConfiguration config, WifiInfo wifiInfo) { 795 if (config == null || wifiInfo == null) { 796 return false; 797 } 798 if (!config.isPasspoint() && !isSameSsidOrBssid(wifiInfo)) { 799 return false; 800 } 801 return matches(config); 802 } 803 804 @VisibleForTesting matches(ScanResult scanResult)805 boolean matches(ScanResult scanResult) { 806 if (scanResult == null) { 807 return false; 808 } 809 if (isPasspoint() || isOsuProvider()) { 810 throw new IllegalStateException("Should not matches a Passpoint by ScanResult"); 811 } 812 813 if (!isSameSsidOrBssid(scanResult)) { 814 return false; 815 } 816 817 if (mIsPskSaeTransitionMode) { 818 if (scanResult.capabilities.contains("SAE") 819 && getWifiManager().isWpa3SaeSupported()) { 820 return true; 821 } else if (scanResult.capabilities.contains("PSK")) { 822 return true; 823 } 824 } else { 825 if ((security == SECURITY_SAE || security == SECURITY_PSK) 826 && AccessPoint.isPskSaeTransitionMode(scanResult)) { 827 return true; 828 } 829 } 830 831 if (mIsOweTransitionMode) { 832 final int scanResultSccurity = getSecurity(mContext, scanResult); 833 if (scanResultSccurity == SECURITY_OWE && getWifiManager().isEnhancedOpenSupported()) { 834 return true; 835 } else if (scanResultSccurity == SECURITY_NONE) { 836 return true; 837 } 838 } else { 839 if ((security == SECURITY_OWE || security == SECURITY_NONE) 840 && AccessPoint.isOweTransitionMode(scanResult)) { 841 return true; 842 } 843 } 844 845 return security == getSecurity(mContext, scanResult); 846 } 847 getConfig()848 public WifiConfiguration getConfig() { 849 return mConfig; 850 } 851 getPasspointFqdn()852 public String getPasspointFqdn() { 853 return mFqdn; 854 } 855 clearConfig()856 public void clearConfig() { 857 mConfig = null; 858 networkId = WifiConfiguration.INVALID_NETWORK_ID; 859 } 860 getInfo()861 public WifiInfo getInfo() { 862 return mInfo; 863 } 864 865 /** 866 * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1. 867 * 868 * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will 869 * always return at least 0. 870 */ getLevel()871 public int getLevel() { 872 return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS); 873 } 874 getRssi()875 public int getRssi() { 876 return mRssi; 877 } 878 879 /** 880 * Returns the underlying scan result set. 881 * 882 * <p>Callers should not modify this set. 883 */ getScanResults()884 public Set<ScanResult> getScanResults() { 885 Set<ScanResult> allScans = new ArraySet<>(); 886 synchronized (mLock) { 887 allScans.addAll(mScanResults); 888 allScans.addAll(mExtraScanResults); 889 } 890 return allScans; 891 } 892 getScoredNetworkCache()893 public Map<String, TimestampedScoredNetwork> getScoredNetworkCache() { 894 return mScoredNetworkCache; 895 } 896 897 /** 898 * Updates {@link #mRssi} and sets scan result information to that of the best RSSI scan result. 899 * 900 * <p>If the given connection is active, the existing value of {@link #mRssi} will be returned. 901 * If the given AccessPoint is not active, a value will be calculated from previous scan 902 * results, returning the best RSSI for all matching AccessPoints averaged with the previous 903 * value. If the access point is not connected and there are no scan results, the rssi will be 904 * set to {@link #UNREACHABLE_RSSI}. 905 */ updateBestRssiInfo()906 private void updateBestRssiInfo() { 907 if (this.isActive()) { 908 return; 909 } 910 911 ScanResult bestResult = null; 912 int bestRssi = UNREACHABLE_RSSI; 913 synchronized (mLock) { 914 for (ScanResult result : mScanResults) { 915 if (result.level > bestRssi) { 916 bestRssi = result.level; 917 bestResult = result; 918 } 919 } 920 } 921 922 // Set the rssi to the average of the current rssi and the previous rssi. 923 if (bestRssi != UNREACHABLE_RSSI && mRssi != UNREACHABLE_RSSI) { 924 mRssi = (mRssi + bestRssi) / 2; 925 } else { 926 mRssi = bestRssi; 927 } 928 929 if (bestResult != null) { 930 ssid = bestResult.SSID; 931 bssid = bestResult.BSSID; 932 security = getSecurity(mContext, bestResult); 933 if (security == SECURITY_PSK || security == SECURITY_SAE) { 934 pskType = getPskType(bestResult); 935 } 936 if (security == SECURITY_EAP) { 937 mEapType = getEapType(bestResult); 938 } 939 940 mIsPskSaeTransitionMode = AccessPoint.isPskSaeTransitionMode(bestResult); 941 mIsOweTransitionMode = AccessPoint.isOweTransitionMode(bestResult); 942 943 mIsCarrierAp = bestResult.isCarrierAp; 944 mCarrierApEapType = bestResult.carrierApEapType; 945 mCarrierName = bestResult.carrierName; 946 } 947 // Update the config SSID of a Passpoint network to that of the best RSSI 948 if (isPasspoint()) { 949 mConfig.SSID = convertToQuotedString(ssid); 950 } 951 } 952 953 /** 954 * Returns if the network should be considered metered. 955 */ isMetered()956 public boolean isMetered() { 957 return mIsScoredNetworkMetered 958 || WifiConfiguration.isMetered(mConfig, mInfo); 959 } 960 getNetworkInfo()961 public NetworkInfo getNetworkInfo() { 962 return mNetworkInfo; 963 } 964 getSecurity()965 public int getSecurity() { 966 return security; 967 } 968 getSecurityString(boolean concise)969 public String getSecurityString(boolean concise) { 970 Context context = mContext; 971 if (isPasspoint() || isPasspointConfig()) { 972 return concise ? context.getString(R.string.wifi_security_short_eap) : 973 context.getString(R.string.wifi_security_eap); 974 } 975 976 if (mIsPskSaeTransitionMode) { 977 return concise ? context.getString(R.string.wifi_security_short_psk_sae) : 978 context.getString(R.string.wifi_security_psk_sae); 979 } 980 981 switch(security) { 982 case SECURITY_EAP: 983 switch (mEapType) { 984 case EAP_WPA: 985 return concise ? context.getString(R.string.wifi_security_short_eap_wpa) : 986 context.getString(R.string.wifi_security_eap_wpa); 987 case EAP_WPA2_WPA3: 988 return concise 989 ? context.getString(R.string.wifi_security_short_eap_wpa2_wpa3) : 990 context.getString(R.string.wifi_security_eap_wpa2_wpa3); 991 case EAP_UNKNOWN: 992 default: 993 return concise 994 ? context.getString(R.string.wifi_security_short_eap) : 995 context.getString(R.string.wifi_security_eap); 996 } 997 case SECURITY_EAP_SUITE_B: 998 return concise ? context.getString(R.string.wifi_security_short_eap_suiteb) : 999 context.getString(R.string.wifi_security_eap_suiteb); 1000 case SECURITY_PSK: 1001 switch (pskType) { 1002 case PSK_WPA: 1003 return concise ? context.getString(R.string.wifi_security_short_wpa) : 1004 context.getString(R.string.wifi_security_wpa); 1005 case PSK_WPA2: 1006 return concise ? context.getString(R.string.wifi_security_short_wpa2) : 1007 context.getString(R.string.wifi_security_wpa2); 1008 case PSK_WPA_WPA2: 1009 return concise ? context.getString(R.string.wifi_security_short_wpa_wpa2) : 1010 context.getString(R.string.wifi_security_wpa_wpa2); 1011 case PSK_UNKNOWN: 1012 default: 1013 return concise ? context.getString(R.string.wifi_security_short_psk_generic) 1014 : context.getString(R.string.wifi_security_psk_generic); 1015 } 1016 case SECURITY_WEP: 1017 return concise ? context.getString(R.string.wifi_security_short_wep) : 1018 context.getString(R.string.wifi_security_wep); 1019 case SECURITY_SAE: 1020 return concise ? context.getString(R.string.wifi_security_short_sae) : 1021 context.getString(R.string.wifi_security_sae); 1022 case SECURITY_OWE: 1023 return concise ? context.getString(R.string.wifi_security_short_owe) : 1024 context.getString(R.string.wifi_security_owe); 1025 case SECURITY_NONE: 1026 default: 1027 return concise ? "" : context.getString(R.string.wifi_security_none); 1028 } 1029 } 1030 getSsidStr()1031 public String getSsidStr() { 1032 return ssid; 1033 } 1034 getBssid()1035 public String getBssid() { 1036 return bssid; 1037 } 1038 getSsid()1039 public CharSequence getSsid() { 1040 return ssid; 1041 } 1042 1043 /** 1044 * Returns the name associated with the stored config. 1045 * @deprecated Please use {@link #getTitle()} instead to get the display name of an AccessPoint. 1046 */ 1047 @Deprecated getConfigName()1048 public String getConfigName() { 1049 if (mConfig != null && mConfig.isPasspoint()) { 1050 return mConfig.providerFriendlyName; 1051 } else if (mFqdn != null) { 1052 return mProviderFriendlyName; 1053 } else { 1054 return ssid; 1055 } 1056 } 1057 getDetailedState()1058 public DetailedState getDetailedState() { 1059 if (mNetworkInfo != null) { 1060 return mNetworkInfo.getDetailedState(); 1061 } 1062 Log.w(TAG, "NetworkInfo is null, cannot return detailed state"); 1063 return null; 1064 } 1065 isCarrierAp()1066 public boolean isCarrierAp() { 1067 return mIsCarrierAp; 1068 } 1069 getCarrierApEapType()1070 public int getCarrierApEapType() { 1071 return mCarrierApEapType; 1072 } 1073 getCarrierName()1074 public String getCarrierName() { 1075 return mCarrierName; 1076 } 1077 getSavedNetworkSummary()1078 public String getSavedNetworkSummary() { 1079 WifiConfiguration config = mConfig; 1080 if (config != null) { 1081 PackageManager pm = mContext.getPackageManager(); 1082 String systemName = pm.getNameForUid(android.os.Process.SYSTEM_UID); 1083 int userId = UserHandle.getUserId(config.creatorUid); 1084 ApplicationInfo appInfo = null; 1085 if (config.creatorName != null && config.creatorName.equals(systemName)) { 1086 appInfo = mContext.getApplicationInfo(); 1087 } else { 1088 try { 1089 IPackageManager ipm = AppGlobals.getPackageManager(); 1090 appInfo = ipm.getApplicationInfo(config.creatorName, 0 /* flags */, userId); 1091 } catch (RemoteException rex) { 1092 } 1093 } 1094 if (appInfo != null && 1095 !appInfo.packageName.equals(mContext.getString(R.string.settings_package)) && 1096 !appInfo.packageName.equals( 1097 mContext.getString(R.string.certinstaller_package))) { 1098 return mContext.getString(R.string.saved_network, appInfo.loadLabel(pm)); 1099 } 1100 } 1101 return ""; 1102 } 1103 1104 /** 1105 * Returns the display title for the AccessPoint, such as for an AccessPointPreference's title. 1106 */ getTitle()1107 public String getTitle() { 1108 if (isPasspoint()) { 1109 return mConfig.providerFriendlyName; 1110 } else if (isPasspointConfig()) { 1111 return mProviderFriendlyName; 1112 } else if (isOsuProvider()) { 1113 return mOsuProvider.getFriendlyName(); 1114 } else { 1115 return getSsidStr(); 1116 } 1117 } 1118 getSummary()1119 public String getSummary() { 1120 return getSettingsSummary(); 1121 } 1122 getSettingsSummary()1123 public String getSettingsSummary() { 1124 return getSettingsSummary(false /*convertSavedAsDisconnected*/); 1125 } 1126 1127 /** 1128 * Returns the summary for the AccessPoint. 1129 */ getSettingsSummary(boolean convertSavedAsDisconnected)1130 public String getSettingsSummary(boolean convertSavedAsDisconnected) { 1131 // Update to new summary 1132 StringBuilder summary = new StringBuilder(); 1133 1134 if (isOsuProvider()) { 1135 if (mOsuProvisioningComplete) { 1136 summary.append(mContext.getString(R.string.osu_sign_up_complete)); 1137 } else if (mOsuFailure != null) { 1138 summary.append(mOsuFailure); 1139 } else if (mOsuStatus != null) { 1140 summary.append(mOsuStatus); 1141 } else { 1142 summary.append(mContext.getString(R.string.tap_to_sign_up)); 1143 } 1144 } else if (isActive()) { 1145 if (getDetailedState() == DetailedState.CONNECTED && mIsCarrierAp) { 1146 // This is the active connection on a carrier AP 1147 summary.append(String.format(mContext.getString(R.string.connected_via_carrier), 1148 mCarrierName)); 1149 } else { 1150 summary.append(getSummary(mContext, /* ssid */ null, getDetailedState(), 1151 mInfo != null && mInfo.isEphemeral(), 1152 mInfo != null ? mInfo.getNetworkSuggestionOrSpecifierPackageName() : null)); 1153 } 1154 } else { // not active 1155 if (mConfig != null && mConfig.hasNoInternetAccess()) { 1156 int messageID = mConfig.getNetworkSelectionStatus().isNetworkPermanentlyDisabled() 1157 ? R.string.wifi_no_internet_no_reconnect 1158 : R.string.wifi_no_internet; 1159 summary.append(mContext.getString(messageID)); 1160 } else if (mConfig != null && !mConfig.getNetworkSelectionStatus().isNetworkEnabled()) { 1161 WifiConfiguration.NetworkSelectionStatus networkStatus = 1162 mConfig.getNetworkSelectionStatus(); 1163 switch (networkStatus.getNetworkSelectionDisableReason()) { 1164 case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE: 1165 summary.append(mContext.getString(R.string.wifi_disabled_password_failure)); 1166 break; 1167 case WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD: 1168 summary.append(mContext.getString(R.string.wifi_check_password_try_again)); 1169 break; 1170 case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE: 1171 case WifiConfiguration.NetworkSelectionStatus.DISABLED_DNS_FAILURE: 1172 summary.append(mContext.getString(R.string.wifi_disabled_network_failure)); 1173 break; 1174 case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION: 1175 summary.append(mContext.getString(R.string.wifi_disabled_generic)); 1176 break; 1177 } 1178 } else if (mConfig != null && mConfig.getNetworkSelectionStatus().isNotRecommended()) { 1179 summary.append(mContext.getString( 1180 R.string.wifi_disabled_by_recommendation_provider)); 1181 } else if (mIsCarrierAp) { 1182 summary.append(String.format(mContext.getString( 1183 R.string.available_via_carrier), mCarrierName)); 1184 } else if (!isReachable()) { // Wifi out of range 1185 summary.append(mContext.getString(R.string.wifi_not_in_range)); 1186 } else { // In range, not disabled. 1187 if (mConfig != null) { // Is saved network 1188 // Last attempt to connect to this failed. Show reason why 1189 switch (mConfig.recentFailure.getAssociationStatus()) { 1190 case WifiConfiguration.RecentFailure.STATUS_AP_UNABLE_TO_HANDLE_NEW_STA: 1191 summary.append(mContext.getString( 1192 R.string.wifi_ap_unable_to_handle_new_sta)); 1193 break; 1194 default: 1195 if (convertSavedAsDisconnected) { 1196 // Disconnected 1197 summary.append(mContext.getString(R.string.wifi_disconnected)); 1198 } else { 1199 // "Saved" 1200 summary.append(mContext.getString(R.string.wifi_remembered)); 1201 } 1202 break; 1203 } 1204 } 1205 } 1206 } 1207 1208 1209 1210 if (isVerboseLoggingEnabled()) { 1211 summary.append(WifiUtils.buildLoggingSummary(this, mConfig)); 1212 } 1213 1214 if (mConfig != null && (WifiUtils.isMeteredOverridden(mConfig) || mConfig.meteredHint)) { 1215 return mContext.getResources().getString( 1216 R.string.preference_summary_default_combination, 1217 WifiUtils.getMeteredLabel(mContext, mConfig), 1218 summary.toString()); 1219 } 1220 1221 // If Speed label and summary are both present, use the preference combination to combine 1222 // the two, else return the non-null one. 1223 if (getSpeedLabel() != null && summary.length() != 0) { 1224 return mContext.getResources().getString( 1225 R.string.preference_summary_default_combination, 1226 getSpeedLabel(), 1227 summary.toString()); 1228 } else if (getSpeedLabel() != null) { 1229 return getSpeedLabel(); 1230 } else { 1231 return summary.toString(); 1232 } 1233 } 1234 1235 /** 1236 * Return whether this is the active connection. 1237 * For ephemeral connections (networkId is invalid), this returns false if the network is 1238 * disconnected. 1239 */ isActive()1240 public boolean isActive() { 1241 return mNetworkInfo != null && 1242 (networkId != WifiConfiguration.INVALID_NETWORK_ID || 1243 mNetworkInfo.getState() != State.DISCONNECTED); 1244 } 1245 isConnectable()1246 public boolean isConnectable() { 1247 return getLevel() != -1 && getDetailedState() == null; 1248 } 1249 isEphemeral()1250 public boolean isEphemeral() { 1251 return mInfo != null && mInfo.isEphemeral() && 1252 mNetworkInfo != null && mNetworkInfo.getState() != State.DISCONNECTED; 1253 } 1254 1255 /** 1256 * Return true if this AccessPoint represents a Passpoint AP. 1257 */ isPasspoint()1258 public boolean isPasspoint() { 1259 return mConfig != null && mConfig.isPasspoint(); 1260 } 1261 1262 /** 1263 * Return true if this AccessPoint represents a Passpoint provider configuration. 1264 */ isPasspointConfig()1265 public boolean isPasspointConfig() { 1266 return mFqdn != null && mConfig == null; 1267 } 1268 1269 /** 1270 * Return true if this AccessPoint represents an OSU Provider. 1271 */ isOsuProvider()1272 public boolean isOsuProvider() { 1273 return mOsuProvider != null; 1274 } 1275 1276 /** 1277 * Starts the OSU Provisioning flow. 1278 */ startOsuProvisioning(@ullable WifiManager.ActionListener connectListener)1279 public void startOsuProvisioning(@Nullable WifiManager.ActionListener connectListener) { 1280 mConnectListener = connectListener; 1281 1282 getWifiManager().startSubscriptionProvisioning( 1283 mOsuProvider, 1284 mContext.getMainExecutor(), 1285 new AccessPointProvisioningCallback() 1286 ); 1287 } 1288 1289 /** 1290 * Return whether the given {@link WifiInfo} is for this access point. 1291 * If the current AP does not have a network Id then the config is used to 1292 * match based on SSID and security. 1293 */ isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info)1294 private boolean isInfoForThisAccessPoint(WifiConfiguration config, WifiInfo info) { 1295 if (info.isOsuAp() || mOsuStatus != null) { 1296 return (info.isOsuAp() && mOsuStatus != null); 1297 } else if (info.isPasspointAp() || isPasspoint()) { 1298 return (info.isPasspointAp() && isPasspoint() 1299 && TextUtils.equals(info.getPasspointFqdn(), mConfig.FQDN)); 1300 } 1301 1302 if (networkId != WifiConfiguration.INVALID_NETWORK_ID) { 1303 return networkId == info.getNetworkId(); 1304 } else if (config != null) { 1305 return matches(config, info); 1306 } else { 1307 // Might be an ephemeral connection with no WifiConfiguration. Try matching on SSID. 1308 // (Note that we only do this if the WifiConfiguration explicitly equals INVALID). 1309 // TODO: Handle hex string SSIDs. 1310 return TextUtils.equals(removeDoubleQuotes(info.getSSID()), ssid); 1311 } 1312 } 1313 isSaved()1314 public boolean isSaved() { 1315 return mConfig != null; 1316 } 1317 getTag()1318 public Object getTag() { 1319 return mTag; 1320 } 1321 setTag(Object tag)1322 public void setTag(Object tag) { 1323 mTag = tag; 1324 } 1325 1326 /** 1327 * Generate and save a default wifiConfiguration with common values. 1328 * Can only be called for unsecured networks. 1329 */ generateOpenNetworkConfig()1330 public void generateOpenNetworkConfig() { 1331 if ((security != SECURITY_NONE) && (security != SECURITY_OWE)) { 1332 throw new IllegalStateException(); 1333 } 1334 if (mConfig != null) 1335 return; 1336 mConfig = new WifiConfiguration(); 1337 mConfig.SSID = AccessPoint.convertToQuotedString(ssid); 1338 1339 if (security == SECURITY_NONE || !getWifiManager().isEasyConnectSupported()) { 1340 mConfig.allowedKeyManagement.set(KeyMgmt.NONE); 1341 } else { 1342 mConfig.allowedKeyManagement.set(KeyMgmt.OWE); 1343 mConfig.requirePMF = true; 1344 } 1345 } 1346 saveWifiState(Bundle savedState)1347 public void saveWifiState(Bundle savedState) { 1348 if (ssid != null) savedState.putString(KEY_SSID, getSsidStr()); 1349 savedState.putInt(KEY_SECURITY, security); 1350 savedState.putInt(KEY_SPEED, mSpeed); 1351 savedState.putInt(KEY_PSKTYPE, pskType); 1352 savedState.putInt(KEY_EAPTYPE, mEapType); 1353 if (mConfig != null) savedState.putParcelable(KEY_CONFIG, mConfig); 1354 savedState.putParcelable(KEY_WIFIINFO, mInfo); 1355 synchronized (mLock) { 1356 savedState.putParcelableArray(KEY_SCANRESULTS, 1357 mScanResults.toArray(new Parcelable[mScanResults.size() 1358 + mExtraScanResults.size()])); 1359 } 1360 savedState.putParcelableArrayList(KEY_SCOREDNETWORKCACHE, 1361 new ArrayList<>(mScoredNetworkCache.values())); 1362 if (mNetworkInfo != null) { 1363 savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo); 1364 } 1365 if (mFqdn != null) { 1366 savedState.putString(KEY_FQDN, mFqdn); 1367 } 1368 if (mProviderFriendlyName != null) { 1369 savedState.putString(KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName); 1370 } 1371 savedState.putBoolean(KEY_IS_CARRIER_AP, mIsCarrierAp); 1372 savedState.putInt(KEY_CARRIER_AP_EAP_TYPE, mCarrierApEapType); 1373 savedState.putString(KEY_CARRIER_NAME, mCarrierName); 1374 savedState.putBoolean(KEY_IS_PSK_SAE_TRANSITION_MODE, mIsPskSaeTransitionMode); 1375 savedState.putBoolean(KEY_IS_OWE_TRANSITION_MODE, mIsOweTransitionMode); 1376 } 1377 setListener(AccessPointListener listener)1378 public void setListener(AccessPointListener listener) { 1379 mAccessPointListener = listener; 1380 } 1381 1382 /** 1383 * Sets {@link #mScanResults} to the given collection and updates info based on the best RSSI 1384 * scan result. 1385 * 1386 * @param scanResults a collection of scan results to add to the internal set 1387 */ setScanResults(Collection<ScanResult> scanResults)1388 void setScanResults(Collection<ScanResult> scanResults) { 1389 if (CollectionUtils.isEmpty(scanResults)) { 1390 Log.d(TAG, "Cannot set scan results to empty list"); 1391 return; 1392 } 1393 1394 // Validate scan results are for current AP only by matching SSID/BSSID 1395 // Passpoint networks are not bound to a specific SSID/BSSID, so skip this for passpoint. 1396 if (mKey != null && !isPasspoint() && !isOsuProvider()) { 1397 for (ScanResult result : scanResults) { 1398 if (!matches(result)) { 1399 Log.d(TAG, String.format( 1400 "ScanResult %s\nkey of %s did not match current AP key %s", 1401 result, getKey(mContext, result), mKey)); 1402 return; 1403 } 1404 } 1405 } 1406 1407 int oldLevel = getLevel(); 1408 synchronized (mLock) { 1409 mScanResults.clear(); 1410 mScanResults.addAll(scanResults); 1411 } 1412 updateBestRssiInfo(); 1413 int newLevel = getLevel(); 1414 1415 // If newLevel is 0, there will be no displayed Preference since the AP is unreachable 1416 if (newLevel > 0 && newLevel != oldLevel) { 1417 // Only update labels on visible rssi changes 1418 updateSpeed(); 1419 ThreadUtils.postOnMainThread(() -> { 1420 if (mAccessPointListener != null) { 1421 mAccessPointListener.onLevelChanged(this); 1422 } 1423 }); 1424 1425 } 1426 1427 ThreadUtils.postOnMainThread(() -> { 1428 if (mAccessPointListener != null) { 1429 mAccessPointListener.onAccessPointChanged(this); 1430 } 1431 }); 1432 } 1433 1434 /** 1435 * Sets the internal scan result cache to the list of home scans. 1436 * If there are no home scans, then the roaming scan list is used, and the AccessPoint is 1437 * marked as roaming. 1438 */ setScanResultsPasspoint( @ullable Collection<ScanResult> homeScans, @Nullable Collection<ScanResult> roamingScans)1439 void setScanResultsPasspoint( 1440 @Nullable Collection<ScanResult> homeScans, 1441 @Nullable Collection<ScanResult> roamingScans) { 1442 synchronized (mLock) { 1443 mExtraScanResults.clear(); 1444 if (!CollectionUtils.isEmpty(homeScans)) { 1445 mIsRoaming = false; 1446 if (!CollectionUtils.isEmpty(roamingScans)) { 1447 mExtraScanResults.addAll(roamingScans); 1448 } 1449 setScanResults(homeScans); 1450 } else if (!CollectionUtils.isEmpty(roamingScans)) { 1451 mIsRoaming = true; 1452 setScanResults(roamingScans); 1453 } 1454 } 1455 } 1456 1457 /** 1458 * Attempt to update the AccessPoint with the current connection info. 1459 * This is used to set an AccessPoint to the active one if the connection info matches, or 1460 * conversely to set an AccessPoint to inactive if the connection info does not match. The RSSI 1461 * is also updated upon a match. Listeners will be notified if an update occurred. 1462 * 1463 * This is called in {@link WifiTracker#updateAccessPoints} as well as in callbacks for handling 1464 * NETWORK_STATE_CHANGED_ACTION, RSSI_CHANGED_ACTION, and onCapabilitiesChanged in WifiTracker. 1465 * 1466 * Returns true if an update occurred. 1467 */ update( @ullable WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo)1468 public boolean update( 1469 @Nullable WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) { 1470 boolean updated = false; 1471 final int oldLevel = getLevel(); 1472 if (info != null && isInfoForThisAccessPoint(config, info)) { 1473 updated = (mInfo == null); 1474 if (!isPasspoint() && mConfig != config) { 1475 // We do not set updated = true as we do not want to increase the amount of sorting 1476 // and copying performed in WifiTracker at this time. If issues involving refresh 1477 // are still seen, we will investigate further. 1478 update(config); // Notifies the AccessPointListener of the change 1479 } 1480 if (mRssi != info.getRssi() && info.getRssi() != WifiInfo.INVALID_RSSI) { 1481 mRssi = info.getRssi(); 1482 updated = true; 1483 } else if (mNetworkInfo != null && networkInfo != null 1484 && mNetworkInfo.getDetailedState() != networkInfo.getDetailedState()) { 1485 updated = true; 1486 } 1487 mInfo = info; 1488 mNetworkInfo = networkInfo; 1489 } else if (mInfo != null) { 1490 updated = true; 1491 mInfo = null; 1492 mNetworkInfo = null; 1493 } 1494 if (updated && mAccessPointListener != null) { 1495 ThreadUtils.postOnMainThread(() -> { 1496 if (mAccessPointListener != null) { 1497 mAccessPointListener.onAccessPointChanged(this); 1498 } 1499 }); 1500 1501 if (oldLevel != getLevel() /* current level */) { 1502 ThreadUtils.postOnMainThread(() -> { 1503 if (mAccessPointListener != null) { 1504 mAccessPointListener.onLevelChanged(this); 1505 } 1506 }); 1507 } 1508 } 1509 1510 return updated; 1511 } 1512 update(@ullable WifiConfiguration config)1513 void update(@Nullable WifiConfiguration config) { 1514 mConfig = config; 1515 if (mConfig != null && !isPasspoint()) { 1516 ssid = removeDoubleQuotes(mConfig.SSID); 1517 } 1518 networkId = config != null ? config.networkId : WifiConfiguration.INVALID_NETWORK_ID; 1519 ThreadUtils.postOnMainThread(() -> { 1520 if (mAccessPointListener != null) { 1521 mAccessPointListener.onAccessPointChanged(this); 1522 } 1523 }); 1524 } 1525 1526 @VisibleForTesting setRssi(int rssi)1527 void setRssi(int rssi) { 1528 mRssi = rssi; 1529 } 1530 1531 /** Sets the rssi to {@link #UNREACHABLE_RSSI}. */ setUnreachable()1532 void setUnreachable() { 1533 setRssi(AccessPoint.UNREACHABLE_RSSI); 1534 } 1535 getSpeed()1536 int getSpeed() { return mSpeed;} 1537 1538 @Nullable getSpeedLabel()1539 String getSpeedLabel() { 1540 return getSpeedLabel(mSpeed); 1541 } 1542 1543 @Nullable 1544 @Speed roundToClosestSpeedEnum(int speed)1545 private static int roundToClosestSpeedEnum(int speed) { 1546 if (speed < Speed.SLOW) { 1547 return Speed.NONE; 1548 } else if (speed < (Speed.SLOW + Speed.MODERATE) / 2) { 1549 return Speed.SLOW; 1550 } else if (speed < (Speed.MODERATE + Speed.FAST) / 2) { 1551 return Speed.MODERATE; 1552 } else if (speed < (Speed.FAST + Speed.VERY_FAST) / 2) { 1553 return Speed.FAST; 1554 } else { 1555 return Speed.VERY_FAST; 1556 } 1557 } 1558 1559 @Nullable getSpeedLabel(@peed int speed)1560 String getSpeedLabel(@Speed int speed) { 1561 return getSpeedLabel(mContext, speed); 1562 } 1563 getSpeedLabel(Context context, int speed)1564 private static String getSpeedLabel(Context context, int speed) { 1565 switch (speed) { 1566 case Speed.VERY_FAST: 1567 return context.getString(R.string.speed_label_very_fast); 1568 case Speed.FAST: 1569 return context.getString(R.string.speed_label_fast); 1570 case Speed.MODERATE: 1571 return context.getString(R.string.speed_label_okay); 1572 case Speed.SLOW: 1573 return context.getString(R.string.speed_label_slow); 1574 case Speed.NONE: 1575 default: 1576 return null; 1577 } 1578 } 1579 1580 /** Return the speed label for a {@link ScoredNetwork} at the specified {@code rssi} level. */ 1581 @Nullable getSpeedLabel(Context context, ScoredNetwork scoredNetwork, int rssi)1582 public static String getSpeedLabel(Context context, ScoredNetwork scoredNetwork, int rssi) { 1583 return getSpeedLabel(context, roundToClosestSpeedEnum(scoredNetwork.calculateBadge(rssi))); 1584 } 1585 1586 /** Return true if the current RSSI is reachable, and false otherwise. */ isReachable()1587 public boolean isReachable() { 1588 return mRssi != UNREACHABLE_RSSI; 1589 } 1590 getAppLabel(String packageName, PackageManager packageManager)1591 private static CharSequence getAppLabel(String packageName, PackageManager packageManager) { 1592 CharSequence appLabel = ""; 1593 ApplicationInfo appInfo = null; 1594 try { 1595 int userId = UserHandle.getUserId(UserHandle.USER_CURRENT); 1596 appInfo = packageManager.getApplicationInfoAsUser(packageName, 0 /* flags */, userId); 1597 } catch (PackageManager.NameNotFoundException e) { 1598 Log.e(TAG, "Failed to get app info", e); 1599 return appLabel; 1600 } 1601 if (appInfo != null) { 1602 appLabel = appInfo.loadLabel(packageManager); 1603 } 1604 return appLabel; 1605 } 1606 getSummary(Context context, String ssid, DetailedState state, boolean isEphemeral, String suggestionOrSpecifierPackageName)1607 public static String getSummary(Context context, String ssid, DetailedState state, 1608 boolean isEphemeral, String suggestionOrSpecifierPackageName) { 1609 if (state == DetailedState.CONNECTED) { 1610 if (isEphemeral && !TextUtils.isEmpty(suggestionOrSpecifierPackageName)) { 1611 CharSequence appLabel = 1612 getAppLabel(suggestionOrSpecifierPackageName, context.getPackageManager()); 1613 return context.getString(R.string.connected_via_app, appLabel); 1614 } else if (isEphemeral) { 1615 // Special case for connected + ephemeral networks. 1616 final NetworkScoreManager networkScoreManager = context.getSystemService( 1617 NetworkScoreManager.class); 1618 NetworkScorerAppData scorer = networkScoreManager.getActiveScorer(); 1619 if (scorer != null && scorer.getRecommendationServiceLabel() != null) { 1620 String format = context.getString(R.string.connected_via_network_scorer); 1621 return String.format(format, scorer.getRecommendationServiceLabel()); 1622 } else { 1623 return context.getString(R.string.connected_via_network_scorer_default); 1624 } 1625 } 1626 } 1627 1628 // Case when there is wifi connected without internet connectivity. 1629 final ConnectivityManager cm = (ConnectivityManager) 1630 context.getSystemService(Context.CONNECTIVITY_SERVICE); 1631 if (state == DetailedState.CONNECTED) { 1632 IWifiManager wifiManager = IWifiManager.Stub.asInterface( 1633 ServiceManager.getService(Context.WIFI_SERVICE)); 1634 NetworkCapabilities nc = null; 1635 1636 try { 1637 nc = cm.getNetworkCapabilities(wifiManager.getCurrentNetwork()); 1638 } catch (RemoteException e) {} 1639 1640 if (nc != null) { 1641 if (nc.hasCapability(nc.NET_CAPABILITY_CAPTIVE_PORTAL)) { 1642 int id = context.getResources() 1643 .getIdentifier("network_available_sign_in", "string", "android"); 1644 return context.getString(id); 1645 } else if (nc.hasCapability( 1646 NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY)) { 1647 return context.getString(R.string.wifi_limited_connection); 1648 } else if (!nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) { 1649 final String mode = Settings.Global.getString(context.getContentResolver(), 1650 Settings.Global.PRIVATE_DNS_MODE); 1651 if (nc.isPrivateDnsBroken()) { 1652 return context.getString(R.string.private_dns_broken); 1653 } else { 1654 return context.getString(R.string.wifi_connected_no_internet); 1655 } 1656 } 1657 } 1658 } 1659 if (state == null) { 1660 Log.w(TAG, "state is null, returning empty summary"); 1661 return ""; 1662 } 1663 String[] formats = context.getResources().getStringArray((ssid == null) 1664 ? R.array.wifi_status : R.array.wifi_status_with_ssid); 1665 int index = state.ordinal(); 1666 1667 if (index >= formats.length || formats[index].length() == 0) { 1668 return ""; 1669 } 1670 return String.format(formats[index], ssid); 1671 } 1672 convertToQuotedString(String string)1673 public static String convertToQuotedString(String string) { 1674 return "\"" + string + "\""; 1675 } 1676 getPskType(ScanResult result)1677 private static int getPskType(ScanResult result) { 1678 boolean wpa = result.capabilities.contains("WPA-PSK"); 1679 boolean wpa2 = result.capabilities.contains("RSN-PSK"); 1680 boolean wpa3 = result.capabilities.contains("RSN-SAE"); 1681 if (wpa2 && wpa) { 1682 return PSK_WPA_WPA2; 1683 } else if (wpa2) { 1684 return PSK_WPA2; 1685 } else if (wpa) { 1686 return PSK_WPA; 1687 } else { 1688 if (!wpa3) { 1689 // Suppress warning for WPA3 only networks 1690 Log.w(TAG, "Received abnormal flag string: " + result.capabilities); 1691 } 1692 return PSK_UNKNOWN; 1693 } 1694 } 1695 getEapType(ScanResult result)1696 private static int getEapType(ScanResult result) { 1697 // WPA2-Enterprise and WPA3-Enterprise (non 192-bit) advertise RSN-EAP-CCMP 1698 if (result.capabilities.contains("RSN-EAP")) { 1699 return EAP_WPA2_WPA3; 1700 } 1701 // WPA-Enterprise advertises WPA-EAP-TKIP 1702 if (result.capabilities.contains("WPA-EAP")) { 1703 return EAP_WPA; 1704 } 1705 return EAP_UNKNOWN; 1706 } 1707 getSecurity(Context context, ScanResult result)1708 private static int getSecurity(Context context, ScanResult result) { 1709 final boolean isWep = result.capabilities.contains("WEP"); 1710 final boolean isSae = result.capabilities.contains("SAE"); 1711 final boolean isPsk = result.capabilities.contains("PSK"); 1712 final boolean isEapSuiteB192 = result.capabilities.contains("EAP_SUITE_B_192"); 1713 final boolean isEap = result.capabilities.contains("EAP"); 1714 final boolean isOwe = result.capabilities.contains("OWE"); 1715 final boolean isOweTransition = result.capabilities.contains("OWE_TRANSITION"); 1716 1717 if (isSae && isPsk) { 1718 final WifiManager wifiManager = (WifiManager) 1719 context.getSystemService(Context.WIFI_SERVICE); 1720 return wifiManager.isWpa3SaeSupported() ? SECURITY_SAE : SECURITY_PSK; 1721 } 1722 if (isOweTransition) { 1723 final WifiManager wifiManager = (WifiManager) 1724 context.getSystemService(Context.WIFI_SERVICE); 1725 return wifiManager.isEnhancedOpenSupported() ? SECURITY_OWE : SECURITY_NONE; 1726 } 1727 1728 if (isWep) { 1729 return SECURITY_WEP; 1730 } else if (isSae) { 1731 return SECURITY_SAE; 1732 } else if (isPsk) { 1733 return SECURITY_PSK; 1734 } else if (isEapSuiteB192) { 1735 return SECURITY_EAP_SUITE_B; 1736 } else if (isEap) { 1737 return SECURITY_EAP; 1738 } else if (isOwe) { 1739 return SECURITY_OWE; 1740 } 1741 return SECURITY_NONE; 1742 } 1743 getSecurity(WifiConfiguration config)1744 static int getSecurity(WifiConfiguration config) { 1745 if (config.allowedKeyManagement.get(KeyMgmt.SAE)) { 1746 return SECURITY_SAE; 1747 } 1748 if (config.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { 1749 return SECURITY_PSK; 1750 } 1751 if (config.allowedKeyManagement.get(KeyMgmt.SUITE_B_192)) { 1752 return SECURITY_EAP_SUITE_B; 1753 } 1754 if (config.allowedKeyManagement.get(KeyMgmt.WPA_EAP) || 1755 config.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) { 1756 return SECURITY_EAP; 1757 } 1758 if (config.allowedKeyManagement.get(KeyMgmt.OWE)) { 1759 return SECURITY_OWE; 1760 } 1761 return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE; 1762 } 1763 securityToString(int security, int pskType)1764 public static String securityToString(int security, int pskType) { 1765 if (security == SECURITY_WEP) { 1766 return "WEP"; 1767 } else if (security == SECURITY_PSK) { 1768 if (pskType == PSK_WPA) { 1769 return "WPA"; 1770 } else if (pskType == PSK_WPA2) { 1771 return "WPA2"; 1772 } else if (pskType == PSK_WPA_WPA2) { 1773 return "WPA_WPA2"; 1774 } 1775 return "PSK"; 1776 } else if (security == SECURITY_EAP) { 1777 return "EAP"; 1778 } else if (security == SECURITY_SAE) { 1779 return "SAE"; 1780 } else if (security == SECURITY_EAP_SUITE_B) { 1781 return "SUITE_B"; 1782 } else if (security == SECURITY_OWE) { 1783 return "OWE"; 1784 } 1785 return "NONE"; 1786 } 1787 removeDoubleQuotes(String string)1788 static String removeDoubleQuotes(String string) { 1789 if (TextUtils.isEmpty(string)) { 1790 return ""; 1791 } 1792 int length = string.length(); 1793 if ((length > 1) && (string.charAt(0) == '"') 1794 && (string.charAt(length - 1) == '"')) { 1795 return string.substring(1, length - 1); 1796 } 1797 return string; 1798 } 1799 getWifiManager()1800 private WifiManager getWifiManager() { 1801 if (mWifiManager == null) { 1802 mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); 1803 } 1804 return mWifiManager; 1805 } 1806 1807 /** 1808 * Callbacks relaying changes to the AccessPoint representation. 1809 * 1810 * <p>All methods are invoked on the Main Thread. 1811 */ 1812 public interface AccessPointListener { 1813 1814 /** 1815 * Indicates a change to the externally visible state of the AccessPoint trigger by an 1816 * update of ScanResults, saved configuration state, connection state, or score 1817 * (labels/metered) state. 1818 * 1819 * <p>Clients should refresh their view of the AccessPoint to match the updated state when 1820 * this is invoked. Overall this method is extraneous if clients are listening to 1821 * {@link WifiTracker.WifiListener#onAccessPointsChanged()} callbacks. 1822 * 1823 * <p>Examples of changes include signal strength, connection state, speed label, and 1824 * generally anything that would impact the summary string. 1825 * 1826 * @param accessPoint The accessPoint object the listener was registered on which has 1827 * changed 1828 */ onAccessPointChanged(AccessPoint accessPoint)1829 @MainThread void onAccessPointChanged(AccessPoint accessPoint); 1830 /** 1831 * Indicates the "wifi pie signal level" has changed, retrieved via calls to 1832 * {@link AccessPoint#getLevel()}. 1833 * 1834 * <p>This call is a subset of {@link #onAccessPointChanged(AccessPoint)} , hence is also 1835 * extraneous if the client is already reacting to that or the 1836 * {@link WifiTracker.WifiListener#onAccessPointsChanged()} callbacks. 1837 * 1838 * @param accessPoint The accessPoint object the listener was registered on whose level has 1839 * changed 1840 */ onLevelChanged(AccessPoint accessPoint)1841 @MainThread void onLevelChanged(AccessPoint accessPoint); 1842 } 1843 isVerboseLoggingEnabled()1844 private static boolean isVerboseLoggingEnabled() { 1845 return WifiTracker.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE); 1846 } 1847 1848 /** 1849 * Callbacks relaying changes to the OSU provisioning status started in startOsuProvisioning(). 1850 * 1851 * All methods are invoked on the Main Thread 1852 */ 1853 @VisibleForTesting 1854 class AccessPointProvisioningCallback extends ProvisioningCallback { 1855 @Override onProvisioningFailure(int status)1856 @MainThread public void onProvisioningFailure(int status) { 1857 if (TextUtils.equals(mOsuStatus, mContext.getString(R.string.osu_completing_sign_up))) { 1858 mOsuFailure = mContext.getString(R.string.osu_sign_up_failed); 1859 } else { 1860 mOsuFailure = mContext.getString(R.string.osu_connect_failed); 1861 } 1862 mOsuStatus = null; 1863 mOsuProvisioningComplete = false; 1864 ThreadUtils.postOnMainThread(() -> { 1865 if (mAccessPointListener != null) { 1866 mAccessPointListener.onAccessPointChanged(AccessPoint.this); 1867 } 1868 }); 1869 } 1870 1871 @Override onProvisioningStatus(int status)1872 @MainThread public void onProvisioningStatus(int status) { 1873 String newStatus = null; 1874 switch (status) { 1875 case OSU_STATUS_AP_CONNECTING: 1876 case OSU_STATUS_AP_CONNECTED: 1877 case OSU_STATUS_SERVER_CONNECTING: 1878 case OSU_STATUS_SERVER_VALIDATED: 1879 case OSU_STATUS_SERVER_CONNECTED: 1880 case OSU_STATUS_INIT_SOAP_EXCHANGE: 1881 case OSU_STATUS_WAITING_FOR_REDIRECT_RESPONSE: 1882 newStatus = String.format(mContext.getString(R.string.osu_opening_provider), 1883 mOsuProvider.getFriendlyName()); 1884 break; 1885 case OSU_STATUS_REDIRECT_RESPONSE_RECEIVED: 1886 case OSU_STATUS_SECOND_SOAP_EXCHANGE: 1887 case OSU_STATUS_THIRD_SOAP_EXCHANGE: 1888 case OSU_STATUS_RETRIEVING_TRUST_ROOT_CERTS: 1889 newStatus = mContext.getString( 1890 R.string.osu_completing_sign_up); 1891 break; 1892 } 1893 boolean updated = !TextUtils.equals(mOsuStatus, newStatus); 1894 mOsuStatus = newStatus; 1895 mOsuFailure = null; 1896 mOsuProvisioningComplete = false; 1897 if (updated) { 1898 ThreadUtils.postOnMainThread(() -> { 1899 if (mAccessPointListener != null) { 1900 mAccessPointListener.onAccessPointChanged(AccessPoint.this); 1901 } 1902 }); 1903 } 1904 } 1905 1906 @Override onProvisioningComplete()1907 @MainThread public void onProvisioningComplete() { 1908 mOsuProvisioningComplete = true; 1909 mOsuFailure = null; 1910 mOsuStatus = null; 1911 1912 ThreadUtils.postOnMainThread(() -> { 1913 if (mAccessPointListener != null) { 1914 mAccessPointListener.onAccessPointChanged(AccessPoint.this); 1915 } 1916 }); 1917 1918 // Connect to the freshly provisioned network. 1919 WifiManager wifiManager = getWifiManager(); 1920 1921 PasspointConfiguration passpointConfig = wifiManager 1922 .getMatchingPasspointConfigsForOsuProviders(Collections.singleton(mOsuProvider)) 1923 .get(mOsuProvider); 1924 if (passpointConfig == null) { 1925 Log.e(TAG, "Missing PasspointConfiguration for newly provisioned network!"); 1926 if (mConnectListener != null) { 1927 mConnectListener.onFailure(0); 1928 } 1929 return; 1930 } 1931 1932 String fqdn = passpointConfig.getHomeSp().getFqdn(); 1933 for (Pair<WifiConfiguration, Map<Integer, List<ScanResult>>> pairing : 1934 wifiManager.getAllMatchingWifiConfigs(wifiManager.getScanResults())) { 1935 WifiConfiguration config = pairing.first; 1936 if (TextUtils.equals(config.FQDN, fqdn)) { 1937 List<ScanResult> homeScans = 1938 pairing.second.get(WifiManager.PASSPOINT_HOME_NETWORK); 1939 List<ScanResult> roamingScans = 1940 pairing.second.get(WifiManager.PASSPOINT_ROAMING_NETWORK); 1941 1942 AccessPoint connectionAp = 1943 new AccessPoint(mContext, config, homeScans, roamingScans); 1944 wifiManager.connect(connectionAp.getConfig(), mConnectListener); 1945 return; 1946 } 1947 } 1948 if (mConnectListener != null) { 1949 mConnectListener.onFailure(0); 1950 } 1951 } 1952 } 1953 isPskSaeTransitionMode()1954 public boolean isPskSaeTransitionMode() { 1955 return mIsPskSaeTransitionMode; 1956 } 1957 isOweTransitionMode()1958 public boolean isOweTransitionMode() { 1959 return mIsOweTransitionMode; 1960 } 1961 isPskSaeTransitionMode(ScanResult scanResult)1962 private static boolean isPskSaeTransitionMode(ScanResult scanResult) { 1963 return scanResult.capabilities.contains("PSK") 1964 && scanResult.capabilities.contains("SAE"); 1965 } 1966 isOweTransitionMode(ScanResult scanResult)1967 private static boolean isOweTransitionMode(ScanResult scanResult) { 1968 return scanResult.capabilities.contains("OWE_TRANSITION"); 1969 } 1970 isSameSsidOrBssid(ScanResult scanResult)1971 private boolean isSameSsidOrBssid(ScanResult scanResult) { 1972 if (scanResult == null) { 1973 return false; 1974 } 1975 1976 if (TextUtils.equals(ssid, scanResult.SSID)) { 1977 return true; 1978 } else if (scanResult.BSSID != null && TextUtils.equals(bssid, scanResult.BSSID)) { 1979 return true; 1980 } 1981 return false; 1982 } 1983 isSameSsidOrBssid(WifiInfo wifiInfo)1984 private boolean isSameSsidOrBssid(WifiInfo wifiInfo) { 1985 if (wifiInfo == null) { 1986 return false; 1987 } 1988 1989 if (TextUtils.equals(ssid, removeDoubleQuotes(wifiInfo.getSSID()))) { 1990 return true; 1991 } else if (wifiInfo.getBSSID() != null && TextUtils.equals(bssid, wifiInfo.getBSSID())) { 1992 return true; 1993 } 1994 return false; 1995 } 1996 isSameSsidOrBssid(AccessPoint accessPoint)1997 private boolean isSameSsidOrBssid(AccessPoint accessPoint) { 1998 if (accessPoint == null) { 1999 return false; 2000 } 2001 2002 if (TextUtils.equals(ssid, accessPoint.getSsid())) { 2003 return true; 2004 } else if (accessPoint.getBssid() != null 2005 && TextUtils.equals(bssid, accessPoint.getBssid())) { 2006 return true; 2007 } 2008 return false; 2009 } 2010 } 2011