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