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.IntDef; 22 import android.annotation.IntRange; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.RequiresPermission; 26 import android.annotation.SystemApi; 27 import android.net.MacAddress; 28 import android.net.NetworkCapabilities; 29 import android.net.NetworkRequest; 30 import android.net.wifi.hotspot2.PasspointConfiguration; 31 import android.os.Build; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 import android.telephony.SubscriptionInfo; 35 import android.telephony.SubscriptionManager; 36 import android.telephony.TelephonyManager; 37 import android.text.TextUtils; 38 39 import androidx.annotation.RequiresApi; 40 41 import com.android.modules.utils.build.SdkLevel; 42 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 import java.nio.charset.CharsetEncoder; 46 import java.nio.charset.StandardCharsets; 47 import java.util.List; 48 import java.util.Objects; 49 50 /** 51 * The Network Suggestion object is used to provide a Wi-Fi network for consideration when 52 * auto-connecting to networks. Apps cannot directly create this object, they must use 53 * {@link WifiNetworkSuggestion.Builder#build()} to obtain an instance of this object. 54 *<p> 55 * Apps can provide a list of such networks to the platform using 56 * {@link WifiManager#addNetworkSuggestions(List)}. 57 */ 58 public final class WifiNetworkSuggestion implements Parcelable { 59 /** @hide */ 60 @Retention(RetentionPolicy.SOURCE) 61 @IntDef(prefix = {"RANDOMIZATION_"}, value = { 62 RANDOMIZATION_PERSISTENT, 63 RANDOMIZATION_NON_PERSISTENT}) 64 public @interface MacRandomizationSetting {} 65 /** 66 * Generate a randomized MAC from a secret seed and information from the Wi-Fi configuration 67 * (SSID or Passpoint profile) and reuse it for all connections to this network. The 68 * randomized MAC address for this network will stay the same for each subsequent association 69 * until the device undergoes factory reset. 70 */ 71 public static final int RANDOMIZATION_PERSISTENT = 0; 72 /** 73 * With this option, the randomized MAC address will periodically get re-randomized, and 74 * the randomized MAC address will change if the suggestion is removed and then added back. 75 */ 76 public static final int RANDOMIZATION_NON_PERSISTENT = 1; 77 /** 78 * Builder used to create {@link WifiNetworkSuggestion} objects. 79 */ 80 public static final class Builder { 81 private static final int UNASSIGNED_PRIORITY = -1; 82 83 /** 84 * Set WPA Enterprise type according to certificate security level. 85 * This is for backward compatibility in R. 86 */ 87 private static final int WPA3_ENTERPRISE_AUTO = 0; 88 /** Set WPA Enterprise type to standard mode only. */ 89 private static final int WPA3_ENTERPRISE_STANDARD = 1; 90 /** Set WPA Enterprise type to 192 bit mode only. */ 91 private static final int WPA3_ENTERPRISE_192_BIT = 2; 92 93 /** 94 * SSID of the network. 95 */ 96 private String mSsid; 97 /** 98 * Optional BSSID within the network. 99 */ 100 private MacAddress mBssid; 101 /** 102 * Whether this is an OWE network or not. 103 */ 104 private boolean mIsEnhancedOpen; 105 /** 106 * Pre-shared key for use with WPA-PSK networks. 107 */ 108 private @Nullable String mWpa2PskPassphrase; 109 /** 110 * Pre-shared key for use with WPA3-SAE networks. 111 */ 112 private @Nullable String mWpa3SaePassphrase; 113 /** 114 * The enterprise configuration details specifying the EAP method, 115 * certificates and other settings associated with the WPA/WPA2-Enterprise networks. 116 */ 117 private @Nullable WifiEnterpriseConfig mWpa2EnterpriseConfig; 118 /** 119 * The enterprise configuration details specifying the EAP method, 120 * certificates and other settings associated with the WPA3-Enterprise networks. 121 */ 122 private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig; 123 /** 124 * Indicate what type this WPA3-Enterprise network is. 125 */ 126 private int mWpa3EnterpriseType = WPA3_ENTERPRISE_AUTO; 127 /** 128 * The passpoint config for use with Hotspot 2.0 network 129 */ 130 private @Nullable PasspointConfiguration mPasspointConfiguration; 131 /** 132 * This is a network that does not broadcast its SSID, so an 133 * SSID-specific probe request must be used for scans. 134 */ 135 private boolean mIsHiddenSSID; 136 /** 137 * Whether app needs to log in to captive portal to obtain Internet access. 138 */ 139 private boolean mIsAppInteractionRequired; 140 /** 141 * Whether user needs to log in to captive portal to obtain Internet access. 142 */ 143 private boolean mIsUserInteractionRequired; 144 /** 145 * Whether this network is metered or not. 146 */ 147 private int mMeteredOverride; 148 /** 149 * Priority of this network among other network suggestions from same priority group 150 * provided by the app. 151 * The higher the number, the higher the priority (i.e value of 0 = lowest priority). 152 */ 153 private int mPriority; 154 /** 155 * Priority group ID, while suggestion priority will only effect inside the priority group. 156 */ 157 private int mPriorityGroup; 158 159 /** 160 * The carrier ID identifies the operator who provides this network configuration. 161 * see {@link TelephonyManager#getSimCarrierId()} 162 */ 163 private int mCarrierId; 164 165 /** 166 * The Subscription ID identifies the SIM card for which this network configuration is 167 * valid. 168 */ 169 private int mSubscriptionId; 170 171 /** 172 * Whether this network is shared credential with user to allow user manually connect. 173 */ 174 private boolean mIsSharedWithUser; 175 176 /** 177 * Whether the setCredentialSharedWithUser have been called. 178 */ 179 private boolean mIsSharedWithUserSet; 180 181 /** 182 * Whether this network is initialized with auto-join enabled (the default) or not. 183 */ 184 private boolean mIsInitialAutojoinEnabled; 185 186 /** 187 * Pre-shared key for use with WAPI-PSK networks. 188 */ 189 private @Nullable String mWapiPskPassphrase; 190 191 /** 192 * The enterprise configuration details specifying the EAP method, 193 * certificates and other settings associated with the WAPI networks. 194 */ 195 private @Nullable WifiEnterpriseConfig mWapiEnterpriseConfig; 196 197 /** 198 * Whether this network will be brought up as untrusted (TRUSTED capability bit removed). 199 */ 200 private boolean mIsNetworkUntrusted; 201 202 /** 203 * Whether this network will be brought up as OEM paid (OEM_PAID capability bit added). 204 */ 205 private boolean mIsNetworkOemPaid; 206 207 /** 208 * Whether this network will be brought up as OEM private (OEM_PRIVATE capability bit 209 * added). 210 */ 211 private boolean mIsNetworkOemPrivate; 212 213 /** 214 * Whether this network is a carrier merged network. 215 */ 216 private boolean mIsCarrierMerged; 217 218 /** 219 * The MAC randomization strategy. 220 */ 221 @MacRandomizationSetting 222 private int mMacRandomizationSetting; 223 224 /** 225 * The SAE Hash-to-Element only mode. 226 */ 227 private boolean mSaeH2eOnlyMode; 228 Builder()229 public Builder() { 230 mSsid = null; 231 mBssid = null; 232 mIsEnhancedOpen = false; 233 mWpa2PskPassphrase = null; 234 mWpa3SaePassphrase = null; 235 mWpa2EnterpriseConfig = null; 236 mWpa3EnterpriseConfig = null; 237 mPasspointConfiguration = null; 238 mIsHiddenSSID = false; 239 mIsAppInteractionRequired = false; 240 mIsUserInteractionRequired = false; 241 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NONE; 242 mIsSharedWithUser = true; 243 mIsSharedWithUserSet = false; 244 mIsInitialAutojoinEnabled = true; 245 mPriority = UNASSIGNED_PRIORITY; 246 mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; 247 mWapiPskPassphrase = null; 248 mWapiEnterpriseConfig = null; 249 mIsNetworkUntrusted = false; 250 mIsNetworkOemPaid = false; 251 mIsNetworkOemPrivate = false; 252 mIsCarrierMerged = false; 253 mPriorityGroup = 0; 254 mMacRandomizationSetting = RANDOMIZATION_PERSISTENT; 255 mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 256 mSaeH2eOnlyMode = false; 257 } 258 259 /** 260 * Set the unicode SSID for the network. 261 * <p> 262 * <li>Overrides any previous value set using {@link #setSsid(String)}.</li> 263 * 264 * @param ssid The SSID of the network. It must be valid Unicode. 265 * @return Instance of {@link Builder} to enable chaining of the builder method. 266 * @throws IllegalArgumentException if the SSID is not valid unicode. 267 */ setSsid(@onNull String ssid)268 public @NonNull Builder setSsid(@NonNull String ssid) { 269 checkNotNull(ssid); 270 final CharsetEncoder unicodeEncoder = StandardCharsets.UTF_8.newEncoder(); 271 if (!unicodeEncoder.canEncode(ssid)) { 272 throw new IllegalArgumentException("SSID is not a valid unicode string"); 273 } 274 mSsid = new String(ssid); 275 return this; 276 } 277 278 /** 279 * Set the BSSID to use for filtering networks from scan results. Will only match network 280 * whose BSSID is identical to the specified value. 281 * <p> 282 * <li Sets a specific BSSID for the network suggestion. If set, only the specified BSSID 283 * with the specified SSID will be considered for connection. 284 * <li>If set, only the specified BSSID with the specified SSID will be considered for 285 * connection.</li> 286 * <li>If not set, all BSSIDs with the specified SSID will be considered for connection. 287 * </li> 288 * <li>Overrides any previous value set using {@link #setBssid(MacAddress)}.</li> 289 * 290 * @param bssid BSSID of the network. 291 * @return Instance of {@link Builder} to enable chaining of the builder method. 292 */ setBssid(@onNull MacAddress bssid)293 public @NonNull Builder setBssid(@NonNull MacAddress bssid) { 294 checkNotNull(bssid); 295 mBssid = MacAddress.fromBytes(bssid.toByteArray()); 296 return this; 297 } 298 299 /** 300 * Specifies whether this represents an Enhanced Open (OWE) network. 301 * 302 * @param isEnhancedOpen {@code true} to indicate that the network used enhanced open, 303 * {@code false} otherwise. 304 * @return Instance of {@link Builder} to enable chaining of the builder method. 305 */ setIsEnhancedOpen(boolean isEnhancedOpen)306 public @NonNull Builder setIsEnhancedOpen(boolean isEnhancedOpen) { 307 mIsEnhancedOpen = isEnhancedOpen; 308 return this; 309 } 310 311 /** 312 * Set the ASCII WPA2 passphrase for this network. Needed for authenticating to 313 * WPA2-PSK networks. 314 * 315 * @param passphrase passphrase of the network. 316 * @return Instance of {@link Builder} to enable chaining of the builder method. 317 * @throws IllegalArgumentException if the passphrase is not ASCII encodable. 318 */ setWpa2Passphrase(@onNull String passphrase)319 public @NonNull Builder setWpa2Passphrase(@NonNull String passphrase) { 320 checkNotNull(passphrase); 321 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); 322 if (!asciiEncoder.canEncode(passphrase)) { 323 throw new IllegalArgumentException("passphrase not ASCII encodable"); 324 } 325 mWpa2PskPassphrase = passphrase; 326 return this; 327 } 328 329 /** 330 * Set the ASCII WPA3 passphrase for this network. Needed for authenticating to WPA3-SAE 331 * networks. 332 * 333 * @param passphrase passphrase of the network. 334 * @return Instance of {@link Builder} to enable chaining of the builder method. 335 * @throws IllegalArgumentException if the passphrase is not ASCII encodable. 336 */ setWpa3Passphrase(@onNull String passphrase)337 public @NonNull Builder setWpa3Passphrase(@NonNull String passphrase) { 338 checkNotNull(passphrase); 339 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); 340 if (!asciiEncoder.canEncode(passphrase)) { 341 throw new IllegalArgumentException("passphrase not ASCII encodable"); 342 } 343 mWpa3SaePassphrase = passphrase; 344 return this; 345 } 346 347 /** 348 * Set the associated enterprise configuration for this network. Needed for authenticating 349 * to WPA2 enterprise networks. See {@link WifiEnterpriseConfig} for description. 350 * 351 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 352 * @return Instance of {@link Builder} to enable chaining of the builder method. 353 * @throws IllegalArgumentException If configuration uses server certificate but validation 354 * is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()} 355 */ setWpa2EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)356 public @NonNull Builder setWpa2EnterpriseConfig( 357 @NonNull WifiEnterpriseConfig enterpriseConfig) { 358 checkNotNull(enterpriseConfig); 359 if (enterpriseConfig.isEapMethodServerCertUsed() 360 && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) { 361 throw new IllegalArgumentException("Enterprise configuration mandates server " 362 + "certificate but validation is not enabled."); 363 } 364 mWpa2EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 365 return this; 366 } 367 368 /** 369 * Set the associated enterprise configuration for this network. Needed for authenticating 370 * to WPA3-Enterprise networks (standard and 192-bit security). See 371 * {@link WifiEnterpriseConfig} for description. For 192-bit security networks, both the 372 * client and CA certificates must be provided, and must be of type of either 373 * sha384WithRSAEncryption (OID 1.2.840.113549.1.1.12) or ecdsa-with-SHA384 374 * (OID 1.2.840.10045.4.3.3). 375 * 376 * @deprecated use {@link #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig)} or 377 * {@link #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} to specify 378 * WPA3-Enterprise type explicitly. 379 * 380 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 381 * @return Instance of {@link Builder} to enable chaining of the builder method. 382 * @throws IllegalArgumentException If configuration uses server certificate but validation 383 * is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()} 384 */ 385 @Deprecated setWpa3EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)386 public @NonNull Builder setWpa3EnterpriseConfig( 387 @NonNull WifiEnterpriseConfig enterpriseConfig) { 388 checkNotNull(enterpriseConfig); 389 if (enterpriseConfig.isEapMethodServerCertUsed() 390 && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) { 391 throw new IllegalArgumentException("Enterprise configuration mandates server " 392 + "certificate but validation is not enabled."); 393 } 394 mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 395 return this; 396 } 397 398 /** 399 * Set the associated enterprise configuration for this network. Needed for authenticating 400 * to WPA3-Enterprise standard networks. See {@link WifiEnterpriseConfig} for description. 401 * For WPA3-Enterprise in 192-bit security mode networks, 402 * see {@link #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} for description. 403 * 404 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 405 * @return Instance of {@link Builder} to enable chaining of the builder method. 406 * @throws IllegalArgumentException If configuration uses server certificate but validation 407 * is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()} 408 */ setWpa3EnterpriseStandardModeConfig( @onNull WifiEnterpriseConfig enterpriseConfig)409 public @NonNull Builder setWpa3EnterpriseStandardModeConfig( 410 @NonNull WifiEnterpriseConfig enterpriseConfig) { 411 checkNotNull(enterpriseConfig); 412 if (enterpriseConfig.isEapMethodServerCertUsed() 413 && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) { 414 throw new IllegalArgumentException("Enterprise configuration mandates server " 415 + "certificate but validation is not enabled."); 416 } 417 mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 418 mWpa3EnterpriseType = WPA3_ENTERPRISE_STANDARD; 419 return this; 420 } 421 422 /** 423 * Set the associated enterprise configuration for this network. Needed for authenticating 424 * to WPA3-Enterprise in 192-bit security mode networks. See {@link WifiEnterpriseConfig} 425 * for description. Both the client and CA certificates must be provided, 426 * and must be of type of either sha384WithRSAEncryption with key length of 3072bit or 427 * more (OID 1.2.840.113549.1.1.12), or ecdsa-with-SHA384 with key length of 384bit or 428 * more (OID 1.2.840.10045.4.3.3). 429 * 430 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 431 * @return Instance of {@link Builder} to enable chaining of the builder method. 432 * @throws IllegalArgumentException if the EAP type or certificates do not 433 * meet 192-bit mode requirements. 434 */ setWpa3Enterprise192BitModeConfig( @onNull WifiEnterpriseConfig enterpriseConfig)435 public @NonNull Builder setWpa3Enterprise192BitModeConfig( 436 @NonNull WifiEnterpriseConfig enterpriseConfig) { 437 checkNotNull(enterpriseConfig); 438 if (enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.TLS) { 439 throw new IllegalArgumentException("The 192-bit mode network type must be TLS"); 440 } 441 if (!WifiEnterpriseConfig.isSuiteBCipherCert( 442 enterpriseConfig.getClientCertificate())) { 443 throw new IllegalArgumentException( 444 "The client certificate does not meet 192-bit mode requirements."); 445 } 446 if (!WifiEnterpriseConfig.isSuiteBCipherCert( 447 enterpriseConfig.getCaCertificate())) { 448 throw new IllegalArgumentException( 449 "The CA certificate does not meet 192-bit mode requirements."); 450 } 451 452 mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 453 mWpa3EnterpriseType = WPA3_ENTERPRISE_192_BIT; 454 return this; 455 } 456 457 /** 458 * Set the associated Passpoint configuration for this network. Needed for authenticating 459 * to Hotspot 2.0 networks. See {@link PasspointConfiguration} for description. 460 * 461 * @param passpointConfig Instance of {@link PasspointConfiguration}. 462 * @return Instance of {@link Builder} to enable chaining of the builder method. 463 * @throws IllegalArgumentException if passpoint configuration is invalid. 464 */ setPasspointConfig( @onNull PasspointConfiguration passpointConfig)465 public @NonNull Builder setPasspointConfig( 466 @NonNull PasspointConfiguration passpointConfig) { 467 checkNotNull(passpointConfig); 468 if (!passpointConfig.validate()) { 469 throw new IllegalArgumentException("Passpoint configuration is invalid"); 470 } 471 mPasspointConfiguration = new PasspointConfiguration(passpointConfig); 472 return this; 473 } 474 475 /** 476 * Set the carrier ID of the network operator. The carrier ID associates a Suggested 477 * network with a specific carrier (and therefore SIM). The carrier ID must be provided 478 * for any network which uses the SIM-based authentication: e.g. EAP-SIM, EAP-AKA, 479 * EAP-AKA', and EAP-PEAP with SIM-based phase 2 authentication. 480 * @param carrierId see {@link TelephonyManager#getSimCarrierId()}. 481 * @return Instance of {@link Builder} to enable chaining of the builder method. 482 * 483 * @hide 484 */ 485 @SystemApi 486 @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) setCarrierId(int carrierId)487 public @NonNull Builder setCarrierId(int carrierId) { 488 mCarrierId = carrierId; 489 return this; 490 } 491 492 /** 493 * Configure the suggestion to only be used with the SIM identified by the subscription 494 * ID specified in this method. The suggested network will only be used by that SIM and 495 * no other SIM - even from the same carrier. 496 * <p> 497 * The caller is restricted to be either of: 498 * <li>A carrier provisioning app (which holds the 499 * {@code android.Manifest.permission#NETWORK_CARRIER_PROVISIONING} permission). 500 * <li>A carrier-privileged app - which is restricted to only specify a subscription ID 501 * which belong to the same carrier which signed the app, see 502 * {@link TelephonyManager#hasCarrierPrivileges()}. 503 * <p> 504 * Specifying a subscription ID which doesn't match these restriction will cause the 505 * suggestion to be rejected with the error code 506 * {@link WifiManager#STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED}. 507 * 508 * @param subscriptionId subscription ID see {@link SubscriptionInfo#getSubscriptionId()} 509 * @return Instance of {@link Builder} to enable chaining of the builder method. 510 * @throws IllegalArgumentException if subscriptionId equals to {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} 511 */ 512 @RequiresApi(Build.VERSION_CODES.S) setSubscriptionId(int subscriptionId)513 public @NonNull Builder setSubscriptionId(int subscriptionId) { 514 if (!SdkLevel.isAtLeastS()) { 515 throw new UnsupportedOperationException(); 516 } 517 if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 518 throw new IllegalArgumentException("Subscription Id is invalid"); 519 } 520 mSubscriptionId = subscriptionId; 521 return this; 522 } 523 524 /** 525 * Suggested networks are considered as part of a pool of all suggested networks and other 526 * networks (e.g. saved networks) - one of which will be selected. 527 * <ul> 528 * <li> Any app can suggest any number of networks. </li> 529 * <li> Priority: only the highest priority (0 being the lowest) currently visible suggested 530 * network or networks (multiple suggested networks may have the same priority) are added to 531 * the network selection pool.</li> 532 * </ul> 533 * <p> 534 * However, this restricts a suggesting app to have a single group of networks which can be 535 * prioritized. In some circumstances multiple groups are useful: for instance, suggesting 536 * networks for 2 different SIMs - each of which may have its own priority order. 537 * <p> 538 * Priority group: creates a separate priority group. Only the highest priority, currently 539 * visible suggested network or networks, within each priority group are included in the 540 * network selection pool. 541 * <p> 542 * Specify an arbitrary integer only used as the priority group. Use with 543 * {@link #setPriority(int)}. 544 * 545 * @param priorityGroup priority group id, if not set default is 0. 546 * @return Instance of {@link Builder} to enable chaining of the builder method. 547 */ setPriorityGroup(@ntRangefrom = 0) int priorityGroup)548 public @NonNull Builder setPriorityGroup(@IntRange(from = 0) int priorityGroup) { 549 mPriorityGroup = priorityGroup; 550 return this; 551 } 552 553 /** 554 * Set the ASCII WAPI passphrase for this network. Needed for authenticating to 555 * WAPI-PSK networks. 556 * 557 * @param passphrase passphrase of the network. 558 * @return Instance of {@link Builder} to enable chaining of the builder method. 559 * @throws IllegalArgumentException if the passphrase is not ASCII encodable. 560 * 561 */ setWapiPassphrase(@onNull String passphrase)562 public @NonNull Builder setWapiPassphrase(@NonNull String passphrase) { 563 checkNotNull(passphrase); 564 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); 565 if (!asciiEncoder.canEncode(passphrase)) { 566 throw new IllegalArgumentException("passphrase not ASCII encodable"); 567 } 568 mWapiPskPassphrase = passphrase; 569 return this; 570 } 571 572 /** 573 * Set the associated enterprise configuration for this network. Needed for authenticating 574 * to WAPI-CERT networks. See {@link WifiEnterpriseConfig} for description. 575 * 576 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 577 * @return Instance of {@link Builder} to enable chaining of the builder method. 578 */ setWapiEnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)579 public @NonNull Builder setWapiEnterpriseConfig( 580 @NonNull WifiEnterpriseConfig enterpriseConfig) { 581 checkNotNull(enterpriseConfig); 582 mWapiEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 583 return this; 584 } 585 586 /** 587 * Specifies whether this represents a hidden network. 588 * <p> 589 * <li>If not set, defaults to false (i.e not a hidden network).</li> 590 * 591 * @param isHiddenSsid {@code true} to indicate that the network is hidden, {@code false} 592 * otherwise. 593 * @return Instance of {@link Builder} to enable chaining of the builder method. 594 */ setIsHiddenSsid(boolean isHiddenSsid)595 public @NonNull Builder setIsHiddenSsid(boolean isHiddenSsid) { 596 mIsHiddenSSID = isHiddenSsid; 597 return this; 598 } 599 600 /** 601 * Specifies the MAC randomization method. 602 * <p> 603 * Suggested networks will never use the device (factory) MAC address to associate to the 604 * network - instead they use a locally generated random MAC address. This method controls 605 * the strategy for generating the random MAC address. If not set, defaults to 606 * {@link #RANDOMIZATION_PERSISTENT}. 607 * 608 * @param macRandomizationSetting - one of {@code RANDOMIZATION_*} values 609 * @return Instance of {@link Builder} to enable chaining of the builder method. 610 */ setMacRandomizationSetting( @acRandomizationSetting int macRandomizationSetting)611 public @NonNull Builder setMacRandomizationSetting( 612 @MacRandomizationSetting int macRandomizationSetting) { 613 switch (macRandomizationSetting) { 614 case RANDOMIZATION_PERSISTENT: 615 case RANDOMIZATION_NON_PERSISTENT: 616 mMacRandomizationSetting = macRandomizationSetting; 617 break; 618 default: 619 throw new IllegalArgumentException(); 620 } 621 return this; 622 } 623 624 /** 625 * Specifies whether the app needs to log in to a captive portal to obtain Internet access. 626 * <p> 627 * This will dictate if the directed broadcast 628 * {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} will be sent to the 629 * app after successfully connecting to the network. 630 * Use this for captive portal type networks where the app needs to authenticate the user 631 * before the device can access the network. 632 * <p> 633 * <li>If not set, defaults to false (i.e no app interaction required).</li> 634 * 635 * @param isAppInteractionRequired {@code true} to indicate that app interaction is 636 * required, {@code false} otherwise. 637 * @return Instance of {@link Builder} to enable chaining of the builder method. 638 */ setIsAppInteractionRequired(boolean isAppInteractionRequired)639 public @NonNull Builder setIsAppInteractionRequired(boolean isAppInteractionRequired) { 640 mIsAppInteractionRequired = isAppInteractionRequired; 641 return this; 642 } 643 644 /** 645 * Specifies whether the user needs to log in to a captive portal to obtain Internet access. 646 * <p> 647 * <li>If not set, defaults to false (i.e no user interaction required).</li> 648 * 649 * @param isUserInteractionRequired {@code true} to indicate that user interaction is 650 * required, {@code false} otherwise. 651 * @return Instance of {@link Builder} to enable chaining of the builder method. 652 */ setIsUserInteractionRequired(boolean isUserInteractionRequired)653 public @NonNull Builder setIsUserInteractionRequired(boolean isUserInteractionRequired) { 654 mIsUserInteractionRequired = isUserInteractionRequired; 655 return this; 656 } 657 658 /** 659 * Specify the priority of this network among other network suggestions provided by the same 660 * app and within the same priority group, see {@link #setPriorityGroup(int)}. Priorities 661 * have no impact on suggestions by other apps or suggestions from the same app using a 662 * different priority group. The higher the number, the higher the priority 663 * (i.e value of 0 = lowest priority). If not set, defaults to a lower priority than any 664 * assigned priority. 665 * 666 * @param priority Integer number representing the priority among suggestions by the app. 667 * @return Instance of {@link Builder} to enable chaining of the builder method. 668 * @throws IllegalArgumentException if the priority value is negative. 669 */ setPriority(@ntRangefrom = 0) int priority)670 public @NonNull Builder setPriority(@IntRange(from = 0) int priority) { 671 if (priority < 0) { 672 throw new IllegalArgumentException("Invalid priority value " + priority); 673 } 674 mPriority = priority; 675 return this; 676 } 677 678 /** 679 * Specifies whether this network is metered. 680 * <p> 681 * <li>If not set, defaults to detect automatically.</li> 682 * 683 * @param isMetered {@code true} to indicate that the network is metered, {@code false} 684 * for not metered. 685 * @return Instance of {@link Builder} to enable chaining of the builder method. 686 */ setIsMetered(boolean isMetered)687 public @NonNull Builder setIsMetered(boolean isMetered) { 688 if (isMetered) { 689 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED; 690 } else { 691 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED; 692 } 693 return this; 694 } 695 696 /** 697 * Specifies whether the network credentials provided with this suggestion can be used by 698 * the user to explicitly (manually) connect to this network. If true this network will 699 * appear in the Wi-Fi Picker (in Settings) and the user will be able to select and connect 700 * to it with the provided credentials. If false, the user will need to enter network 701 * credentials and the resulting configuration will become a user saved network. 702 * <p> 703 * <li>Note: Only valid for secure (non-open) networks. 704 * <li>If not set, defaults to true (i.e. allow user to manually connect) for secure 705 * networks and false for open networks.</li> 706 * 707 * @param isShared {@code true} to indicate that the credentials may be used by the user to 708 * manually connect to the network, {@code false} otherwise. 709 * @return Instance of {@link Builder} to enable chaining of the builder method. 710 */ setCredentialSharedWithUser(boolean isShared)711 public @NonNull Builder setCredentialSharedWithUser(boolean isShared) { 712 mIsSharedWithUser = isShared; 713 mIsSharedWithUserSet = true; 714 return this; 715 } 716 717 /** 718 * Specifies whether the suggestion is created with auto-join enabled or disabled. The 719 * user may modify the auto-join configuration of a suggestion directly once the device 720 * associates to the network. 721 * <p> 722 * If auto-join is initialized as disabled the user may still be able to manually connect 723 * to the network. Therefore, disabling auto-join only makes sense if 724 * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which 725 * itself implies a secure (non-open) network. 726 * <p> 727 * If not set, defaults to true (i.e. auto-join is initialized as enabled). 728 * 729 * @param enabled true for initializing with auto-join enabled (the default), false to 730 * initializing with auto-join disabled. 731 * @return Instance of {@link Builder} to enable chaining of the builder method. 732 */ setIsInitialAutojoinEnabled(boolean enabled)733 public @NonNull Builder setIsInitialAutojoinEnabled(boolean enabled) { 734 mIsInitialAutojoinEnabled = enabled; 735 return this; 736 } 737 738 /** 739 * Specifies whether the system will bring up the network (if selected) as untrusted. An 740 * untrusted network has its {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} 741 * capability removed. The Wi-Fi network selection process may use this information to 742 * influence priority of the suggested network for Wi-Fi network selection (most likely to 743 * reduce it). The connectivity service may use this information to influence the overall 744 * network configuration of the device. 745 * <p> 746 * <li> These suggestions are only considered for network selection if a 747 * {@link NetworkRequest} without {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} 748 * capability is filed. 749 * <li> An untrusted network's credentials may not be shared with the user using 750 * {@link #setCredentialSharedWithUser(boolean)}.</li> 751 * <li> If not set, defaults to false (i.e. network is trusted).</li> 752 * 753 * @param isUntrusted Boolean indicating whether the network should be brought up untrusted 754 * (if true) or trusted (if false). 755 * @return Instance of {@link Builder} to enable chaining of the builder method. 756 */ setUntrusted(boolean isUntrusted)757 public @NonNull Builder setUntrusted(boolean isUntrusted) { 758 mIsNetworkUntrusted = isUntrusted; 759 return this; 760 } 761 762 /** 763 * Specifies whether the system will bring up the network (if selected) as OEM paid. An 764 * OEM paid network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID} capability 765 * added. 766 * Note: 767 * <li>The connectivity service may use this information to influence the overall 768 * network configuration of the device. This network is typically only available to system 769 * apps. 770 * <li>On devices which do not support concurrent connection (indicated via 771 * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), Wi-Fi 772 * network selection process may use this information to influence priority of the 773 * suggested network for Wi-Fi network selection (most likely to reduce it). 774 * <li>On devices which support concurrent connections (indicated via 775 * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), these 776 * OEM paid networks may be brought up as a secondary concurrent connection (primary 777 * connection will be used for networks available to the user and all apps. 778 * <p> 779 * <li> An OEM paid network's credentials may not be shared with the user using 780 * {@link #setCredentialSharedWithUser(boolean)}.</li> 781 * <li> These suggestions are only considered for network selection if a 782 * {@link NetworkRequest} with {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID} 783 * capability is filed. 784 * <li> Each suggestion can have both {@link #setOemPaid(boolean)} and 785 * {@link #setOemPrivate(boolean)} set if the app wants these suggestions considered 786 * for creating either an OEM paid network or OEM private network determined based on 787 * the {@link NetworkRequest} that is active. 788 * <li> If not set, defaults to false (i.e. network is not OEM paid).</li> 789 * 790 * @param isOemPaid Boolean indicating whether the network should be brought up as OEM paid 791 * (if true) or not OEM paid (if false). 792 * @return Instance of {@link Builder} to enable chaining of the builder method. 793 * @hide 794 */ 795 @SystemApi 796 @RequiresApi(Build.VERSION_CODES.S) setOemPaid(boolean isOemPaid)797 public @NonNull Builder setOemPaid(boolean isOemPaid) { 798 if (!SdkLevel.isAtLeastS()) { 799 throw new UnsupportedOperationException(); 800 } 801 mIsNetworkOemPaid = isOemPaid; 802 return this; 803 } 804 805 /** 806 * Specifies whether the system will bring up the network (if selected) as OEM private. An 807 * OEM private network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE} capability 808 * added. 809 * Note: 810 * <li>The connectivity service may use this information to influence the overall 811 * network configuration of the device. This network is typically only available to system 812 * apps. 813 * <li>On devices which do not support concurrent connection (indicated via 814 * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), Wi-Fi 815 * network selection process may use this information to influence priority of the suggested 816 * network for Wi-Fi network selection (most likely to reduce it). 817 * <li>On devices which support concurrent connections (indicated via 818 * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), these OEM 819 * private networks may be brought up as a secondary concurrent connection (primary 820 * connection will be used for networks available to the user and all apps. 821 * <p> 822 * <li> An OEM private network's credentials may not be shared with the user using 823 * {@link #setCredentialSharedWithUser(boolean)}.</li> 824 * <li> These suggestions are only considered for network selection if a 825 * {@link NetworkRequest} with {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE} 826 * capability is filed. 827 * <li> Each suggestion can have both {@link #setOemPaid(boolean)} and 828 * {@link #setOemPrivate(boolean)} set if the app wants these suggestions considered 829 * for creating either an OEM paid network or OEM private network determined based on 830 * the {@link NetworkRequest} that is active. 831 * <li> If not set, defaults to false (i.e. network is not OEM private).</li> 832 * 833 * @param isOemPrivate Boolean indicating whether the network should be brought up as OEM 834 * private (if true) or not OEM private (if false). 835 * @return Instance of {@link Builder} to enable chaining of the builder method. 836 * @hide 837 */ 838 @SystemApi 839 @RequiresApi(Build.VERSION_CODES.S) setOemPrivate(boolean isOemPrivate)840 public @NonNull Builder setOemPrivate(boolean isOemPrivate) { 841 if (!SdkLevel.isAtLeastS()) { 842 throw new UnsupportedOperationException(); 843 } 844 mIsNetworkOemPrivate = isOemPrivate; 845 return this; 846 } 847 848 /** 849 * Specifies whether the suggestion represents a carrier merged network. A carrier merged 850 * Wi-Fi network is treated as part of the mobile carrier network. Such configuration may 851 * impact the user interface and data usage accounting. 852 * <p> 853 * Only carriers with the 854 * {@link android.telephony.CarrierConfigManager#KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL} 855 * flag set to {@code true} may use this API. 856 * <p> 857 * <li>A suggestion marked as carrier merged must be metered enterprise network with a valid 858 * subscription Id set. 859 * @see #setIsMetered(boolean) 860 * @see #setSubscriptionId(int) 861 * @see #setWpa2EnterpriseConfig(WifiEnterpriseConfig) 862 * @see #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig) 863 * @see #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig) 864 * @see #setPasspointConfig(PasspointConfiguration) 865 * </li> 866 * <li>If not set, defaults to false (i.e. not a carrier merged network.)</li> 867 * </p> 868 * @param isCarrierMerged Boolean indicating whether the network is treated a carrier 869 * merged network (if true) or non-merged network (if false); 870 * @return Instance of {@link Builder} to enable chaining of the builder method. 871 */ 872 @RequiresApi(Build.VERSION_CODES.S) setCarrierMerged(boolean isCarrierMerged)873 public @NonNull Builder setCarrierMerged(boolean isCarrierMerged) { 874 if (!SdkLevel.isAtLeastS()) { 875 throw new UnsupportedOperationException(); 876 } 877 mIsCarrierMerged = isCarrierMerged; 878 return this; 879 } 880 881 /** 882 * Specifies whether the suggestion represents an SAE network which only 883 * accepts Hash-to-Element mode. 884 * If this is enabled, Hunting & Pecking mode is disabled and only Hash-to-Element 885 * mode is used for this network. 886 * This is only valid for an SAE network which is configured using the 887 * {@link #setWpa3Passphrase}. 888 * Before calling this API, the application should check Hash-to-Element support using 889 * {@link WifiManager#isWpa3SaeH2eSupported()}. 890 * 891 * @param enable Boolean indicating whether the network only accepts Hash-to-Element mode, 892 * default is false. 893 * @return Instance of {@link Builder} to enable chaining of the builder method. 894 */ 895 @RequiresApi(Build.VERSION_CODES.S) setIsWpa3SaeH2eOnlyModeEnabled(boolean enable)896 public @NonNull Builder setIsWpa3SaeH2eOnlyModeEnabled(boolean enable) { 897 if (!SdkLevel.isAtLeastS()) { 898 throw new UnsupportedOperationException(); 899 } 900 mSaeH2eOnlyMode = enable; 901 return this; 902 } 903 setSecurityParamsInWifiConfiguration( @onNull WifiConfiguration configuration)904 private void setSecurityParamsInWifiConfiguration( 905 @NonNull WifiConfiguration configuration) { 906 if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network. 907 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK); 908 // WifiConfiguration.preSharedKey needs quotes around ASCII password. 909 configuration.preSharedKey = "\"" + mWpa2PskPassphrase + "\""; 910 } else if (!TextUtils.isEmpty(mWpa3SaePassphrase)) { // WPA3-SAE network. 911 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE); 912 // WifiConfiguration.preSharedKey needs quotes around ASCII password. 913 configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\""; 914 if (mSaeH2eOnlyMode) configuration.enableSaeH2eOnlyMode(mSaeH2eOnlyMode); 915 } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network 916 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP); 917 configuration.enterpriseConfig = mWpa2EnterpriseConfig; 918 } else if (mWpa3EnterpriseConfig != null) { // WPA3-Enterprise 919 if (mWpa3EnterpriseType == WPA3_ENTERPRISE_AUTO 920 && mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS 921 && WifiEnterpriseConfig.isSuiteBCipherCert( 922 mWpa3EnterpriseConfig.getClientCertificate()) 923 && WifiEnterpriseConfig.isSuiteBCipherCert( 924 mWpa3EnterpriseConfig.getCaCertificate())) { 925 // WPA3-Enterprise in 192-bit security mode 926 configuration.setSecurityParams( 927 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT); 928 } else if (mWpa3EnterpriseType == WPA3_ENTERPRISE_192_BIT) { 929 // WPA3-Enterprise in 192-bit security mode 930 configuration.setSecurityParams( 931 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT); 932 } else { 933 // WPA3-Enterprise 934 configuration.setSecurityParams( 935 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE); 936 } 937 configuration.enterpriseConfig = mWpa3EnterpriseConfig; 938 } else if (mIsEnhancedOpen) { // OWE network 939 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE); 940 } else if (!TextUtils.isEmpty(mWapiPskPassphrase)) { // WAPI-PSK network. 941 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_PSK); 942 // WifiConfiguration.preSharedKey needs quotes around ASCII password. 943 configuration.preSharedKey = "\"" + mWapiPskPassphrase + "\""; 944 } else if (mWapiEnterpriseConfig != null) { // WAPI-CERT network 945 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT); 946 configuration.enterpriseConfig = mWapiEnterpriseConfig; 947 } else { // Open network 948 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); 949 } 950 } 951 952 /** 953 * Helper method to build WifiConfiguration object from the builder. 954 * @return Instance of {@link WifiConfiguration}. 955 */ buildWifiConfiguration()956 private WifiConfiguration buildWifiConfiguration() { 957 final WifiConfiguration wifiConfiguration = new WifiConfiguration(); 958 // WifiConfiguration.SSID needs quotes around unicode SSID. 959 wifiConfiguration.SSID = "\"" + mSsid + "\""; 960 if (mBssid != null) { 961 wifiConfiguration.BSSID = mBssid.toString(); 962 } 963 964 setSecurityParamsInWifiConfiguration(wifiConfiguration); 965 966 wifiConfiguration.hiddenSSID = mIsHiddenSSID; 967 wifiConfiguration.priority = mPriority; 968 wifiConfiguration.meteredOverride = mMeteredOverride; 969 wifiConfiguration.carrierId = mCarrierId; 970 wifiConfiguration.trusted = !mIsNetworkUntrusted; 971 wifiConfiguration.oemPaid = mIsNetworkOemPaid; 972 wifiConfiguration.oemPrivate = mIsNetworkOemPrivate; 973 wifiConfiguration.carrierMerged = mIsCarrierMerged; 974 wifiConfiguration.macRandomizationSetting = 975 mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT 976 ? WifiConfiguration.RANDOMIZATION_NON_PERSISTENT 977 : WifiConfiguration.RANDOMIZATION_PERSISTENT; 978 wifiConfiguration.subscriptionId = mSubscriptionId; 979 return wifiConfiguration; 980 } 981 validateSecurityParams()982 private void validateSecurityParams() { 983 int numSecurityTypes = 0; 984 numSecurityTypes += mIsEnhancedOpen ? 1 : 0; 985 numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0; 986 numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0; 987 numSecurityTypes += !TextUtils.isEmpty(mWapiPskPassphrase) ? 1 : 0; 988 numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0; 989 numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0; 990 numSecurityTypes += mWapiEnterpriseConfig != null ? 1 : 0; 991 numSecurityTypes += mPasspointConfiguration != null ? 1 : 0; 992 if (numSecurityTypes > 1) { 993 throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase," 994 + " setWpa3Passphrase, setWpa2EnterpriseConfig, setWpa3EnterpriseConfig" 995 + " setWapiPassphrase, setWapiCertSuite, setIsWapiCertSuiteAuto" 996 + " or setPasspointConfig can be invoked for network suggestion"); 997 } 998 } 999 buildWifiConfigurationForPasspoint()1000 private WifiConfiguration buildWifiConfigurationForPasspoint() { 1001 WifiConfiguration wifiConfiguration = new WifiConfiguration(); 1002 wifiConfiguration.FQDN = mPasspointConfiguration.getHomeSp().getFqdn(); 1003 wifiConfiguration.setPasspointUniqueId(mPasspointConfiguration.getUniqueId()); 1004 wifiConfiguration.priority = mPriority; 1005 wifiConfiguration.meteredOverride = mMeteredOverride; 1006 wifiConfiguration.trusted = !mIsNetworkUntrusted; 1007 wifiConfiguration.oemPaid = mIsNetworkOemPaid; 1008 wifiConfiguration.oemPrivate = mIsNetworkOemPrivate; 1009 wifiConfiguration.carrierMerged = mIsCarrierMerged; 1010 wifiConfiguration.subscriptionId = mSubscriptionId; 1011 mPasspointConfiguration.setCarrierId(mCarrierId); 1012 mPasspointConfiguration.setSubscriptionId(mSubscriptionId); 1013 mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride); 1014 mPasspointConfiguration.setOemPrivate(mIsNetworkOemPrivate); 1015 mPasspointConfiguration.setOemPaid(mIsNetworkOemPaid); 1016 mPasspointConfiguration.setCarrierMerged(mIsCarrierMerged); 1017 wifiConfiguration.macRandomizationSetting = 1018 mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT 1019 ? WifiConfiguration.RANDOMIZATION_NON_PERSISTENT 1020 : WifiConfiguration.RANDOMIZATION_PERSISTENT; 1021 return wifiConfiguration; 1022 } 1023 1024 /** 1025 * Create a network suggestion object for use in 1026 * {@link WifiManager#addNetworkSuggestions(List)}. 1027 * 1028 *<p class="note"> 1029 * <b>Note:</b> Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID 1030 * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to 1031 * the platform. 1032 * </p> 1033 * 1034 * For example: 1035 * To provide credentials for one open, one WPA2, one WPA3 network with their 1036 * corresponding SSID's and one with Passpoint config: 1037 * 1038 * <pre>{@code 1039 * final WifiNetworkSuggestion suggestion1 = 1040 * new Builder() 1041 * .setSsid("test111111") 1042 * .build(); 1043 * final WifiNetworkSuggestion suggestion2 = 1044 * new Builder() 1045 * .setSsid("test222222") 1046 * .setWpa2Passphrase("test123456") 1047 * .build(); 1048 * final WifiNetworkSuggestion suggestion3 = 1049 * new Builder() 1050 * .setSsid("test333333") 1051 * .setWpa3Passphrase("test6789") 1052 * .build(); 1053 * final PasspointConfiguration passpointConfig= new PasspointConfiguration(); 1054 * // configure passpointConfig to include a valid Passpoint configuration 1055 * final WifiNetworkSuggestion suggestion4 = 1056 * new Builder() 1057 * .setPasspointConfig(passpointConfig) 1058 * .build(); 1059 * final List<WifiNetworkSuggestion> suggestionsList = 1060 * new ArrayList<WifiNetworkSuggestion> { { 1061 * add(suggestion1); 1062 * add(suggestion2); 1063 * add(suggestion3); 1064 * add(suggestion4); 1065 * } }; 1066 * final WifiManager wifiManager = 1067 * context.getSystemService(Context.WIFI_SERVICE); 1068 * wifiManager.addNetworkSuggestions(suggestionsList); 1069 * // ... 1070 * }</pre> 1071 * 1072 * @return Instance of {@link WifiNetworkSuggestion} 1073 * @throws IllegalStateException on invalid params set 1074 * @see WifiNetworkSuggestion 1075 */ build()1076 public @NonNull WifiNetworkSuggestion build() { 1077 validateSecurityParams(); 1078 WifiConfiguration wifiConfiguration; 1079 if (mPasspointConfiguration != null) { 1080 if (mSsid != null) { 1081 throw new IllegalStateException("setSsid should not be invoked for suggestion " 1082 + "with Passpoint configuration"); 1083 } 1084 if (mIsHiddenSSID) { 1085 throw new IllegalStateException("setIsHiddenSsid should not be invoked for " 1086 + "suggestion with Passpoint configuration"); 1087 } 1088 wifiConfiguration = buildWifiConfigurationForPasspoint(); 1089 mPasspointConfiguration.setEnhancedMacRandomizationEnabled( 1090 mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT); 1091 } else { 1092 if (mSsid == null) { 1093 throw new IllegalStateException("setSsid should be invoked for suggestion"); 1094 } 1095 if (TextUtils.isEmpty(mSsid)) { 1096 throw new IllegalStateException("invalid ssid for suggestion"); 1097 } 1098 if (mBssid != null 1099 && (mBssid.equals(MacAddress.BROADCAST_ADDRESS) 1100 || mBssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS))) { 1101 throw new IllegalStateException("invalid bssid for suggestion"); 1102 } 1103 if (TextUtils.isEmpty(mWpa3SaePassphrase) && mSaeH2eOnlyMode) { 1104 throw new IllegalStateException( 1105 "Hash-to-Element only mode is only allowed for the SAE network"); 1106 } 1107 1108 wifiConfiguration = buildWifiConfiguration(); 1109 if (wifiConfiguration.isOpenNetwork()) { 1110 if (mIsSharedWithUserSet && mIsSharedWithUser) { 1111 throw new IllegalStateException("Open network should not be " 1112 + "setCredentialSharedWithUser to true"); 1113 } 1114 mIsSharedWithUser = false; 1115 } 1116 } 1117 if (!mIsSharedWithUser && !mIsInitialAutojoinEnabled) { 1118 throw new IllegalStateException("Should have not a network with both " 1119 + "setCredentialSharedWithUser and " 1120 + "setIsAutojoinEnabled set to false"); 1121 } 1122 if (mIsNetworkUntrusted) { 1123 if (mIsSharedWithUserSet && mIsSharedWithUser) { 1124 throw new IllegalStateException("Should not be both" 1125 + "setCredentialSharedWithUser and +" 1126 + "setUntrusted to true"); 1127 } 1128 mIsSharedWithUser = false; 1129 } 1130 if (mIsNetworkOemPaid) { 1131 if (mIsSharedWithUserSet && mIsSharedWithUser) { 1132 throw new IllegalStateException("Should not be both" 1133 + "setCredentialSharedWithUser and +" 1134 + "setOemPaid to true"); 1135 } 1136 mIsSharedWithUser = false; 1137 } 1138 if (mIsNetworkOemPrivate) { 1139 if (mIsSharedWithUserSet && mIsSharedWithUser) { 1140 throw new IllegalStateException("Should not be both" 1141 + "setCredentialSharedWithUser and +" 1142 + "setOemPrivate to true"); 1143 } 1144 mIsSharedWithUser = false; 1145 } 1146 if (mIsCarrierMerged) { 1147 if (mSubscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID 1148 || mMeteredOverride != WifiConfiguration.METERED_OVERRIDE_METERED 1149 || !isEnterpriseSuggestion()) { 1150 throw new IllegalStateException("A carrier merged network must be a metered, " 1151 + "enterprise network with valid subscription Id"); 1152 } 1153 } 1154 return new WifiNetworkSuggestion( 1155 wifiConfiguration, 1156 mPasspointConfiguration, 1157 mIsAppInteractionRequired, 1158 mIsUserInteractionRequired, 1159 mIsSharedWithUser, 1160 mIsInitialAutojoinEnabled, 1161 mPriorityGroup); 1162 } 1163 isEnterpriseSuggestion()1164 private boolean isEnterpriseSuggestion() { 1165 return !(mWpa2EnterpriseConfig == null && mWpa3EnterpriseConfig == null 1166 && mWapiEnterpriseConfig == null && mPasspointConfiguration == null); 1167 } 1168 } 1169 1170 1171 1172 /** 1173 * Network configuration for the provided network. 1174 * @hide 1175 */ 1176 @NonNull 1177 public final WifiConfiguration wifiConfiguration; 1178 1179 /** 1180 * Passpoint configuration for the provided network. 1181 * @hide 1182 */ 1183 @Nullable 1184 public final PasspointConfiguration passpointConfiguration; 1185 1186 /** 1187 * Whether app needs to log in to captive portal to obtain Internet access. 1188 * @hide 1189 */ 1190 public final boolean isAppInteractionRequired; 1191 1192 /** 1193 * Whether user needs to log in to captive portal to obtain Internet access. 1194 * @hide 1195 */ 1196 public final boolean isUserInteractionRequired; 1197 1198 /** 1199 * Whether app share credential with the user, allow user use provided credential to 1200 * connect network manually. 1201 * @hide 1202 */ 1203 public final boolean isUserAllowedToManuallyConnect; 1204 1205 /** 1206 * Whether the suggestion will be initialized as auto-joined or not. 1207 * @hide 1208 */ 1209 public final boolean isInitialAutoJoinEnabled; 1210 1211 /** 1212 * Priority group ID. 1213 * @hide 1214 */ 1215 public final int priorityGroup; 1216 1217 /** @hide */ WifiNetworkSuggestion()1218 public WifiNetworkSuggestion() { 1219 this.wifiConfiguration = new WifiConfiguration(); 1220 this.passpointConfiguration = null; 1221 this.isAppInteractionRequired = false; 1222 this.isUserInteractionRequired = false; 1223 this.isUserAllowedToManuallyConnect = true; 1224 this.isInitialAutoJoinEnabled = true; 1225 this.priorityGroup = 0; 1226 } 1227 1228 /** @hide */ WifiNetworkSuggestion(@onNull WifiConfiguration networkConfiguration, @Nullable PasspointConfiguration passpointConfiguration, boolean isAppInteractionRequired, boolean isUserInteractionRequired, boolean isUserAllowedToManuallyConnect, boolean isInitialAutoJoinEnabled, int priorityGroup)1229 public WifiNetworkSuggestion(@NonNull WifiConfiguration networkConfiguration, 1230 @Nullable PasspointConfiguration passpointConfiguration, 1231 boolean isAppInteractionRequired, 1232 boolean isUserInteractionRequired, 1233 boolean isUserAllowedToManuallyConnect, 1234 boolean isInitialAutoJoinEnabled, int priorityGroup) { 1235 checkNotNull(networkConfiguration); 1236 this.wifiConfiguration = networkConfiguration; 1237 this.passpointConfiguration = passpointConfiguration; 1238 1239 this.isAppInteractionRequired = isAppInteractionRequired; 1240 this.isUserInteractionRequired = isUserInteractionRequired; 1241 this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect; 1242 this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled; 1243 this.priorityGroup = priorityGroup; 1244 } 1245 1246 public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR = 1247 new Creator<WifiNetworkSuggestion>() { 1248 @Override 1249 public WifiNetworkSuggestion createFromParcel(Parcel in) { 1250 return new WifiNetworkSuggestion( 1251 in.readParcelable(null), // wifiConfiguration 1252 in.readParcelable(null), // PasspointConfiguration 1253 in.readBoolean(), // isAppInteractionRequired 1254 in.readBoolean(), // isUserInteractionRequired 1255 in.readBoolean(), // isSharedCredentialWithUser 1256 in.readBoolean(), // isAutojoinEnabled 1257 in.readInt() // priorityGroup 1258 ); 1259 } 1260 1261 @Override 1262 public WifiNetworkSuggestion[] newArray(int size) { 1263 return new WifiNetworkSuggestion[size]; 1264 } 1265 }; 1266 1267 @Override describeContents()1268 public int describeContents() { 1269 return 0; 1270 } 1271 1272 @Override writeToParcel(Parcel dest, int flags)1273 public void writeToParcel(Parcel dest, int flags) { 1274 dest.writeParcelable(wifiConfiguration, flags); 1275 dest.writeParcelable(passpointConfiguration, flags); 1276 dest.writeBoolean(isAppInteractionRequired); 1277 dest.writeBoolean(isUserInteractionRequired); 1278 dest.writeBoolean(isUserAllowedToManuallyConnect); 1279 dest.writeBoolean(isInitialAutoJoinEnabled); 1280 dest.writeInt(priorityGroup); 1281 } 1282 1283 @Override hashCode()1284 public int hashCode() { 1285 return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID, 1286 wifiConfiguration.getDefaultSecurityType(), 1287 wifiConfiguration.getPasspointUniqueId(), 1288 wifiConfiguration.subscriptionId, wifiConfiguration.carrierId); 1289 } 1290 1291 @Override equals(Object obj)1292 public boolean equals(Object obj) { 1293 if (this == obj) { 1294 return true; 1295 } 1296 if (!(obj instanceof WifiNetworkSuggestion)) { 1297 return false; 1298 } 1299 WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj; 1300 if (this.passpointConfiguration == null ^ lhs.passpointConfiguration == null) { 1301 return false; 1302 } 1303 1304 return TextUtils.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID) 1305 && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID) 1306 && TextUtils.equals(this.wifiConfiguration.getDefaultSecurityType(), 1307 lhs.wifiConfiguration.getDefaultSecurityType()) 1308 && TextUtils.equals(this.wifiConfiguration.getPasspointUniqueId(), 1309 lhs.wifiConfiguration.getPasspointUniqueId()) 1310 && this.wifiConfiguration.carrierId == lhs.wifiConfiguration.carrierId 1311 && this.wifiConfiguration.subscriptionId == lhs.wifiConfiguration.subscriptionId; 1312 } 1313 1314 @Override toString()1315 public String toString() { 1316 StringBuilder sb = new StringBuilder("WifiNetworkSuggestion[ ") 1317 .append("SSID=").append(wifiConfiguration.SSID) 1318 .append(", BSSID=").append(wifiConfiguration.BSSID) 1319 .append(", FQDN=").append(wifiConfiguration.FQDN) 1320 .append(", SecurityParams="); 1321 wifiConfiguration.getSecurityParamsList().stream() 1322 .forEach(param -> { 1323 sb.append(" "); 1324 sb.append(WifiConfiguration.getSecurityTypeName(param.getSecurityType())); 1325 if (param.isAddedByAutoUpgrade()) sb.append("^"); 1326 }); 1327 sb.append(", isAppInteractionRequired=").append(isAppInteractionRequired) 1328 .append(", isUserInteractionRequired=").append(isUserInteractionRequired) 1329 .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect) 1330 .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled) 1331 .append(", isUnTrusted=").append(!wifiConfiguration.trusted) 1332 .append(", isOemPaid=").append(wifiConfiguration.oemPaid) 1333 .append(", isOemPrivate=").append(wifiConfiguration.oemPrivate) 1334 .append(", isCarrierMerged=").append(wifiConfiguration.carrierMerged) 1335 .append(", isHiddenSsid=").append(wifiConfiguration.hiddenSSID) 1336 .append(", priorityGroup=").append(priorityGroup) 1337 .append(", subscriptionId=").append(wifiConfiguration.subscriptionId) 1338 .append(", carrierId=").append(wifiConfiguration.carrierId) 1339 .append(", priority=").append(wifiConfiguration.priority) 1340 .append(", meteredness=").append(wifiConfiguration.meteredOverride) 1341 .append(" ]"); 1342 return sb.toString(); 1343 } 1344 1345 /** 1346 * Get the {@link WifiConfiguration} associated with this Suggestion. 1347 * @hide 1348 */ 1349 @SystemApi 1350 @NonNull getWifiConfiguration()1351 public WifiConfiguration getWifiConfiguration() { 1352 return wifiConfiguration; 1353 } 1354 1355 /** 1356 * Get the BSSID, or null if unset. 1357 * @see Builder#setBssid(MacAddress) 1358 */ 1359 @Nullable getBssid()1360 public MacAddress getBssid() { 1361 if (wifiConfiguration.BSSID == null) { 1362 return null; 1363 } 1364 return MacAddress.fromString(wifiConfiguration.BSSID); 1365 } 1366 1367 /** @see Builder#setCredentialSharedWithUser(boolean) */ isCredentialSharedWithUser()1368 public boolean isCredentialSharedWithUser() { 1369 return isUserAllowedToManuallyConnect; 1370 } 1371 1372 /** @see Builder#setIsAppInteractionRequired(boolean) */ isAppInteractionRequired()1373 public boolean isAppInteractionRequired() { 1374 return isAppInteractionRequired; 1375 } 1376 1377 /** @see Builder#setIsEnhancedOpen(boolean) */ isEnhancedOpen()1378 public boolean isEnhancedOpen() { 1379 return wifiConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE); 1380 } 1381 1382 /** @see Builder#setIsHiddenSsid(boolean) */ isHiddenSsid()1383 public boolean isHiddenSsid() { 1384 return wifiConfiguration.hiddenSSID; 1385 } 1386 1387 /** @see Builder#setIsInitialAutojoinEnabled(boolean) */ isInitialAutojoinEnabled()1388 public boolean isInitialAutojoinEnabled() { 1389 return isInitialAutoJoinEnabled; 1390 } 1391 1392 /** @see Builder#setIsMetered(boolean) */ isMetered()1393 public boolean isMetered() { 1394 return wifiConfiguration.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED; 1395 } 1396 1397 /** @see Builder#setIsUserInteractionRequired(boolean) */ isUserInteractionRequired()1398 public boolean isUserInteractionRequired() { 1399 return isUserInteractionRequired; 1400 } 1401 1402 /** 1403 * Get the {@link PasspointConfiguration} associated with this Suggestion, or null if this 1404 * Suggestion is not for a Passpoint network. 1405 */ 1406 @Nullable getPasspointConfig()1407 public PasspointConfiguration getPasspointConfig() { 1408 return passpointConfiguration; 1409 } 1410 1411 /** @see Builder#setPriority(int) */ 1412 @IntRange(from = 0) getPriority()1413 public int getPriority() { 1414 return wifiConfiguration.priority; 1415 } 1416 1417 /** 1418 * Return the SSID of the network, or null if this is a Passpoint network. 1419 * @see Builder#setSsid(String) 1420 */ 1421 @Nullable getSsid()1422 public String getSsid() { 1423 if (wifiConfiguration.SSID == null) { 1424 return null; 1425 } 1426 return WifiInfo.sanitizeSsid(wifiConfiguration.SSID); 1427 } 1428 1429 /** @see Builder#setUntrusted(boolean) */ isUntrusted()1430 public boolean isUntrusted() { 1431 return !wifiConfiguration.trusted; 1432 } 1433 1434 /** 1435 * @see Builder#setOemPaid(boolean) 1436 * @hide 1437 */ 1438 @SystemApi 1439 @RequiresApi(Build.VERSION_CODES.S) isOemPaid()1440 public boolean isOemPaid() { 1441 if (!SdkLevel.isAtLeastS()) { 1442 throw new UnsupportedOperationException(); 1443 } 1444 return wifiConfiguration.oemPaid; 1445 } 1446 1447 /** 1448 * @see Builder#setOemPrivate(boolean) 1449 * @hide 1450 */ 1451 @SystemApi 1452 @RequiresApi(Build.VERSION_CODES.S) isOemPrivate()1453 public boolean isOemPrivate() { 1454 if (!SdkLevel.isAtLeastS()) { 1455 throw new UnsupportedOperationException(); 1456 } 1457 return wifiConfiguration.oemPrivate; 1458 } 1459 1460 /** 1461 * @see Builder#setCarrierMerged(boolean) 1462 */ 1463 @RequiresApi(Build.VERSION_CODES.S) isCarrierMerged()1464 public boolean isCarrierMerged() { 1465 if (!SdkLevel.isAtLeastS()) { 1466 throw new UnsupportedOperationException(); 1467 } 1468 return wifiConfiguration.carrierMerged; 1469 } 1470 1471 /** 1472 * Get the WifiEnterpriseConfig, or null if unset. 1473 * @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig) 1474 * @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig) 1475 * @see Builder#setWpa3EnterpriseConfig(WifiEnterpriseConfig) 1476 */ 1477 @Nullable getEnterpriseConfig()1478 public WifiEnterpriseConfig getEnterpriseConfig() { 1479 if (!wifiConfiguration.isEnterprise()) { 1480 return null; 1481 } 1482 return wifiConfiguration.enterpriseConfig; 1483 } 1484 1485 /** 1486 * Get the passphrase, or null if unset. 1487 * @see Builder#setWapiPassphrase(String) 1488 * @see Builder#setWpa2Passphrase(String) 1489 * @see Builder#setWpa3Passphrase(String) 1490 */ 1491 @Nullable getPassphrase()1492 public String getPassphrase() { 1493 if (wifiConfiguration.preSharedKey == null) { 1494 return null; 1495 } 1496 return WifiInfo.removeDoubleQuotes(wifiConfiguration.preSharedKey); 1497 } 1498 1499 /** 1500 * @see Builder#setPriorityGroup(int) 1501 */ 1502 @IntRange(from = 0) getPriorityGroup()1503 public int getPriorityGroup() { 1504 return priorityGroup; 1505 } 1506 1507 /** 1508 * @see Builder#setSubscriptionId(int) 1509 */ 1510 @RequiresApi(Build.VERSION_CODES.S) getSubscriptionId()1511 public int getSubscriptionId() { 1512 if (!SdkLevel.isAtLeastS()) { 1513 throw new UnsupportedOperationException(); 1514 } 1515 return wifiConfiguration.subscriptionId; 1516 } 1517 1518 /** 1519 * @see Builder#setCarrierId(int) 1520 * @hide 1521 */ 1522 @SystemApi getCarrierId()1523 public int getCarrierId() { 1524 return wifiConfiguration.carrierId; 1525 } 1526 } 1527