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