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