1 /* 2 * Copyright (C) 2019 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.wifitrackerlib; 18 19 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE; 20 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_NO_CREDENTIALS; 21 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD; 22 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; 23 import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS; 24 import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP; 25 import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE; 26 import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT; 27 import static android.net.wifi.WifiInfo.SECURITY_TYPE_OPEN; 28 import static android.net.wifi.WifiInfo.SECURITY_TYPE_OWE; 29 import static android.net.wifi.WifiInfo.SECURITY_TYPE_PASSPOINT_R1_R2; 30 import static android.net.wifi.WifiInfo.SECURITY_TYPE_PASSPOINT_R3; 31 import static android.net.wifi.WifiInfo.SECURITY_TYPE_PSK; 32 import static android.net.wifi.WifiInfo.SECURITY_TYPE_SAE; 33 import static android.net.wifi.WifiInfo.SECURITY_TYPE_UNKNOWN; 34 import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP; 35 import static android.net.wifi.WifiInfo.sanitizeSsid; 36 37 import static com.android.wifitrackerlib.Utils.getAutoConnectDescription; 38 import static com.android.wifitrackerlib.Utils.getBestScanResultByLevel; 39 import static com.android.wifitrackerlib.Utils.getConnectedDescription; 40 import static com.android.wifitrackerlib.Utils.getConnectingDescription; 41 import static com.android.wifitrackerlib.Utils.getDisconnectedDescription; 42 import static com.android.wifitrackerlib.Utils.getMeteredDescription; 43 import static com.android.wifitrackerlib.Utils.getSecurityTypesFromScanResult; 44 import static com.android.wifitrackerlib.Utils.getSecurityTypesFromWifiConfiguration; 45 import static com.android.wifitrackerlib.Utils.getSingleSecurityTypeFromMultipleSecurityTypes; 46 import static com.android.wifitrackerlib.Utils.getVerboseLoggingDescription; 47 48 import android.annotation.SuppressLint; 49 import android.app.admin.DevicePolicyManager; 50 import android.app.admin.WifiSsidPolicy; 51 import android.net.ConnectivityManager; 52 import android.net.Network; 53 import android.net.NetworkCapabilities; 54 import android.net.wifi.MloLink; 55 import android.net.wifi.ScanResult; 56 import android.net.wifi.WifiConfiguration; 57 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; 58 import android.net.wifi.WifiInfo; 59 import android.net.wifi.WifiManager; 60 import android.net.wifi.WifiScanner; 61 import android.net.wifi.WifiSsid; 62 import android.os.Handler; 63 import android.os.SystemClock; 64 import android.os.UserHandle; 65 import android.os.UserManager; 66 import android.telephony.SubscriptionInfo; 67 import android.telephony.SubscriptionManager; 68 import android.telephony.TelephonyManager; 69 import android.text.TextUtils; 70 import android.util.ArrayMap; 71 import android.util.ArraySet; 72 import android.util.Log; 73 74 import androidx.annotation.NonNull; 75 import androidx.annotation.Nullable; 76 import androidx.annotation.WorkerThread; 77 import androidx.core.os.BuildCompat; 78 79 import org.json.JSONArray; 80 import org.json.JSONException; 81 import org.json.JSONObject; 82 83 import java.nio.charset.StandardCharsets; 84 import java.util.ArrayList; 85 import java.util.Collections; 86 import java.util.Comparator; 87 import java.util.List; 88 import java.util.Map; 89 import java.util.Objects; 90 import java.util.Set; 91 import java.util.StringJoiner; 92 import java.util.stream.Collectors; 93 94 /** 95 * WifiEntry representation of a logical Wi-Fi network, uniquely identified by SSID and security. 96 * 97 * This type of WifiEntry can represent both open and saved networks. 98 */ 99 public class StandardWifiEntry extends WifiEntry { 100 static final String TAG = "StandardWifiEntry"; 101 public static final String KEY_PREFIX = "StandardWifiEntry:"; 102 103 @NonNull private final StandardWifiEntryKey mKey; 104 105 // Map of security type to matching scan results 106 @NonNull private final Map<Integer, List<ScanResult>> mMatchingScanResults = new ArrayMap<>(); 107 // Map of security type to matching WifiConfiguration 108 // TODO: Change this to single WifiConfiguration once we can get multiple security type configs. 109 @NonNull private final Map<Integer, WifiConfiguration> mMatchingWifiConfigs = new ArrayMap<>(); 110 111 // List of the target scan results to be displayed. This should match the highest available 112 // security from all of the matched WifiConfigurations. 113 // If no WifiConfigurations are available, then these should match the most appropriate security 114 // type (e.g. PSK for an PSK/SAE entry, OWE for an Open/OWE entry). 115 @NonNull private final List<ScanResult> mTargetScanResults = new ArrayList<>(); 116 // Target WifiConfiguration for connection and displaying WifiConfiguration info 117 private WifiConfiguration mTargetWifiConfig; 118 private List<Integer> mTargetSecurityTypes = new ArrayList<>(); 119 120 private boolean mIsUserShareable = false; 121 122 private boolean mShouldAutoOpenCaptivePortal = false; 123 124 private boolean mIsAdminRestricted = false; 125 private boolean mHasAddConfigUserRestriction = false; 126 127 private final boolean mIsWpa3SaeSupported; 128 private final boolean mIsWpa3SuiteBSupported; 129 private final boolean mIsEnhancedOpenSupported; 130 131 private final UserManager mUserManager; 132 private final DevicePolicyManager mDevicePolicyManager; 133 StandardWifiEntry( @onNull WifiTrackerInjector injector, @NonNull Handler callbackHandler, @NonNull StandardWifiEntryKey key, @NonNull WifiManager wifiManager, boolean forSavedNetworksPage)134 StandardWifiEntry( 135 @NonNull WifiTrackerInjector injector, 136 @NonNull Handler callbackHandler, 137 @NonNull StandardWifiEntryKey key, 138 @NonNull WifiManager wifiManager, 139 boolean forSavedNetworksPage) { 140 super(injector, callbackHandler, wifiManager, forSavedNetworksPage); 141 mKey = key; 142 mIsWpa3SaeSupported = wifiManager.isWpa3SaeSupported(); 143 mIsWpa3SuiteBSupported = wifiManager.isWpa3SuiteBSupported(); 144 mIsEnhancedOpenSupported = wifiManager.isEnhancedOpenSupported(); 145 mUserManager = injector.getUserManager(); 146 mDevicePolicyManager = injector.getDevicePolicyManager(); 147 updateSecurityTypes(); 148 updateAdminRestrictions(); 149 } 150 StandardWifiEntry( @onNull WifiTrackerInjector injector, @NonNull Handler callbackHandler, @NonNull StandardWifiEntryKey key, @Nullable List<WifiConfiguration> configs, @Nullable List<ScanResult> scanResults, @NonNull WifiManager wifiManager, boolean forSavedNetworksPage)151 StandardWifiEntry( 152 @NonNull WifiTrackerInjector injector, 153 @NonNull Handler callbackHandler, 154 @NonNull StandardWifiEntryKey key, 155 @Nullable List<WifiConfiguration> configs, 156 @Nullable List<ScanResult> scanResults, 157 @NonNull WifiManager wifiManager, 158 boolean forSavedNetworksPage) throws IllegalArgumentException { 159 this(injector, callbackHandler, key, wifiManager, 160 forSavedNetworksPage); 161 if (configs != null && !configs.isEmpty()) { 162 updateConfig(configs); 163 } 164 if (scanResults != null && !scanResults.isEmpty()) { 165 updateScanResultInfo(scanResults); 166 } 167 } 168 169 @Override getKey()170 public String getKey() { 171 return mKey.toString(); 172 } 173 getStandardWifiEntryKey()174 StandardWifiEntryKey getStandardWifiEntryKey() { 175 return mKey; 176 } 177 178 @Override getTitle()179 public String getTitle() { 180 return mKey.getScanResultKey().getSsid(); 181 } 182 183 @Override getSummary(boolean concise)184 public synchronized String getSummary(boolean concise) { 185 StringJoiner sj = new StringJoiner(mContext.getString( 186 R.string.wifitrackerlib_summary_separator)); 187 188 final String connectedStateDescription; 189 final @ConnectedState int connectedState = getConnectedState(); 190 switch (connectedState) { 191 case CONNECTED_STATE_DISCONNECTED: 192 connectedStateDescription = getDisconnectedDescription(mInjector, mContext, 193 mTargetWifiConfig, 194 mForSavedNetworksPage, 195 concise); 196 break; 197 case CONNECTED_STATE_CONNECTING: 198 connectedStateDescription = getConnectingDescription(mContext, mNetworkInfo); 199 break; 200 case CONNECTED_STATE_CONNECTED: 201 connectedStateDescription = getConnectedDescription(mContext, 202 mTargetWifiConfig, 203 mNetworkCapabilities, 204 mIsDefaultNetwork, 205 isLowQuality(), 206 mConnectivityReport); 207 break; 208 default: 209 Log.e(TAG, "getConnectedState() returned unknown state: " + connectedState); 210 connectedStateDescription = null; 211 } 212 if (!TextUtils.isEmpty(connectedStateDescription)) { 213 sj.add(connectedStateDescription); 214 } 215 216 final String autoConnectDescription = getAutoConnectDescription(mContext, this); 217 if (!TextUtils.isEmpty(autoConnectDescription)) { 218 sj.add(autoConnectDescription); 219 } 220 221 final String meteredDescription = getMeteredDescription(mContext, this); 222 if (!TextUtils.isEmpty(meteredDescription)) { 223 sj.add(meteredDescription); 224 } 225 226 if (!concise) { 227 final String verboseLoggingDescription = getVerboseLoggingDescription(this); 228 if (!TextUtils.isEmpty(verboseLoggingDescription)) { 229 sj.add(verboseLoggingDescription); 230 } 231 } 232 233 return sj.toString(); 234 } 235 236 @Override getSsid()237 public String getSsid() { 238 return mKey.getScanResultKey().getSsid(); 239 } 240 241 @Override getSecurityTypes()242 public synchronized List<Integer> getSecurityTypes() { 243 return new ArrayList<>(mTargetSecurityTypes); 244 } 245 246 @Override 247 @Nullable getMacAddress()248 public synchronized String getMacAddress() { 249 if (mWifiInfo != null) { 250 final String wifiInfoMac = mWifiInfo.getMacAddress(); 251 if (!TextUtils.isEmpty(wifiInfoMac) 252 && !TextUtils.equals(wifiInfoMac, DEFAULT_MAC_ADDRESS)) { 253 return wifiInfoMac; 254 } 255 } 256 if (mTargetWifiConfig == null || getPrivacy() != PRIVACY_RANDOMIZED_MAC) { 257 final String[] factoryMacs = mWifiManager.getFactoryMacAddresses(); 258 if (factoryMacs.length > 0) { 259 return factoryMacs[0]; 260 } 261 return null; 262 } 263 return mTargetWifiConfig.getRandomizedMacAddress().toString(); 264 } 265 266 @Override isMetered()267 public synchronized boolean isMetered() { 268 return getMeteredChoice() == METERED_CHOICE_METERED 269 || (mTargetWifiConfig != null && mTargetWifiConfig.meteredHint); 270 } 271 272 @Override isSaved()273 public synchronized boolean isSaved() { 274 return mTargetWifiConfig != null && !mTargetWifiConfig.fromWifiNetworkSuggestion 275 && !mTargetWifiConfig.isEphemeral(); 276 } 277 278 @Override isSuggestion()279 public synchronized boolean isSuggestion() { 280 return mTargetWifiConfig != null && mTargetWifiConfig.fromWifiNetworkSuggestion; 281 } 282 283 @Override 284 @Nullable getWifiConfiguration()285 public synchronized WifiConfiguration getWifiConfiguration() { 286 if (!isSaved()) { 287 return null; 288 } 289 return mTargetWifiConfig; 290 } 291 292 @Override canConnect()293 public synchronized boolean canConnect() { 294 if (mLevel == WIFI_LEVEL_UNREACHABLE 295 || getConnectedState() != CONNECTED_STATE_DISCONNECTED) { 296 return false; 297 } 298 299 if (hasAdminRestrictions()) return false; 300 301 // Allow connection for EAP SIM dependent methods if the SIM of specified carrier ID is 302 // active in the device. 303 if (mTargetSecurityTypes.contains(SECURITY_TYPE_EAP) && mTargetWifiConfig != null 304 && mTargetWifiConfig.enterpriseConfig != null) { 305 if (!mTargetWifiConfig.enterpriseConfig.isAuthenticationSimBased()) { 306 return true; 307 } 308 List<SubscriptionInfo> activeSubscriptionInfos = mContext 309 .getSystemService(SubscriptionManager.class).getActiveSubscriptionInfoList(); 310 if (activeSubscriptionInfos == null || activeSubscriptionInfos.size() == 0) { 311 return false; 312 } 313 if (mTargetWifiConfig.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { 314 // To connect via default subscription. 315 return true; 316 } 317 for (SubscriptionInfo subscriptionInfo : activeSubscriptionInfos) { 318 if (subscriptionInfo.getCarrierId() == mTargetWifiConfig.carrierId) { 319 return true; 320 } 321 } 322 return false; 323 } 324 return true; 325 } 326 327 @Override connect(@ullable ConnectCallback callback)328 public synchronized void connect(@Nullable ConnectCallback callback) { 329 mConnectCallback = callback; 330 // We should flag this network to auto-open captive portal since this method represents 331 // the user manually connecting to a network (i.e. not auto-join). 332 mShouldAutoOpenCaptivePortal = true; 333 mWifiManager.stopRestrictingAutoJoinToSubscriptionId(); 334 if (isSaved() || isSuggestion()) { 335 if (Utils.isSimCredential(mTargetWifiConfig) 336 && !Utils.isSimPresent(mContext, mTargetWifiConfig.carrierId)) { 337 if (callback != null) { 338 mCallbackHandler.post(() -> 339 callback.onConnectResult( 340 ConnectCallback.CONNECT_STATUS_FAILURE_SIM_ABSENT)); 341 } 342 return; 343 } 344 // Saved/suggested network 345 mWifiManager.connect(mTargetWifiConfig.networkId, new ConnectActionListener()); 346 } else { 347 if (mTargetSecurityTypes.contains(SECURITY_TYPE_OWE)) { 348 // OWE network 349 final WifiConfiguration oweConfig = new WifiConfiguration(); 350 oweConfig.SSID = "\"" + mKey.getScanResultKey().getSsid() + "\""; 351 oweConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE); 352 mWifiManager.connect(oweConfig, new ConnectActionListener()); 353 if (mTargetSecurityTypes.contains(SECURITY_TYPE_OPEN)) { 354 // Add an extra Open config for OWE transition networks 355 final WifiConfiguration openConfig = new WifiConfiguration(); 356 openConfig.SSID = "\"" + mKey.getScanResultKey().getSsid() + "\""; 357 openConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); 358 mWifiManager.save(openConfig, null); 359 } 360 } else if (mTargetSecurityTypes.contains(SECURITY_TYPE_OPEN)) { 361 // Open network 362 final WifiConfiguration openConfig = new WifiConfiguration(); 363 openConfig.SSID = "\"" + mKey.getScanResultKey().getSsid() + "\""; 364 openConfig.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); 365 mWifiManager.connect(openConfig, new ConnectActionListener()); 366 } else { 367 // Secure network 368 if (callback != null) { 369 mCallbackHandler.post(() -> 370 callback.onConnectResult( 371 ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG)); 372 } 373 } 374 } 375 } 376 377 @Override canDisconnect()378 public boolean canDisconnect() { 379 return getConnectedState() == CONNECTED_STATE_CONNECTED; 380 } 381 382 @Override disconnect(@ullable DisconnectCallback callback)383 public synchronized void disconnect(@Nullable DisconnectCallback callback) { 384 if (canDisconnect()) { 385 mCalledDisconnect = true; 386 mDisconnectCallback = callback; 387 mCallbackHandler.postDelayed(() -> { 388 if (callback != null && mCalledDisconnect) { 389 callback.onDisconnectResult( 390 DisconnectCallback.DISCONNECT_STATUS_FAILURE_UNKNOWN); 391 } 392 }, 10_000 /* delayMillis */); 393 mWifiManager.disableEphemeralNetwork("\"" + mKey.getScanResultKey().getSsid() + "\""); 394 mWifiManager.disconnect(); 395 } 396 } 397 398 @Override canForget()399 public boolean canForget() { 400 return getWifiConfiguration() != null; 401 } 402 403 @Override forget(@ullable ForgetCallback callback)404 public synchronized void forget(@Nullable ForgetCallback callback) { 405 if (canForget()) { 406 mForgetCallback = callback; 407 mWifiManager.forget(mTargetWifiConfig.networkId, new ForgetActionListener()); 408 } 409 } 410 411 @Override canSignIn()412 public synchronized boolean canSignIn() { 413 return mNetwork != null 414 && mNetworkCapabilities != null 415 && mNetworkCapabilities.hasCapability( 416 NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); 417 } 418 419 @Override signIn(@ullable SignInCallback callback)420 public void signIn(@Nullable SignInCallback callback) { 421 if (canSignIn()) { 422 NonSdkApiWrapper.startCaptivePortalApp( 423 mContext.getSystemService(ConnectivityManager.class), mNetwork); 424 } 425 } 426 427 /** 428 * Returns whether the network can be shared via QR code. 429 * See https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11 430 */ 431 @Override canShare()432 public synchronized boolean canShare() { 433 if (mInjector.isDemoMode()) { 434 return false; 435 } 436 437 WifiConfiguration wifiConfig = getWifiConfiguration(); 438 if (wifiConfig == null) { 439 return false; 440 } 441 442 if (BuildCompat.isAtLeastT() && mUserManager.hasUserRestrictionForUser( 443 UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, 444 UserHandle.getUserHandleForUid(wifiConfig.creatorUid)) 445 && Utils.isDeviceOrProfileOwner(wifiConfig.creatorUid, 446 wifiConfig.creatorName, mContext)) { 447 return false; 448 } 449 450 for (int securityType : mTargetSecurityTypes) { 451 switch (securityType) { 452 case SECURITY_TYPE_OPEN: 453 case SECURITY_TYPE_OWE: 454 case SECURITY_TYPE_WEP: 455 case SECURITY_TYPE_PSK: 456 case SECURITY_TYPE_SAE: 457 return true; 458 } 459 } 460 return false; 461 } 462 463 /** 464 * Returns whether the user can use Easy Connect to onboard a device to the network. 465 * See https://www.wi-fi.org/discover-wi-fi/wi-fi-easy-connect 466 */ 467 @Override canEasyConnect()468 public synchronized boolean canEasyConnect() { 469 if (mInjector.isDemoMode()) { 470 return false; 471 } 472 473 WifiConfiguration wifiConfig = getWifiConfiguration(); 474 if (wifiConfig == null) { 475 return false; 476 } 477 478 if (!mWifiManager.isEasyConnectSupported()) { 479 return false; 480 } 481 482 if (BuildCompat.isAtLeastT() && mUserManager.hasUserRestrictionForUser( 483 UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, 484 UserHandle.getUserHandleForUid(wifiConfig.creatorUid)) 485 && Utils.isDeviceOrProfileOwner(wifiConfig.creatorUid, 486 wifiConfig.creatorName, mContext)) { 487 return false; 488 } 489 490 // DPP 1.0 only supports WPA2 and WPA3. 491 return mTargetSecurityTypes.contains(SECURITY_TYPE_PSK) 492 || mTargetSecurityTypes.contains(SECURITY_TYPE_SAE); 493 } 494 495 @Override 496 @MeteredChoice getMeteredChoice()497 public synchronized int getMeteredChoice() { 498 if (!isSuggestion() && mTargetWifiConfig != null) { 499 final int meteredOverride = mTargetWifiConfig.meteredOverride; 500 if (meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED) { 501 return METERED_CHOICE_METERED; 502 } else if (meteredOverride == WifiConfiguration.METERED_OVERRIDE_NOT_METERED) { 503 return METERED_CHOICE_UNMETERED; 504 } 505 } 506 return METERED_CHOICE_AUTO; 507 } 508 509 @Override canSetMeteredChoice()510 public boolean canSetMeteredChoice() { 511 return getWifiConfiguration() != null; 512 } 513 514 @Override setMeteredChoice(int meteredChoice)515 public synchronized void setMeteredChoice(int meteredChoice) { 516 if (!canSetMeteredChoice()) { 517 return; 518 } 519 520 // Refresh the current config so we don't overwrite any changes that we haven't gotten 521 // the CONFIGURED_NETWORKS_CHANGED broadcast for yet. 522 refreshTargetWifiConfig(); 523 if (meteredChoice == METERED_CHOICE_AUTO) { 524 mTargetWifiConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_NONE; 525 } else if (meteredChoice == METERED_CHOICE_METERED) { 526 mTargetWifiConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED; 527 } else if (meteredChoice == METERED_CHOICE_UNMETERED) { 528 mTargetWifiConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED; 529 } 530 mWifiManager.save(mTargetWifiConfig, null /* listener */); 531 } 532 533 @Override canSetPrivacy()534 public boolean canSetPrivacy() { 535 return isSaved(); 536 } 537 538 @Override 539 @Privacy getPrivacy()540 public synchronized int getPrivacy() { 541 if (mTargetWifiConfig != null 542 && mTargetWifiConfig.macRandomizationSetting 543 == WifiConfiguration.RANDOMIZATION_NONE) { 544 return PRIVACY_DEVICE_MAC; 545 } else { 546 return PRIVACY_RANDOMIZED_MAC; 547 } 548 } 549 550 @Override setPrivacy(int privacy)551 public synchronized void setPrivacy(int privacy) { 552 if (!canSetPrivacy()) { 553 return; 554 } 555 // Refresh the current config so we don't overwrite any changes that we haven't gotten 556 // the CONFIGURED_NETWORKS_CHANGED broadcast for yet. 557 refreshTargetWifiConfig(); 558 mTargetWifiConfig.macRandomizationSetting = privacy == PRIVACY_RANDOMIZED_MAC 559 ? WifiConfiguration.RANDOMIZATION_AUTO : WifiConfiguration.RANDOMIZATION_NONE; 560 mWifiManager.save(mTargetWifiConfig, null /* listener */); 561 } 562 563 @Override isAutoJoinEnabled()564 public synchronized boolean isAutoJoinEnabled() { 565 if (mTargetWifiConfig == null) { 566 return false; 567 } 568 569 return mTargetWifiConfig.allowAutojoin; 570 } 571 572 @Override canSetAutoJoinEnabled()573 public boolean canSetAutoJoinEnabled() { 574 return isSaved() || isSuggestion(); 575 } 576 577 @Override setAutoJoinEnabled(boolean enabled)578 public synchronized void setAutoJoinEnabled(boolean enabled) { 579 if (mTargetWifiConfig == null || !canSetAutoJoinEnabled()) { 580 return; 581 } 582 583 mWifiManager.allowAutojoin(mTargetWifiConfig.networkId, enabled); 584 } 585 586 @Override getSecurityString(boolean concise)587 public synchronized String getSecurityString(boolean concise) { 588 return Utils.getSecurityString(mContext, mTargetSecurityTypes, concise); 589 } 590 591 @Override getStandardString()592 public synchronized String getStandardString() { 593 if (mWifiInfo != null) { 594 return Utils.getStandardString(mContext, mWifiInfo.getWifiStandard()); 595 } 596 if (!mTargetScanResults.isEmpty()) { 597 return Utils.getStandardString(mContext, mTargetScanResults.get(0).getWifiStandard()); 598 } 599 return ""; 600 } 601 602 @Override getBandString()603 public synchronized String getBandString() { 604 if (mWifiInfo != null) { 605 return Utils.wifiInfoToBandString(mContext, mWifiInfo); 606 } 607 if (!mTargetScanResults.isEmpty()) { 608 return Utils.frequencyToBandString(mContext, mTargetScanResults.get(0).frequency); 609 } 610 return ""; 611 } 612 613 @Override shouldEditBeforeConnect()614 public synchronized boolean shouldEditBeforeConnect() { 615 WifiConfiguration wifiConfig = getWifiConfiguration(); 616 if (wifiConfig == null) { 617 return false; 618 } 619 620 // The network is disabled because of one of the authentication problems. 621 NetworkSelectionStatus networkSelectionStatus = wifiConfig.getNetworkSelectionStatus(); 622 if (networkSelectionStatus.getNetworkSelectionStatus() != NETWORK_SELECTION_ENABLED 623 || !networkSelectionStatus.hasEverConnected()) { 624 if (networkSelectionStatus.getDisableReasonCounter(DISABLED_AUTHENTICATION_FAILURE) > 0 625 || networkSelectionStatus.getDisableReasonCounter( 626 DISABLED_BY_WRONG_PASSWORD) > 0 627 || networkSelectionStatus.getDisableReasonCounter( 628 DISABLED_AUTHENTICATION_NO_CREDENTIALS) > 0) { 629 return true; 630 } 631 } 632 633 return false; 634 } 635 636 @WorkerThread updateScanResultInfo(@ullable List<ScanResult> scanResults)637 synchronized void updateScanResultInfo(@Nullable List<ScanResult> scanResults) 638 throws IllegalArgumentException { 639 if (scanResults == null) scanResults = new ArrayList<>(); 640 641 final String ssid = mKey.getScanResultKey().getSsid(); 642 for (ScanResult scan : scanResults) { 643 if (!TextUtils.equals(scan.SSID, ssid)) { 644 throw new IllegalArgumentException( 645 "Attempted to update with wrong SSID! Expected: " 646 + ssid + ", Actual: " + scan.SSID + ", ScanResult: " + scan); 647 } 648 } 649 // Populate the cached scan result map 650 mMatchingScanResults.clear(); 651 final Set<Integer> keySecurityTypes = mKey.getScanResultKey().getSecurityTypes(); 652 for (ScanResult scan : scanResults) { 653 for (int security : getSecurityTypesFromScanResult(scan)) { 654 if (!keySecurityTypes.contains(security) || !isSecurityTypeSupported(security)) { 655 continue; 656 } 657 if (!mMatchingScanResults.containsKey(security)) { 658 mMatchingScanResults.put(security, new ArrayList<>()); 659 } 660 mMatchingScanResults.get(security).add(scan); 661 } 662 } 663 664 updateSecurityTypes(); 665 updateTargetScanResultInfo(); 666 notifyOnUpdated(); 667 } 668 updateTargetScanResultInfo()669 private synchronized void updateTargetScanResultInfo() { 670 // Update the level using the scans matching the target security type 671 final ScanResult bestScanResult = getBestScanResultByLevel(mTargetScanResults); 672 673 if (getConnectedState() == CONNECTED_STATE_DISCONNECTED) { 674 mLevel = bestScanResult != null 675 ? mWifiManager.calculateSignalLevel(bestScanResult.level) 676 : WIFI_LEVEL_UNREACHABLE; 677 } 678 } 679 680 @WorkerThread 681 @Override onNetworkCapabilitiesChanged( @onNull Network network, @NonNull NetworkCapabilities capabilities)682 synchronized void onNetworkCapabilitiesChanged( 683 @NonNull Network network, @NonNull NetworkCapabilities capabilities) { 684 super.onNetworkCapabilitiesChanged(network, capabilities); 685 686 // Auto-open an available captive portal if the user manually connected to this network. 687 if (canSignIn() && mShouldAutoOpenCaptivePortal) { 688 mShouldAutoOpenCaptivePortal = false; 689 signIn(null /* callback */); 690 } 691 } 692 693 @WorkerThread updateConfig(@ullable List<WifiConfiguration> wifiConfigs)694 synchronized void updateConfig(@Nullable List<WifiConfiguration> wifiConfigs) 695 throws IllegalArgumentException { 696 if (wifiConfigs == null) { 697 wifiConfigs = Collections.emptyList(); 698 } 699 700 final ScanResultKey scanResultKey = mKey.getScanResultKey(); 701 final String ssid = scanResultKey.getSsid(); 702 final Set<Integer> securityTypes = scanResultKey.getSecurityTypes(); 703 mMatchingWifiConfigs.clear(); 704 for (WifiConfiguration config : wifiConfigs) { 705 if (!TextUtils.equals(ssid, sanitizeSsid(config.SSID))) { 706 throw new IllegalArgumentException( 707 "Attempted to update with wrong SSID!" 708 + " Expected: " + ssid 709 + ", Actual: " + sanitizeSsid(config.SSID) 710 + ", Config: " + config); 711 } 712 for (int securityType : getSecurityTypesFromWifiConfiguration(config)) { 713 if (!securityTypes.contains(securityType)) { 714 throw new IllegalArgumentException( 715 "Attempted to update with wrong security!" 716 + " Expected one of: " + securityTypes 717 + ", Actual: " + securityType 718 + ", Config: " + config); 719 } 720 if (isSecurityTypeSupported(securityType)) { 721 mMatchingWifiConfigs.put(securityType, config); 722 } 723 } 724 } 725 updateSecurityTypes(); 726 updateTargetScanResultInfo(); 727 notifyOnUpdated(); 728 } 729 isSecurityTypeSupported(int security)730 private boolean isSecurityTypeSupported(int security) { 731 switch (security) { 732 case SECURITY_TYPE_SAE: 733 return mIsWpa3SaeSupported; 734 case SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT: 735 return mIsWpa3SuiteBSupported; 736 case SECURITY_TYPE_OWE: 737 return mIsEnhancedOpenSupported; 738 default: 739 return true; 740 } 741 } 742 refreshTargetWifiConfig()743 private void refreshTargetWifiConfig() { 744 for (WifiConfiguration config : mWifiManager.getPrivilegedConfiguredNetworks()) { 745 if (config.networkId == mTargetWifiConfig.networkId) { 746 mTargetWifiConfig = config; 747 break; 748 } 749 } 750 } 751 752 @Override updateSecurityTypes()753 protected synchronized void updateSecurityTypes() { 754 mTargetSecurityTypes.clear(); 755 if (mWifiInfo != null) { 756 final int wifiInfoSecurity = mWifiInfo.getCurrentSecurityType(); 757 if (wifiInfoSecurity != SECURITY_TYPE_UNKNOWN) { 758 mTargetSecurityTypes.add(mWifiInfo.getCurrentSecurityType()); 759 } 760 } 761 762 Set<Integer> configSecurityTypes = mMatchingWifiConfigs.keySet(); 763 if (mTargetSecurityTypes.isEmpty() && mKey.isTargetingNewNetworks()) { 764 // If we are targeting new networks for configuration, then we should select the 765 // security type of all visible scan results if we don't have any configs that 766 // can connect to them. This will let us configure this entry as a new network. 767 boolean configMatchesScans = false; 768 Set<Integer> scanSecurityTypes = mMatchingScanResults.keySet(); 769 for (int configSecurity : configSecurityTypes) { 770 if (scanSecurityTypes.contains(configSecurity)) { 771 configMatchesScans = true; 772 break; 773 } 774 } 775 if (!configMatchesScans) { 776 mTargetSecurityTypes.addAll(scanSecurityTypes); 777 } 778 } 779 780 // Use security types of any configs we have 781 if (mTargetSecurityTypes.isEmpty()) { 782 mTargetSecurityTypes.addAll(configSecurityTypes); 783 } 784 785 // Default to the key security types. This shouldn't happen since we should always have 786 // scans or configs. 787 if (mTargetSecurityTypes.isEmpty()) { 788 mTargetSecurityTypes.addAll(mKey.getScanResultKey().getSecurityTypes()); 789 } 790 791 // The target wifi config should match the security type we return in getSecurity(), since 792 // clients (QR code/DPP, modify network page) may expect them to match. 793 mTargetWifiConfig = mMatchingWifiConfigs.get( 794 getSingleSecurityTypeFromMultipleSecurityTypes(mTargetSecurityTypes)); 795 // Collect target scan results in a set to remove duplicates when one scan matches multiple 796 // security types. 797 Set<ScanResult> targetScanResultSet = new ArraySet<>(); 798 for (int security : mTargetSecurityTypes) { 799 if (mMatchingScanResults.containsKey(security)) { 800 targetScanResultSet.addAll(mMatchingScanResults.get(security)); 801 } 802 } 803 mTargetScanResults.clear(); 804 mTargetScanResults.addAll(targetScanResultSet); 805 } 806 807 /** 808 * Sets whether the suggested config for this entry is shareable to the user or not. 809 */ 810 @WorkerThread setUserShareable(boolean isUserShareable)811 synchronized void setUserShareable(boolean isUserShareable) { 812 mIsUserShareable = isUserShareable; 813 } 814 815 /** 816 * Returns whether the suggested config for this entry is shareable to the user or not. 817 */ 818 @WorkerThread isUserShareable()819 synchronized boolean isUserShareable() { 820 return mIsUserShareable; 821 } 822 823 @WorkerThread connectionInfoMatches(@onNull WifiInfo wifiInfo)824 protected synchronized boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo) { 825 if (wifiInfo.isPasspointAp() || wifiInfo.isOsuAp()) { 826 return false; 827 } 828 for (WifiConfiguration config : mMatchingWifiConfigs.values()) { 829 if (config.networkId == wifiInfo.getNetworkId()) { 830 return true; 831 } 832 } 833 return false; 834 } 835 836 @NonNull ssidAndSecurityTypeToStandardWifiEntryKey( @onNull String ssid, int security)837 static StandardWifiEntryKey ssidAndSecurityTypeToStandardWifiEntryKey( 838 @NonNull String ssid, int security) { 839 return ssidAndSecurityTypeToStandardWifiEntryKey( 840 ssid, security, false /* isTargetingNewNetworks */); 841 } 842 843 @NonNull ssidAndSecurityTypeToStandardWifiEntryKey( @onNull String ssid, int security, boolean isTargetingNewNetworks)844 static StandardWifiEntryKey ssidAndSecurityTypeToStandardWifiEntryKey( 845 @NonNull String ssid, int security, boolean isTargetingNewNetworks) { 846 return new StandardWifiEntryKey( 847 new ScanResultKey(ssid, Collections.singletonList(security)), 848 isTargetingNewNetworks); 849 } 850 851 @Override getScanResultDescription()852 protected synchronized String getScanResultDescription() { 853 if (mTargetScanResults.size() == 0) { 854 return ""; 855 } 856 857 final StringBuilder description = new StringBuilder(); 858 description.append("["); 859 description.append(getScanResultDescription(MIN_FREQ_24GHZ, MAX_FREQ_24GHZ)).append(";"); 860 description.append(getScanResultDescription(MIN_FREQ_5GHZ, MAX_FREQ_5GHZ)).append(";"); 861 description.append(getScanResultDescription(MIN_FREQ_6GHZ, MAX_FREQ_6GHZ)).append(";"); 862 description.append(getScanResultDescription(MIN_FREQ_60GHZ, MAX_FREQ_60GHZ)); 863 description.append("]"); 864 return description.toString(); 865 } 866 getScanResultDescription(int minFrequency, int maxFrequency)867 private synchronized String getScanResultDescription(int minFrequency, int maxFrequency) { 868 final List<ScanResult> scanResults = mTargetScanResults.stream() 869 .filter(scanResult -> scanResult.frequency >= minFrequency 870 && scanResult.frequency <= maxFrequency) 871 .sorted(Comparator.comparingInt(scanResult -> -1 * scanResult.level)) 872 .collect(Collectors.toList()); 873 874 final int scanResultCount = scanResults.size(); 875 if (scanResultCount == 0) { 876 return ""; 877 } 878 879 final StringBuilder description = new StringBuilder(); 880 description.append("(").append(scanResultCount).append(")"); 881 if (scanResultCount > MAX_VERBOSE_LOG_DISPLAY_SCANRESULT_COUNT) { 882 final int maxLavel = scanResults.stream() 883 .mapToInt(scanResult -> scanResult.level).max().getAsInt(); 884 description.append("max=").append(maxLavel).append(","); 885 } 886 final long nowMs = SystemClock.elapsedRealtime(); 887 scanResults.forEach(scanResult -> 888 description.append(getScanResultDescription(scanResult, nowMs))); 889 return description.toString(); 890 } 891 892 // TODO(b/227622961): Remove the suppression once the linter recognizes BuildCompat.isAtLeastT() 893 @SuppressLint({"NewApi", "SwitchIntDef"}) getScanResultDescription(ScanResult scanResult, long nowMs)894 private synchronized String getScanResultDescription(ScanResult scanResult, long nowMs) { 895 final StringBuilder description = new StringBuilder(); 896 description.append(" \n{"); 897 description.append(scanResult.BSSID); 898 if (mWifiInfo != null && scanResult.BSSID.equals(mWifiInfo.getBSSID())) { 899 description.append("*"); 900 } 901 description.append("=").append(scanResult.frequency); 902 description.append(",").append(scanResult.level); 903 int wifiStandard = scanResult.getWifiStandard(); 904 description.append(",").append(Utils.getStandardString(mContext, wifiStandard)); 905 if (BuildCompat.isAtLeastT() && wifiStandard == ScanResult.WIFI_STANDARD_11BE) { 906 description.append(",mldMac=").append(scanResult.getApMldMacAddress()); 907 description.append(",linkId=").append(scanResult.getApMloLinkId()); 908 description.append(",affLinks="); 909 StringJoiner affLinks = new StringJoiner(",", "[", "]"); 910 for (MloLink link : scanResult.getAffiliatedMloLinks()) { 911 final int scanResultBand; 912 switch (link.getBand()) { 913 case WifiScanner.WIFI_BAND_24_GHZ: 914 scanResultBand = ScanResult.WIFI_BAND_24_GHZ; 915 break; 916 case WifiScanner.WIFI_BAND_5_GHZ: 917 scanResultBand = ScanResult.WIFI_BAND_5_GHZ; 918 break; 919 case WifiScanner.WIFI_BAND_6_GHZ: 920 scanResultBand = ScanResult.WIFI_BAND_6_GHZ; 921 break; 922 case WifiScanner.WIFI_BAND_60_GHZ: 923 scanResultBand = ScanResult.WIFI_BAND_60_GHZ; 924 break; 925 default: 926 Log.e(TAG, "Unknown MLO link band: " + link.getBand()); 927 scanResultBand = ScanResult.UNSPECIFIED; 928 break; 929 } 930 affLinks.add(new StringJoiner(",", "{", "}") 931 .add("apMacAddr=" + link.getApMacAddress()) 932 .add("freq=" + ScanResult.convertChannelToFrequencyMhzIfSupported( 933 link.getChannel(), scanResultBand)) 934 .toString()); 935 } 936 description.append(affLinks.toString()); 937 } 938 final int ageSeconds = (int) (nowMs - scanResult.timestamp / 1000) / 1000; 939 description.append(",").append(ageSeconds).append("s"); 940 description.append("}"); 941 return description.toString(); 942 } 943 944 @Override getNetworkSelectionDescription()945 String getNetworkSelectionDescription() { 946 return Utils.getNetworkSelectionDescription(getWifiConfiguration()); 947 } 948 949 // TODO(b/227622961): Remove the suppression once the linter recognizes BuildCompat.isAtLeastT() 950 @SuppressLint("NewApi") updateAdminRestrictions()951 void updateAdminRestrictions() { 952 if (!BuildCompat.isAtLeastT()) { 953 return; 954 } 955 if (mUserManager != null) { 956 mHasAddConfigUserRestriction = mUserManager.hasUserRestriction( 957 UserManager.DISALLOW_ADD_WIFI_CONFIG); 958 } 959 if (mDevicePolicyManager != null) { 960 //check minimum security level restriction 961 int adminMinimumSecurityLevel = 962 mDevicePolicyManager.getMinimumRequiredWifiSecurityLevel(); 963 if (adminMinimumSecurityLevel != DevicePolicyManager.WIFI_SECURITY_OPEN) { 964 boolean securityRestrictionPassed = false; 965 for (int type : getSecurityTypes()) { 966 int securityLevel = Utils.convertSecurityTypeToDpmWifiSecurity(type); 967 968 // Skip unknown security type since security level cannot be determined. 969 // If all the security types are unknown when the minimum security level 970 // restriction is set, the device cannot connect to this network. 971 if (securityLevel == Utils.DPM_SECURITY_TYPE_UNKNOWN) continue; 972 973 if (adminMinimumSecurityLevel <= securityLevel) { 974 securityRestrictionPassed = true; 975 break; 976 } 977 } 978 if (!securityRestrictionPassed) { 979 mIsAdminRestricted = true; 980 return; 981 } 982 } 983 //check SSID restriction 984 WifiSsidPolicy policy = NonSdkApiWrapper.getWifiSsidPolicy(mDevicePolicyManager); 985 if (policy != null) { 986 int policyType = policy.getPolicyType(); 987 Set<WifiSsid> ssids = policy.getSsids(); 988 989 if (policyType == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST 990 && !ssids.contains( 991 WifiSsid.fromBytes(getSsid().getBytes(StandardCharsets.UTF_8)))) { 992 mIsAdminRestricted = true; 993 return; 994 } 995 if (policyType == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST 996 && ssids.contains( 997 WifiSsid.fromBytes(getSsid().getBytes(StandardCharsets.UTF_8)))) { 998 mIsAdminRestricted = true; 999 return; 1000 } 1001 } 1002 } 1003 mIsAdminRestricted = false; 1004 } 1005 1006 @Override hasAdminRestrictions()1007 public synchronized boolean hasAdminRestrictions() { 1008 if ((mHasAddConfigUserRestriction && !(isSaved() || isSuggestion())) 1009 || mIsAdminRestricted) { 1010 return true; 1011 } 1012 return false; 1013 } 1014 1015 /** 1016 * Class that identifies a unique StandardWifiEntry by the following identifiers 1017 * 1) ScanResult key (SSID + grouped security types) 1018 * 2) Suggestion profile key 1019 * 3) Is network request or not 1020 * 4) Should prioritize configuring a new network (i.e. target the security type of an 1021 * in-range unsaved network, rather than a config that has no scans) 1022 */ 1023 static class StandardWifiEntryKey { 1024 private static final String KEY_SCAN_RESULT_KEY = "SCAN_RESULT_KEY"; 1025 private static final String KEY_SUGGESTION_PROFILE_KEY = "SUGGESTION_PROFILE_KEY"; 1026 private static final String KEY_IS_NETWORK_REQUEST = "IS_NETWORK_REQUEST"; 1027 private static final String KEY_IS_TARGETING_NEW_NETWORKS = "IS_TARGETING_NEW_NETWORKS"; 1028 1029 @NonNull private ScanResultKey mScanResultKey; 1030 @Nullable private String mSuggestionProfileKey; 1031 private boolean mIsNetworkRequest; 1032 private boolean mIsTargetingNewNetworks = false; 1033 1034 /** 1035 * Creates a StandardWifiEntryKey matching a ScanResultKey 1036 */ StandardWifiEntryKey(@onNull ScanResultKey scanResultKey)1037 StandardWifiEntryKey(@NonNull ScanResultKey scanResultKey) { 1038 this(scanResultKey, false /* isTargetingNewNetworks */); 1039 } 1040 1041 /** 1042 * Creates a StandardWifiEntryKey matching a ScanResultKey and sets whether the entry 1043 * should target new networks or not. 1044 */ StandardWifiEntryKey(@onNull ScanResultKey scanResultKey, boolean isTargetingNewNetworks)1045 StandardWifiEntryKey(@NonNull ScanResultKey scanResultKey, boolean isTargetingNewNetworks) { 1046 mScanResultKey = scanResultKey; 1047 mIsTargetingNewNetworks = isTargetingNewNetworks; 1048 } 1049 1050 /** 1051 * Creates a StandardWifiEntryKey matching a WifiConfiguration 1052 */ StandardWifiEntryKey(@onNull WifiConfiguration config)1053 StandardWifiEntryKey(@NonNull WifiConfiguration config) { 1054 this(config, false /* isTargetingNewNetworks */); 1055 } 1056 1057 /** 1058 * Creates a StandardWifiEntryKey matching a WifiConfiguration and sets whether the entry 1059 * should target new networks or not. 1060 */ StandardWifiEntryKey(@onNull WifiConfiguration config, boolean isTargetingNewNetworks)1061 StandardWifiEntryKey(@NonNull WifiConfiguration config, boolean isTargetingNewNetworks) { 1062 mScanResultKey = new ScanResultKey(config); 1063 if (config.fromWifiNetworkSuggestion) { 1064 mSuggestionProfileKey = new StringJoiner(",") 1065 .add(config.creatorName) 1066 .add(String.valueOf(config.carrierId)) 1067 .add(String.valueOf(config.subscriptionId)) 1068 .toString(); 1069 } else if (config.fromWifiNetworkSpecifier) { 1070 mIsNetworkRequest = true; 1071 } 1072 mIsTargetingNewNetworks = isTargetingNewNetworks; 1073 } 1074 1075 /** 1076 * Creates a StandardWifiEntryKey from its String representation. 1077 */ StandardWifiEntryKey(@onNull String string)1078 StandardWifiEntryKey(@NonNull String string) { 1079 mScanResultKey = new ScanResultKey(); 1080 if (!string.startsWith(KEY_PREFIX)) { 1081 Log.e(TAG, "String key does not start with key prefix!"); 1082 return; 1083 } 1084 try { 1085 final JSONObject keyJson = new JSONObject(string.substring(KEY_PREFIX.length())); 1086 if (keyJson.has(KEY_SCAN_RESULT_KEY)) { 1087 mScanResultKey = new ScanResultKey(keyJson.getString(KEY_SCAN_RESULT_KEY)); 1088 } 1089 if (keyJson.has(KEY_SUGGESTION_PROFILE_KEY)) { 1090 mSuggestionProfileKey = keyJson.getString(KEY_SUGGESTION_PROFILE_KEY); 1091 } 1092 if (keyJson.has(KEY_IS_NETWORK_REQUEST)) { 1093 mIsNetworkRequest = keyJson.getBoolean(KEY_IS_NETWORK_REQUEST); 1094 } 1095 if (keyJson.has(KEY_IS_TARGETING_NEW_NETWORKS)) { 1096 mIsTargetingNewNetworks = keyJson.getBoolean( 1097 KEY_IS_TARGETING_NEW_NETWORKS); 1098 } 1099 } catch (JSONException e) { 1100 Log.e(TAG, "JSONException while converting StandardWifiEntryKey to string: " + e); 1101 } 1102 } 1103 1104 /** 1105 * Returns the JSON String representation of this StandardWifiEntryKey. 1106 */ 1107 @Override toString()1108 public String toString() { 1109 final JSONObject keyJson = new JSONObject(); 1110 try { 1111 if (mScanResultKey != null) { 1112 keyJson.put(KEY_SCAN_RESULT_KEY, mScanResultKey.toString()); 1113 } 1114 if (mSuggestionProfileKey != null) { 1115 keyJson.put(KEY_SUGGESTION_PROFILE_KEY, mSuggestionProfileKey); 1116 } 1117 if (mIsNetworkRequest) { 1118 keyJson.put(KEY_IS_NETWORK_REQUEST, mIsNetworkRequest); 1119 } 1120 if (mIsTargetingNewNetworks) { 1121 keyJson.put(KEY_IS_TARGETING_NEW_NETWORKS, mIsTargetingNewNetworks); 1122 } 1123 } catch (JSONException e) { 1124 Log.wtf(TAG, "JSONException while converting StandardWifiEntryKey to string: " + e); 1125 } 1126 return KEY_PREFIX + keyJson.toString(); 1127 } 1128 1129 /** 1130 * Returns the ScanResultKey of this StandardWifiEntryKey to match against ScanResults 1131 */ getScanResultKey()1132 @NonNull ScanResultKey getScanResultKey() { 1133 return mScanResultKey; 1134 } 1135 getSuggestionProfileKey()1136 @Nullable String getSuggestionProfileKey() { 1137 return mSuggestionProfileKey; 1138 } 1139 isNetworkRequest()1140 boolean isNetworkRequest() { 1141 return mIsNetworkRequest; 1142 } 1143 isTargetingNewNetworks()1144 boolean isTargetingNewNetworks() { 1145 return mIsTargetingNewNetworks; 1146 } 1147 1148 @Override equals(Object o)1149 public boolean equals(Object o) { 1150 if (this == o) return true; 1151 if (o == null || getClass() != o.getClass()) return false; 1152 StandardWifiEntryKey that = (StandardWifiEntryKey) o; 1153 return Objects.equals(mScanResultKey, that.mScanResultKey) 1154 && TextUtils.equals(mSuggestionProfileKey, that.mSuggestionProfileKey) 1155 && mIsNetworkRequest == that.mIsNetworkRequest; 1156 } 1157 1158 @Override hashCode()1159 public int hashCode() { 1160 return Objects.hash(mScanResultKey, mSuggestionProfileKey, mIsNetworkRequest); 1161 } 1162 } 1163 1164 /** 1165 * Class for matching ScanResults to StandardWifiEntry by SSID and security type grouping. 1166 */ 1167 static class ScanResultKey { 1168 private static final String KEY_SSID = "SSID"; 1169 private static final String KEY_SECURITY_TYPES = "SECURITY_TYPES"; 1170 1171 @Nullable private String mSsid; 1172 @NonNull private Set<Integer> mSecurityTypes = new ArraySet<>(); 1173 ScanResultKey()1174 ScanResultKey() { 1175 } 1176 ScanResultKey(@ullable String ssid, List<Integer> securityTypes)1177 ScanResultKey(@Nullable String ssid, List<Integer> securityTypes) { 1178 mSsid = ssid; 1179 for (int security : securityTypes) { 1180 // Add any security types that merge to the same WifiEntry 1181 switch (security) { 1182 case SECURITY_TYPE_PASSPOINT_R1_R2: 1183 case SECURITY_TYPE_PASSPOINT_R3: 1184 // Filter out Passpoint security type from key. 1185 continue; 1186 // Group OPEN and OWE networks together 1187 case SECURITY_TYPE_OPEN: 1188 mSecurityTypes.add(SECURITY_TYPE_OWE); 1189 break; 1190 case SECURITY_TYPE_OWE: 1191 mSecurityTypes.add(SECURITY_TYPE_OPEN); 1192 break; 1193 // Group PSK and SAE networks together 1194 case SECURITY_TYPE_PSK: 1195 mSecurityTypes.add(SECURITY_TYPE_SAE); 1196 break; 1197 case SECURITY_TYPE_SAE: 1198 mSecurityTypes.add(SECURITY_TYPE_PSK); 1199 break; 1200 // Group EAP and EAP_WPA3_ENTERPRISE networks together 1201 case SECURITY_TYPE_EAP: 1202 mSecurityTypes.add(SECURITY_TYPE_EAP_WPA3_ENTERPRISE); 1203 break; 1204 case SECURITY_TYPE_EAP_WPA3_ENTERPRISE: 1205 mSecurityTypes.add(SECURITY_TYPE_EAP); 1206 break; 1207 } 1208 mSecurityTypes.add(security); 1209 } 1210 } 1211 1212 /** 1213 * Creates a ScanResultKey from a ScanResult's SSID and security type grouping. 1214 * @param scanResult 1215 */ ScanResultKey(@onNull ScanResult scanResult)1216 ScanResultKey(@NonNull ScanResult scanResult) { 1217 this(scanResult.SSID, getSecurityTypesFromScanResult(scanResult)); 1218 } 1219 1220 /** 1221 * Creates a ScanResultKey from a WifiConfiguration's SSID and security type grouping. 1222 */ ScanResultKey(@onNull WifiConfiguration wifiConfiguration)1223 ScanResultKey(@NonNull WifiConfiguration wifiConfiguration) { 1224 this(sanitizeSsid(wifiConfiguration.SSID), 1225 getSecurityTypesFromWifiConfiguration(wifiConfiguration)); 1226 } 1227 1228 /** 1229 * Creates a ScanResultKey from its String representation. 1230 */ ScanResultKey(@onNull String string)1231 ScanResultKey(@NonNull String string) { 1232 try { 1233 final JSONObject keyJson = new JSONObject(string); 1234 mSsid = keyJson.getString(KEY_SSID); 1235 final JSONArray securityTypesJson = 1236 keyJson.getJSONArray(KEY_SECURITY_TYPES); 1237 for (int i = 0; i < securityTypesJson.length(); i++) { 1238 mSecurityTypes.add(securityTypesJson.getInt(i)); 1239 } 1240 } catch (JSONException e) { 1241 Log.wtf(TAG, "JSONException while constructing ScanResultKey from string: " + e); 1242 } 1243 } 1244 1245 /** 1246 * Returns the JSON String representation of this ScanResultEntry. 1247 */ 1248 @Override toString()1249 public String toString() { 1250 final JSONObject keyJson = new JSONObject(); 1251 try { 1252 if (mSsid != null) { 1253 keyJson.put(KEY_SSID, mSsid); 1254 } 1255 if (!mSecurityTypes.isEmpty()) { 1256 final JSONArray securityTypesJson = new JSONArray(); 1257 for (int security : mSecurityTypes) { 1258 securityTypesJson.put(security); 1259 } 1260 keyJson.put(KEY_SECURITY_TYPES, securityTypesJson); 1261 } 1262 } catch (JSONException e) { 1263 Log.e(TAG, "JSONException while converting ScanResultKey to string: " + e); 1264 } 1265 return keyJson.toString(); 1266 } 1267 getSsid()1268 @Nullable String getSsid() { 1269 return mSsid; 1270 } 1271 getSecurityTypes()1272 @NonNull Set<Integer> getSecurityTypes() { 1273 return mSecurityTypes; 1274 } 1275 1276 @Override equals(Object o)1277 public boolean equals(Object o) { 1278 if (this == o) return true; 1279 if (o == null || getClass() != o.getClass()) return false; 1280 ScanResultKey that = (ScanResultKey) o; 1281 return TextUtils.equals(mSsid, that.mSsid) 1282 && mSecurityTypes.equals(that.mSecurityTypes); 1283 } 1284 1285 @Override hashCode()1286 public int hashCode() { 1287 return Objects.hash(mSsid, mSecurityTypes); 1288 } 1289 } 1290 } 1291