1 /* 2 * Copyright (C) 2018 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 android.net.wifi; 18 19 import static com.android.internal.util.Preconditions.checkNotNull; 20 21 import android.annotation.IntRange; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SystemApi; 26 import android.net.MacAddress; 27 import android.net.wifi.hotspot2.PasspointConfiguration; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.telephony.TelephonyManager; 31 import android.text.TextUtils; 32 33 import java.nio.charset.CharsetEncoder; 34 import java.nio.charset.StandardCharsets; 35 import java.util.List; 36 import java.util.Objects; 37 38 /** 39 * The Network Suggestion object is used to provide a Wi-Fi network for consideration when 40 * auto-connecting to networks. Apps cannot directly create this object, they must use 41 * {@link WifiNetworkSuggestion.Builder#build()} to obtain an instance of this object. 42 *<p> 43 * Apps can provide a list of such networks to the platform using 44 * {@link WifiManager#addNetworkSuggestions(List)}. 45 */ 46 public final class WifiNetworkSuggestion implements Parcelable { 47 /** 48 * Builder used to create {@link WifiNetworkSuggestion} objects. 49 */ 50 public static final class Builder { 51 private static final int UNASSIGNED_PRIORITY = -1; 52 53 /** 54 * SSID of the network. 55 */ 56 private String mSsid; 57 /** 58 * Optional BSSID within the network. 59 */ 60 private MacAddress mBssid; 61 /** 62 * Whether this is an OWE network or not. 63 */ 64 private boolean mIsEnhancedOpen; 65 /** 66 * Pre-shared key for use with WPA-PSK networks. 67 */ 68 private @Nullable String mWpa2PskPassphrase; 69 /** 70 * Pre-shared key for use with WPA3-SAE networks. 71 */ 72 private @Nullable String mWpa3SaePassphrase; 73 /** 74 * The enterprise configuration details specifying the EAP method, 75 * certificates and other settings associated with the WPA/WPA2-Enterprise networks. 76 */ 77 private @Nullable WifiEnterpriseConfig mWpa2EnterpriseConfig; 78 /** 79 * The enterprise configuration details specifying the EAP method, 80 * certificates and other settings associated with the WPA3-Enterprise networks. 81 */ 82 private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig; 83 /** 84 * The passpoint config for use with Hotspot 2.0 network 85 */ 86 private @Nullable PasspointConfiguration mPasspointConfiguration; 87 /** 88 * This is a network that does not broadcast its SSID, so an 89 * SSID-specific probe request must be used for scans. 90 */ 91 private boolean mIsHiddenSSID; 92 /** 93 * Whether app needs to log in to captive portal to obtain Internet access. 94 */ 95 private boolean mIsAppInteractionRequired; 96 /** 97 * Whether user needs to log in to captive portal to obtain Internet access. 98 */ 99 private boolean mIsUserInteractionRequired; 100 /** 101 * Whether this network is metered or not. 102 */ 103 private int mMeteredOverride; 104 /** 105 * Priority of this network among other network suggestions provided by the app. 106 * The lower the number, the higher the priority (i.e value of 0 = highest priority). 107 */ 108 private int mPriority; 109 110 /** 111 * The carrier ID identifies the operator who provides this network configuration. 112 * see {@link TelephonyManager#getSimCarrierId()} 113 */ 114 private int mCarrierId; 115 116 /** 117 * Whether this network is shared credential with user to allow user manually connect. 118 */ 119 private boolean mIsSharedWithUser; 120 121 /** 122 * Whether the setCredentialSharedWithUser have been called. 123 */ 124 private boolean mIsSharedWithUserSet; 125 126 /** 127 * Whether this network is initialized with auto-join enabled (the default) or not. 128 */ 129 private boolean mIsInitialAutojoinEnabled; 130 131 /** 132 * Pre-shared key for use with WAPI-PSK networks. 133 */ 134 private @Nullable String mWapiPskPassphrase; 135 136 /** 137 * The enterprise configuration details specifying the EAP method, 138 * certificates and other settings associated with the WAPI networks. 139 */ 140 private @Nullable WifiEnterpriseConfig mWapiEnterpriseConfig; 141 142 /** 143 * Whether this network will be brought up as untrusted (TRUSTED capability bit removed). 144 */ 145 private boolean mIsNetworkUntrusted; 146 Builder()147 public Builder() { 148 mSsid = null; 149 mBssid = null; 150 mIsEnhancedOpen = false; 151 mWpa2PskPassphrase = null; 152 mWpa3SaePassphrase = null; 153 mWpa2EnterpriseConfig = null; 154 mWpa3EnterpriseConfig = null; 155 mPasspointConfiguration = null; 156 mIsHiddenSSID = false; 157 mIsAppInteractionRequired = false; 158 mIsUserInteractionRequired = false; 159 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NONE; 160 mIsSharedWithUser = true; 161 mIsSharedWithUserSet = false; 162 mIsInitialAutojoinEnabled = true; 163 mPriority = UNASSIGNED_PRIORITY; 164 mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; 165 mWapiPskPassphrase = null; 166 mWapiEnterpriseConfig = null; 167 mIsNetworkUntrusted = false; 168 } 169 170 /** 171 * Set the unicode SSID for the network. 172 * <p> 173 * <li>Overrides any previous value set using {@link #setSsid(String)}.</li> 174 * 175 * @param ssid The SSID of the network. It must be valid Unicode. 176 * @return Instance of {@link Builder} to enable chaining of the builder method. 177 * @throws IllegalArgumentException if the SSID is not valid unicode. 178 */ setSsid(@onNull String ssid)179 public @NonNull Builder setSsid(@NonNull String ssid) { 180 checkNotNull(ssid); 181 final CharsetEncoder unicodeEncoder = StandardCharsets.UTF_8.newEncoder(); 182 if (!unicodeEncoder.canEncode(ssid)) { 183 throw new IllegalArgumentException("SSID is not a valid unicode string"); 184 } 185 mSsid = new String(ssid); 186 return this; 187 } 188 189 /** 190 * Set the BSSID to use for filtering networks from scan results. Will only match network 191 * whose BSSID is identical to the specified value. 192 * <p> 193 * <li Sets a specific BSSID for the network suggestion. If set, only the specified BSSID 194 * with the specified SSID will be considered for connection. 195 * <li>If set, only the specified BSSID with the specified SSID will be considered for 196 * connection.</li> 197 * <li>If not set, all BSSIDs with the specified SSID will be considered for connection. 198 * </li> 199 * <li>Overrides any previous value set using {@link #setBssid(MacAddress)}.</li> 200 * 201 * @param bssid BSSID of the network. 202 * @return Instance of {@link Builder} to enable chaining of the builder method. 203 */ setBssid(@onNull MacAddress bssid)204 public @NonNull Builder setBssid(@NonNull MacAddress bssid) { 205 checkNotNull(bssid); 206 mBssid = MacAddress.fromBytes(bssid.toByteArray()); 207 return this; 208 } 209 210 /** 211 * Specifies whether this represents an Enhanced Open (OWE) network. 212 * 213 * @param isEnhancedOpen {@code true} to indicate that the network used enhanced open, 214 * {@code false} otherwise. 215 * @return Instance of {@link Builder} to enable chaining of the builder method. 216 */ setIsEnhancedOpen(boolean isEnhancedOpen)217 public @NonNull Builder setIsEnhancedOpen(boolean isEnhancedOpen) { 218 mIsEnhancedOpen = isEnhancedOpen; 219 return this; 220 } 221 222 /** 223 * Set the ASCII WPA2 passphrase for this network. Needed for authenticating to 224 * WPA2-PSK networks. 225 * 226 * @param passphrase passphrase of the network. 227 * @return Instance of {@link Builder} to enable chaining of the builder method. 228 * @throws IllegalArgumentException if the passphrase is not ASCII encodable. 229 */ setWpa2Passphrase(@onNull String passphrase)230 public @NonNull Builder setWpa2Passphrase(@NonNull String passphrase) { 231 checkNotNull(passphrase); 232 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); 233 if (!asciiEncoder.canEncode(passphrase)) { 234 throw new IllegalArgumentException("passphrase not ASCII encodable"); 235 } 236 mWpa2PskPassphrase = passphrase; 237 return this; 238 } 239 240 /** 241 * Set the ASCII WPA3 passphrase for this network. Needed for authenticating to WPA3-SAE 242 * networks. 243 * 244 * @param passphrase passphrase of the network. 245 * @return Instance of {@link Builder} to enable chaining of the builder method. 246 * @throws IllegalArgumentException if the passphrase is not ASCII encodable. 247 */ setWpa3Passphrase(@onNull String passphrase)248 public @NonNull Builder setWpa3Passphrase(@NonNull String passphrase) { 249 checkNotNull(passphrase); 250 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); 251 if (!asciiEncoder.canEncode(passphrase)) { 252 throw new IllegalArgumentException("passphrase not ASCII encodable"); 253 } 254 mWpa3SaePassphrase = passphrase; 255 return this; 256 } 257 258 /** 259 * Set the associated enterprise configuration for this network. Needed for authenticating 260 * to WPA2 enterprise networks. See {@link WifiEnterpriseConfig} for description. 261 * 262 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 263 * @return Instance of {@link Builder} to enable chaining of the builder method. 264 * @throws IllegalArgumentException if configuration CA certificate or 265 * AltSubjectMatch/DomainSuffixMatch is not set. 266 */ setWpa2EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)267 public @NonNull Builder setWpa2EnterpriseConfig( 268 @NonNull WifiEnterpriseConfig enterpriseConfig) { 269 checkNotNull(enterpriseConfig); 270 if (enterpriseConfig.isInsecure()) { 271 throw new IllegalArgumentException("Enterprise configuration is insecure"); 272 } 273 mWpa2EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 274 return this; 275 } 276 277 /** 278 * Set the associated enterprise configuration for this network. Needed for authenticating 279 * to WPA3-Enterprise networks (standard and 192-bit security). See 280 * {@link WifiEnterpriseConfig} for description. For 192-bit security networks, both the 281 * client and CA certificates must be provided, and must be of type of either 282 * sha384WithRSAEncryption (OID 1.2.840.113549.1.1.12) or ecdsa-with-SHA384 283 * (OID 1.2.840.10045.4.3.3). 284 * 285 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 286 * @return Instance of {@link Builder} to enable chaining of the builder method. 287 * @throws IllegalArgumentException if configuration CA certificate or 288 * AltSubjectMatch/DomainSuffixMatch is not set. 289 */ setWpa3EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)290 public @NonNull Builder setWpa3EnterpriseConfig( 291 @NonNull WifiEnterpriseConfig enterpriseConfig) { 292 checkNotNull(enterpriseConfig); 293 if (enterpriseConfig.isInsecure()) { 294 throw new IllegalArgumentException("Enterprise configuration is insecure"); 295 } 296 mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 297 return this; 298 } 299 300 /** 301 * Set the associated Passpoint configuration for this network. Needed for authenticating 302 * to Hotspot 2.0 networks. See {@link PasspointConfiguration} for description. 303 * 304 * @param passpointConfig Instance of {@link PasspointConfiguration}. 305 * @return Instance of {@link Builder} to enable chaining of the builder method. 306 * @throws IllegalArgumentException if passpoint configuration is invalid. 307 */ setPasspointConfig( @onNull PasspointConfiguration passpointConfig)308 public @NonNull Builder setPasspointConfig( 309 @NonNull PasspointConfiguration passpointConfig) { 310 checkNotNull(passpointConfig); 311 if (!passpointConfig.validate()) { 312 throw new IllegalArgumentException("Passpoint configuration is invalid"); 313 } 314 mPasspointConfiguration = passpointConfig; 315 return this; 316 } 317 318 /** 319 * Set the carrier ID of the network operator. The carrier ID associates a Suggested 320 * network with a specific carrier (and therefore SIM). The carrier ID must be provided 321 * for any network which uses the SIM-based authentication: e.g. EAP-SIM, EAP-AKA, 322 * EAP-AKA', and EAP-PEAP with SIM-based phase 2 authentication. 323 * @param carrierId see {@link TelephonyManager#getSimCarrierId()}. 324 * @return Instance of {@link Builder} to enable chaining of the builder method. 325 * 326 * @hide 327 */ 328 @SystemApi 329 @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) setCarrierId(int carrierId)330 public @NonNull Builder setCarrierId(int carrierId) { 331 mCarrierId = carrierId; 332 return this; 333 } 334 335 /** 336 * Set the ASCII WAPI passphrase for this network. Needed for authenticating to 337 * WAPI-PSK networks. 338 * 339 * @param passphrase passphrase of the network. 340 * @return Instance of {@link Builder} to enable chaining of the builder method. 341 * @throws IllegalArgumentException if the passphrase is not ASCII encodable. 342 * 343 */ setWapiPassphrase(@onNull String passphrase)344 public @NonNull Builder setWapiPassphrase(@NonNull String passphrase) { 345 checkNotNull(passphrase); 346 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); 347 if (!asciiEncoder.canEncode(passphrase)) { 348 throw new IllegalArgumentException("passphrase not ASCII encodable"); 349 } 350 mWapiPskPassphrase = passphrase; 351 return this; 352 } 353 354 /** 355 * Set the associated enterprise configuration for this network. Needed for authenticating 356 * to WAPI-CERT networks. See {@link WifiEnterpriseConfig} for description. 357 * 358 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 359 * @return Instance of {@link Builder} to enable chaining of the builder method. 360 */ setWapiEnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)361 public @NonNull Builder setWapiEnterpriseConfig( 362 @NonNull WifiEnterpriseConfig enterpriseConfig) { 363 checkNotNull(enterpriseConfig); 364 mWapiEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 365 return this; 366 } 367 368 /** 369 * Specifies whether this represents a hidden network. 370 * <p> 371 * <li>If not set, defaults to false (i.e not a hidden network).</li> 372 * 373 * @param isHiddenSsid {@code true} to indicate that the network is hidden, {@code false} 374 * otherwise. 375 * @return Instance of {@link Builder} to enable chaining of the builder method. 376 */ setIsHiddenSsid(boolean isHiddenSsid)377 public @NonNull Builder setIsHiddenSsid(boolean isHiddenSsid) { 378 mIsHiddenSSID = isHiddenSsid; 379 return this; 380 } 381 382 /** 383 * Specifies whether the app needs to log in to a captive portal to obtain Internet access. 384 * <p> 385 * This will dictate if the directed broadcast 386 * {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} will be sent to the 387 * app after successfully connecting to the network. 388 * Use this for captive portal type networks where the app needs to authenticate the user 389 * before the device can access the network. 390 * <p> 391 * <li>If not set, defaults to false (i.e no app interaction required).</li> 392 * 393 * @param isAppInteractionRequired {@code true} to indicate that app interaction is 394 * required, {@code false} otherwise. 395 * @return Instance of {@link Builder} to enable chaining of the builder method. 396 */ setIsAppInteractionRequired(boolean isAppInteractionRequired)397 public @NonNull Builder setIsAppInteractionRequired(boolean isAppInteractionRequired) { 398 mIsAppInteractionRequired = isAppInteractionRequired; 399 return this; 400 } 401 402 /** 403 * Specifies whether the user needs to log in to a captive portal to obtain Internet access. 404 * <p> 405 * <li>If not set, defaults to false (i.e no user interaction required).</li> 406 * 407 * @param isUserInteractionRequired {@code true} to indicate that user interaction is 408 * required, {@code false} otherwise. 409 * @return Instance of {@link Builder} to enable chaining of the builder method. 410 */ setIsUserInteractionRequired(boolean isUserInteractionRequired)411 public @NonNull Builder setIsUserInteractionRequired(boolean isUserInteractionRequired) { 412 mIsUserInteractionRequired = isUserInteractionRequired; 413 return this; 414 } 415 416 /** 417 * Specify the priority of this network among other network suggestions provided by the same 418 * app (priorities have no impact on suggestions by different apps). The higher the number, 419 * the higher the priority (i.e value of 0 = lowest priority). 420 * <p> 421 * <li>If not set, defaults a lower priority than any assigned priority.</li> 422 * 423 * @param priority Integer number representing the priority among suggestions by the app. 424 * @return Instance of {@link Builder} to enable chaining of the builder method. 425 * @throws IllegalArgumentException if the priority value is negative. 426 */ setPriority(@ntRangefrom = 0) int priority)427 public @NonNull Builder setPriority(@IntRange(from = 0) int priority) { 428 if (priority < 0) { 429 throw new IllegalArgumentException("Invalid priority value " + priority); 430 } 431 mPriority = priority; 432 return this; 433 } 434 435 /** 436 * Specifies whether this network is metered. 437 * <p> 438 * <li>If not set, defaults to detect automatically.</li> 439 * 440 * @param isMetered {@code true} to indicate that the network is metered, {@code false} 441 * for not metered. 442 * @return Instance of {@link Builder} to enable chaining of the builder method. 443 */ setIsMetered(boolean isMetered)444 public @NonNull Builder setIsMetered(boolean isMetered) { 445 if (isMetered) { 446 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED; 447 } else { 448 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED; 449 } 450 return this; 451 } 452 453 /** 454 * Specifies whether the network credentials provided with this suggestion can be used by 455 * the user to explicitly (manually) connect to this network. If true this network will 456 * appear in the Wi-Fi Picker (in Settings) and the user will be able to select and connect 457 * to it with the provided credentials. If false, the user will need to enter network 458 * credentials and the resulting configuration will become a user saved network. 459 * <p> 460 * <li>Note: Only valid for secure (non-open) networks. 461 * <li>If not set, defaults to true (i.e. allow user to manually connect) for secure 462 * networks and false for open networks.</li> 463 * 464 * @param isShared {@code true} to indicate that the credentials may be used by the user to 465 * manually connect to the network, {@code false} otherwise. 466 * @return Instance of {@link Builder} to enable chaining of the builder method. 467 */ setCredentialSharedWithUser(boolean isShared)468 public @NonNull Builder setCredentialSharedWithUser(boolean isShared) { 469 mIsSharedWithUser = isShared; 470 mIsSharedWithUserSet = true; 471 return this; 472 } 473 474 /** 475 * Specifies whether the suggestion is created with auto-join enabled or disabled. The 476 * user may modify the auto-join configuration of a suggestion directly once the device 477 * associates to the network. 478 * <p> 479 * If auto-join is initialized as disabled the user may still be able to manually connect 480 * to the network. Therefore, disabling auto-join only makes sense if 481 * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which 482 * itself implies a secure (non-open) network. 483 * <p> 484 * If not set, defaults to true (i.e. auto-join is initialized as enabled). 485 * 486 * @param enabled true for initializing with auto-join enabled (the default), false to 487 * initializing with auto-join disabled. 488 * @return Instance of {@link Builder} to enable chaining of the builder method. 489 */ setIsInitialAutojoinEnabled(boolean enabled)490 public @NonNull Builder setIsInitialAutojoinEnabled(boolean enabled) { 491 mIsInitialAutojoinEnabled = enabled; 492 return this; 493 } 494 495 /** 496 * Specifies whether the system will bring up the network (if selected) as untrusted. An 497 * untrusted network has its {@link android.net.NetworkCapabilities#NET_CAPABILITY_TRUSTED} 498 * capability removed. The Wi-Fi network selection process may use this information to 499 * influence priority of the suggested network for Wi-Fi network selection (most likely to 500 * reduce it). The connectivity service may use this information to influence the overall 501 * network configuration of the device. 502 * <p> 503 * <li> An untrusted network's credentials may not be shared with the user using 504 * {@link #setCredentialSharedWithUser(boolean)}.</li> 505 * <li> If not set, defaults to false (i.e. network is trusted).</li> 506 * 507 * @param isUntrusted Boolean indicating whether the network should be brought up untrusted 508 * (if true) or trusted (if false). 509 * @return Instance of {@link Builder} to enable chaining of the builder method. 510 */ setUntrusted(boolean isUntrusted)511 public @NonNull Builder setUntrusted(boolean isUntrusted) { 512 mIsNetworkUntrusted = isUntrusted; 513 return this; 514 } 515 setSecurityParamsInWifiConfiguration( @onNull WifiConfiguration configuration)516 private void setSecurityParamsInWifiConfiguration( 517 @NonNull WifiConfiguration configuration) { 518 if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network. 519 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK); 520 // WifiConfiguration.preSharedKey needs quotes around ASCII password. 521 configuration.preSharedKey = "\"" + mWpa2PskPassphrase + "\""; 522 } else if (!TextUtils.isEmpty(mWpa3SaePassphrase)) { // WPA3-SAE network. 523 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE); 524 // WifiConfiguration.preSharedKey needs quotes around ASCII password. 525 configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\""; 526 } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network 527 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP); 528 configuration.enterpriseConfig = mWpa2EnterpriseConfig; 529 } else if (mWpa3EnterpriseConfig != null) { // WPA3-Enterprise 530 if (mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS 531 && WifiEnterpriseConfig.isSuiteBCipherCert( 532 mWpa3EnterpriseConfig.getClientCertificate()) 533 && WifiEnterpriseConfig.isSuiteBCipherCert( 534 mWpa3EnterpriseConfig.getCaCertificate())) { 535 // WPA3-Enterprise in 192-bit security mode (Suite-B) 536 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B); 537 } else { 538 // WPA3-Enterprise 539 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP); 540 configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN); 541 configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); 542 configuration.allowedPairwiseCiphers.set( 543 WifiConfiguration.PairwiseCipher.GCMP_256); 544 configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); 545 configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256); 546 configuration.requirePmf = true; 547 } 548 configuration.enterpriseConfig = mWpa3EnterpriseConfig; 549 } else if (mIsEnhancedOpen) { // OWE network 550 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE); 551 } else if (!TextUtils.isEmpty(mWapiPskPassphrase)) { // WAPI-PSK network. 552 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_PSK); 553 // WifiConfiguration.preSharedKey needs quotes around ASCII password. 554 configuration.preSharedKey = "\"" + mWapiPskPassphrase + "\""; 555 } else if (mWapiEnterpriseConfig != null) { // WAPI-CERT network 556 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT); 557 configuration.enterpriseConfig = mWapiEnterpriseConfig; 558 } else { // Open network 559 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); 560 } 561 } 562 563 /** 564 * Helper method to build WifiConfiguration object from the builder. 565 * @return Instance of {@link WifiConfiguration}. 566 */ buildWifiConfiguration()567 private WifiConfiguration buildWifiConfiguration() { 568 final WifiConfiguration wifiConfiguration = new WifiConfiguration(); 569 // WifiConfiguration.SSID needs quotes around unicode SSID. 570 wifiConfiguration.SSID = "\"" + mSsid + "\""; 571 if (mBssid != null) { 572 wifiConfiguration.BSSID = mBssid.toString(); 573 } 574 575 setSecurityParamsInWifiConfiguration(wifiConfiguration); 576 577 wifiConfiguration.hiddenSSID = mIsHiddenSSID; 578 wifiConfiguration.priority = mPriority; 579 wifiConfiguration.meteredOverride = mMeteredOverride; 580 wifiConfiguration.carrierId = mCarrierId; 581 wifiConfiguration.trusted = !mIsNetworkUntrusted; 582 return wifiConfiguration; 583 } 584 validateSecurityParams()585 private void validateSecurityParams() { 586 int numSecurityTypes = 0; 587 numSecurityTypes += mIsEnhancedOpen ? 1 : 0; 588 numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0; 589 numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0; 590 numSecurityTypes += !TextUtils.isEmpty(mWapiPskPassphrase) ? 1 : 0; 591 numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0; 592 numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0; 593 numSecurityTypes += mWapiEnterpriseConfig != null ? 1 : 0; 594 numSecurityTypes += mPasspointConfiguration != null ? 1 : 0; 595 if (numSecurityTypes > 1) { 596 throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase," 597 + " setWpa3Passphrase, setWpa2EnterpriseConfig, setWpa3EnterpriseConfig" 598 + " setWapiPassphrase, setWapiCertSuite, setIsWapiCertSuiteAuto" 599 + " or setPasspointConfig can be invoked for network suggestion"); 600 } 601 } 602 buildWifiConfigurationForPasspoint()603 private WifiConfiguration buildWifiConfigurationForPasspoint() { 604 WifiConfiguration wifiConfiguration = new WifiConfiguration(); 605 wifiConfiguration.FQDN = mPasspointConfiguration.getHomeSp().getFqdn(); 606 wifiConfiguration.setPasspointUniqueId(mPasspointConfiguration.getUniqueId()); 607 wifiConfiguration.priority = mPriority; 608 wifiConfiguration.meteredOverride = mMeteredOverride; 609 wifiConfiguration.trusted = !mIsNetworkUntrusted; 610 mPasspointConfiguration.setCarrierId(mCarrierId); 611 mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride); 612 return wifiConfiguration; 613 } 614 615 /** 616 * Create a network suggestion object for use in 617 * {@link WifiManager#addNetworkSuggestions(List)}. 618 * 619 *<p class="note"> 620 * <b>Note:</b> Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID 621 * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to 622 * the platform. 623 * </p> 624 * 625 * For example: 626 * To provide credentials for one open, one WPA2, one WPA3 network with their 627 * corresponding SSID's and one with Passpoint config: 628 * 629 * <pre>{@code 630 * final WifiNetworkSuggestion suggestion1 = 631 * new Builder() 632 * .setSsid("test111111") 633 * .build(); 634 * final WifiNetworkSuggestion suggestion2 = 635 * new Builder() 636 * .setSsid("test222222") 637 * .setWpa2Passphrase("test123456") 638 * .build(); 639 * final WifiNetworkSuggestion suggestion3 = 640 * new Builder() 641 * .setSsid("test333333") 642 * .setWpa3Passphrase("test6789") 643 * .build(); 644 * final PasspointConfiguration passpointConfig= new PasspointConfiguration(); 645 * // configure passpointConfig to include a valid Passpoint configuration 646 * final WifiNetworkSuggestion suggestion4 = 647 * new Builder() 648 * .setPasspointConfig(passpointConfig) 649 * .build(); 650 * final List<WifiNetworkSuggestion> suggestionsList = 651 * new ArrayList<WifiNetworkSuggestion> { { 652 * add(suggestion1); 653 * add(suggestion2); 654 * add(suggestion3); 655 * add(suggestion4); 656 * } }; 657 * final WifiManager wifiManager = 658 * context.getSystemService(Context.WIFI_SERVICE); 659 * wifiManager.addNetworkSuggestions(suggestionsList); 660 * // ... 661 * }</pre> 662 * 663 * @return Instance of {@link WifiNetworkSuggestion} 664 * @throws IllegalStateException on invalid params set 665 * @see WifiNetworkSuggestion 666 */ build()667 public @NonNull WifiNetworkSuggestion build() { 668 validateSecurityParams(); 669 WifiConfiguration wifiConfiguration; 670 if (mPasspointConfiguration != null) { 671 if (mSsid != null) { 672 throw new IllegalStateException("setSsid should not be invoked for suggestion " 673 + "with Passpoint configuration"); 674 } 675 if (mIsHiddenSSID) { 676 throw new IllegalStateException("setIsHiddenSsid should not be invoked for " 677 + "suggestion with Passpoint configuration"); 678 } 679 wifiConfiguration = buildWifiConfigurationForPasspoint(); 680 } else { 681 if (mSsid == null) { 682 throw new IllegalStateException("setSsid should be invoked for suggestion"); 683 } 684 if (TextUtils.isEmpty(mSsid)) { 685 throw new IllegalStateException("invalid ssid for suggestion"); 686 } 687 if (mBssid != null 688 && (mBssid.equals(MacAddress.BROADCAST_ADDRESS) 689 || mBssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS))) { 690 throw new IllegalStateException("invalid bssid for suggestion"); 691 } 692 wifiConfiguration = buildWifiConfiguration(); 693 if (wifiConfiguration.isOpenNetwork()) { 694 if (mIsSharedWithUserSet && mIsSharedWithUser) { 695 throw new IllegalStateException("Open network should not be " 696 + "setCredentialSharedWithUser to true"); 697 } 698 mIsSharedWithUser = false; 699 } 700 } 701 if (!mIsSharedWithUser && !mIsInitialAutojoinEnabled) { 702 throw new IllegalStateException("Should have not a network with both " 703 + "setCredentialSharedWithUser and " 704 + "setIsAutojoinEnabled set to false"); 705 } 706 if (mIsNetworkUntrusted) { 707 if (mIsSharedWithUserSet && mIsSharedWithUser) { 708 throw new IllegalStateException("Should not be both" 709 + "setCredentialSharedWithUser and +" 710 + "setIsNetworkAsUntrusted to true"); 711 } 712 mIsSharedWithUser = false; 713 } 714 return new WifiNetworkSuggestion( 715 wifiConfiguration, 716 mPasspointConfiguration, 717 mIsAppInteractionRequired, 718 mIsUserInteractionRequired, 719 mIsSharedWithUser, 720 mIsInitialAutojoinEnabled); 721 } 722 } 723 724 /** 725 * Network configuration for the provided network. 726 * @hide 727 */ 728 @NonNull 729 public final WifiConfiguration wifiConfiguration; 730 731 /** 732 * Passpoint configuration for the provided network. 733 * @hide 734 */ 735 @Nullable 736 public final PasspointConfiguration passpointConfiguration; 737 738 /** 739 * Whether app needs to log in to captive portal to obtain Internet access. 740 * @hide 741 */ 742 public final boolean isAppInteractionRequired; 743 744 /** 745 * Whether user needs to log in to captive portal to obtain Internet access. 746 * @hide 747 */ 748 public final boolean isUserInteractionRequired; 749 750 /** 751 * Whether app share credential with the user, allow user use provided credential to 752 * connect network manually. 753 * @hide 754 */ 755 public final boolean isUserAllowedToManuallyConnect; 756 757 /** 758 * Whether the suggestion will be initialized as auto-joined or not. 759 * @hide 760 */ 761 public final boolean isInitialAutoJoinEnabled; 762 763 /** @hide */ WifiNetworkSuggestion()764 public WifiNetworkSuggestion() { 765 this.wifiConfiguration = new WifiConfiguration(); 766 this.passpointConfiguration = null; 767 this.isAppInteractionRequired = false; 768 this.isUserInteractionRequired = false; 769 this.isUserAllowedToManuallyConnect = true; 770 this.isInitialAutoJoinEnabled = true; 771 } 772 773 /** @hide */ WifiNetworkSuggestion(@onNull WifiConfiguration networkConfiguration, @Nullable PasspointConfiguration passpointConfiguration, boolean isAppInteractionRequired, boolean isUserInteractionRequired, boolean isUserAllowedToManuallyConnect, boolean isInitialAutoJoinEnabled)774 public WifiNetworkSuggestion(@NonNull WifiConfiguration networkConfiguration, 775 @Nullable PasspointConfiguration passpointConfiguration, 776 boolean isAppInteractionRequired, 777 boolean isUserInteractionRequired, 778 boolean isUserAllowedToManuallyConnect, 779 boolean isInitialAutoJoinEnabled) { 780 checkNotNull(networkConfiguration); 781 this.wifiConfiguration = networkConfiguration; 782 this.passpointConfiguration = passpointConfiguration; 783 784 this.isAppInteractionRequired = isAppInteractionRequired; 785 this.isUserInteractionRequired = isUserInteractionRequired; 786 this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect; 787 this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled; 788 } 789 790 public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR = 791 new Creator<WifiNetworkSuggestion>() { 792 @Override 793 public WifiNetworkSuggestion createFromParcel(Parcel in) { 794 return new WifiNetworkSuggestion( 795 in.readParcelable(null), // wifiConfiguration 796 in.readParcelable(null), // PasspointConfiguration 797 in.readBoolean(), // isAppInteractionRequired 798 in.readBoolean(), // isUserInteractionRequired 799 in.readBoolean(), // isSharedCredentialWithUser 800 in.readBoolean() // isAutojoinEnabled 801 ); 802 } 803 804 @Override 805 public WifiNetworkSuggestion[] newArray(int size) { 806 return new WifiNetworkSuggestion[size]; 807 } 808 }; 809 810 @Override describeContents()811 public int describeContents() { 812 return 0; 813 } 814 815 @Override writeToParcel(Parcel dest, int flags)816 public void writeToParcel(Parcel dest, int flags) { 817 dest.writeParcelable(wifiConfiguration, flags); 818 dest.writeParcelable(passpointConfiguration, flags); 819 dest.writeBoolean(isAppInteractionRequired); 820 dest.writeBoolean(isUserInteractionRequired); 821 dest.writeBoolean(isUserAllowedToManuallyConnect); 822 dest.writeBoolean(isInitialAutoJoinEnabled); 823 } 824 825 @Override hashCode()826 public int hashCode() { 827 return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID, 828 wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey()); 829 } 830 831 /** 832 * Equals for network suggestions. 833 */ 834 @Override equals(Object obj)835 public boolean equals(Object obj) { 836 if (this == obj) { 837 return true; 838 } 839 if (!(obj instanceof WifiNetworkSuggestion)) { 840 return false; 841 } 842 WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj; 843 if (this.passpointConfiguration == null ^ lhs.passpointConfiguration == null) { 844 return false; 845 } 846 847 return TextUtils.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID) 848 && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID) 849 && Objects.equals(this.wifiConfiguration.allowedKeyManagement, 850 lhs.wifiConfiguration.allowedKeyManagement) 851 && TextUtils.equals(this.wifiConfiguration.getKey(), 852 lhs.wifiConfiguration.getKey()); 853 } 854 855 @Override toString()856 public String toString() { 857 StringBuilder sb = new StringBuilder("WifiNetworkSuggestion[ ") 858 .append("SSID=").append(wifiConfiguration.SSID) 859 .append(", BSSID=").append(wifiConfiguration.BSSID) 860 .append(", FQDN=").append(wifiConfiguration.FQDN) 861 .append(", isAppInteractionRequired=").append(isAppInteractionRequired) 862 .append(", isUserInteractionRequired=").append(isUserInteractionRequired) 863 .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect) 864 .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled) 865 .append(", isUnTrusted=").append(!wifiConfiguration.trusted) 866 .append(" ]"); 867 return sb.toString(); 868 } 869 870 /** 871 * Get the {@link WifiConfiguration} associated with this Suggestion. 872 * @hide 873 */ 874 @SystemApi 875 @NonNull getWifiConfiguration()876 public WifiConfiguration getWifiConfiguration() { 877 return wifiConfiguration; 878 } 879 880 /** 881 * Get the BSSID, or null if unset. 882 * @see Builder#setBssid(MacAddress) 883 */ 884 @Nullable getBssid()885 public MacAddress getBssid() { 886 if (wifiConfiguration.BSSID == null) { 887 return null; 888 } 889 return MacAddress.fromString(wifiConfiguration.BSSID); 890 } 891 892 /** @see Builder#setCredentialSharedWithUser(boolean) */ isCredentialSharedWithUser()893 public boolean isCredentialSharedWithUser() { 894 return isUserAllowedToManuallyConnect; 895 } 896 897 /** @see Builder#setIsAppInteractionRequired(boolean) */ isAppInteractionRequired()898 public boolean isAppInteractionRequired() { 899 return isAppInteractionRequired; 900 } 901 902 /** @see Builder#setIsEnhancedOpen(boolean) */ isEnhancedOpen()903 public boolean isEnhancedOpen() { 904 return wifiConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE); 905 } 906 907 /** @see Builder#setIsHiddenSsid(boolean) */ isHiddenSsid()908 public boolean isHiddenSsid() { 909 return wifiConfiguration.hiddenSSID; 910 } 911 912 /** @see Builder#setIsInitialAutojoinEnabled(boolean) */ isInitialAutojoinEnabled()913 public boolean isInitialAutojoinEnabled() { 914 return isInitialAutoJoinEnabled; 915 } 916 917 /** @see Builder#setIsMetered(boolean) */ isMetered()918 public boolean isMetered() { 919 return wifiConfiguration.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED; 920 } 921 922 /** @see Builder#setIsUserInteractionRequired(boolean) */ isUserInteractionRequired()923 public boolean isUserInteractionRequired() { 924 return isUserInteractionRequired; 925 } 926 927 /** 928 * Get the {@link PasspointConfiguration} associated with this Suggestion, or null if this 929 * Suggestion is not for a Passpoint network. 930 */ 931 @Nullable getPasspointConfig()932 public PasspointConfiguration getPasspointConfig() { 933 return passpointConfiguration; 934 } 935 936 /** @see Builder#setPriority(int) */ 937 @IntRange(from = 0) getPriority()938 public int getPriority() { 939 return wifiConfiguration.priority; 940 } 941 942 /** 943 * Return the SSID of the network, or null if this is a Passpoint network. 944 * @see Builder#setSsid(String) 945 */ 946 @Nullable getSsid()947 public String getSsid() { 948 if (wifiConfiguration.SSID == null) { 949 return null; 950 } 951 return WifiInfo.sanitizeSsid(wifiConfiguration.SSID); 952 } 953 954 /** @see Builder#setUntrusted(boolean) */ isUntrusted()955 public boolean isUntrusted() { 956 return !wifiConfiguration.trusted; 957 } 958 959 /** 960 * Get the WifiEnterpriseConfig, or null if unset. 961 * @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig) 962 * @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig) 963 * @see Builder#setWpa3EnterpriseConfig(WifiEnterpriseConfig) 964 */ 965 @Nullable getEnterpriseConfig()966 public WifiEnterpriseConfig getEnterpriseConfig() { 967 if (!wifiConfiguration.isEnterprise()) { 968 return null; 969 } 970 return wifiConfiguration.enterpriseConfig; 971 } 972 973 /** 974 * Get the passphrase, or null if unset. 975 * @see Builder#setWapiPassphrase(String) 976 * @see Builder#setWpa2Passphrase(String) 977 * @see Builder#setWpa3Passphrase(String) 978 */ 979 @Nullable getPassphrase()980 public String getPassphrase() { 981 if (wifiConfiguration.preSharedKey == null) { 982 return null; 983 } 984 return WifiInfo.removeDoubleQuotes(wifiConfiguration.preSharedKey); 985 } 986 } 987