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