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-EAP 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 SuiteB 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. See {@link WifiEnterpriseConfig} for description. 280 * 281 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 282 * @return Instance of {@link Builder} to enable chaining of the builder method. 283 * @throws IllegalArgumentException if configuration CA certificate or 284 * AltSubjectMatch/DomainSuffixMatch is not set. 285 */ setWpa3EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)286 public @NonNull Builder setWpa3EnterpriseConfig( 287 @NonNull WifiEnterpriseConfig enterpriseConfig) { 288 checkNotNull(enterpriseConfig); 289 if (enterpriseConfig.isInsecure()) { 290 throw new IllegalArgumentException("Enterprise configuration is insecure"); 291 } 292 mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 293 return this; 294 } 295 296 /** 297 * Set the associated Passpoint configuration for this network. Needed for authenticating 298 * to Hotspot 2.0 networks. See {@link PasspointConfiguration} for description. 299 * 300 * @param passpointConfig Instance of {@link PasspointConfiguration}. 301 * @return Instance of {@link Builder} to enable chaining of the builder method. 302 * @throws IllegalArgumentException if passpoint configuration is invalid. 303 */ setPasspointConfig( @onNull PasspointConfiguration passpointConfig)304 public @NonNull Builder setPasspointConfig( 305 @NonNull PasspointConfiguration passpointConfig) { 306 checkNotNull(passpointConfig); 307 if (!passpointConfig.validate()) { 308 throw new IllegalArgumentException("Passpoint configuration is invalid"); 309 } 310 mPasspointConfiguration = passpointConfig; 311 return this; 312 } 313 314 /** 315 * Set the carrier ID of the network operator. The carrier ID associates a Suggested 316 * network with a specific carrier (and therefore SIM). The carrier ID must be provided 317 * for any network which uses the SIM-based authentication: e.g. EAP-SIM, EAP-AKA, 318 * EAP-AKA', and EAP-PEAP with SIM-based phase 2 authentication. 319 * @param carrierId see {@link TelephonyManager#getSimCarrierId()}. 320 * @return Instance of {@link Builder} to enable chaining of the builder method. 321 * 322 * @hide 323 */ 324 @SystemApi 325 @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) setCarrierId(int carrierId)326 public @NonNull Builder setCarrierId(int carrierId) { 327 mCarrierId = carrierId; 328 return this; 329 } 330 331 /** 332 * Set the ASCII WAPI passphrase for this network. Needed for authenticating to 333 * WAPI-PSK networks. 334 * 335 * @param passphrase passphrase of the network. 336 * @return Instance of {@link Builder} to enable chaining of the builder method. 337 * @throws IllegalArgumentException if the passphrase is not ASCII encodable. 338 * 339 */ setWapiPassphrase(@onNull String passphrase)340 public @NonNull Builder setWapiPassphrase(@NonNull String passphrase) { 341 checkNotNull(passphrase); 342 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); 343 if (!asciiEncoder.canEncode(passphrase)) { 344 throw new IllegalArgumentException("passphrase not ASCII encodable"); 345 } 346 mWapiPskPassphrase = passphrase; 347 return this; 348 } 349 350 /** 351 * Set the associated enterprise configuration for this network. Needed for authenticating 352 * to WAPI-CERT networks. See {@link WifiEnterpriseConfig} for description. 353 * 354 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 355 * @return Instance of {@link Builder} to enable chaining of the builder method. 356 */ setWapiEnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)357 public @NonNull Builder setWapiEnterpriseConfig( 358 @NonNull WifiEnterpriseConfig enterpriseConfig) { 359 checkNotNull(enterpriseConfig); 360 mWapiEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 361 return this; 362 } 363 364 /** 365 * Specifies whether this represents a hidden network. 366 * <p> 367 * <li>If not set, defaults to false (i.e not a hidden network).</li> 368 * 369 * @param isHiddenSsid {@code true} to indicate that the network is hidden, {@code false} 370 * otherwise. 371 * @return Instance of {@link Builder} to enable chaining of the builder method. 372 */ setIsHiddenSsid(boolean isHiddenSsid)373 public @NonNull Builder setIsHiddenSsid(boolean isHiddenSsid) { 374 mIsHiddenSSID = isHiddenSsid; 375 return this; 376 } 377 378 /** 379 * Specifies whether the app needs to log in to a captive portal to obtain Internet access. 380 * <p> 381 * This will dictate if the directed broadcast 382 * {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} will be sent to the 383 * app after successfully connecting to the network. 384 * Use this for captive portal type networks where the app needs to authenticate the user 385 * before the device can access the network. 386 * <p> 387 * <li>If not set, defaults to false (i.e no app interaction required).</li> 388 * 389 * @param isAppInteractionRequired {@code true} to indicate that app interaction is 390 * required, {@code false} otherwise. 391 * @return Instance of {@link Builder} to enable chaining of the builder method. 392 */ setIsAppInteractionRequired(boolean isAppInteractionRequired)393 public @NonNull Builder setIsAppInteractionRequired(boolean isAppInteractionRequired) { 394 mIsAppInteractionRequired = isAppInteractionRequired; 395 return this; 396 } 397 398 /** 399 * Specifies whether the user needs to log in to a captive portal to obtain Internet access. 400 * <p> 401 * <li>If not set, defaults to false (i.e no user interaction required).</li> 402 * 403 * @param isUserInteractionRequired {@code true} to indicate that user interaction is 404 * required, {@code false} otherwise. 405 * @return Instance of {@link Builder} to enable chaining of the builder method. 406 */ setIsUserInteractionRequired(boolean isUserInteractionRequired)407 public @NonNull Builder setIsUserInteractionRequired(boolean isUserInteractionRequired) { 408 mIsUserInteractionRequired = isUserInteractionRequired; 409 return this; 410 } 411 412 /** 413 * Specify the priority of this network among other network suggestions provided by the same 414 * app (priorities have no impact on suggestions by different apps). The higher the number, 415 * the higher the priority (i.e value of 0 = lowest priority). 416 * <p> 417 * <li>If not set, defaults a lower priority than any assigned priority.</li> 418 * 419 * @param priority Integer number representing the priority among suggestions by the app. 420 * @return Instance of {@link Builder} to enable chaining of the builder method. 421 * @throws IllegalArgumentException if the priority value is negative. 422 */ setPriority(@ntRangefrom = 0) int priority)423 public @NonNull Builder setPriority(@IntRange(from = 0) int priority) { 424 if (priority < 0) { 425 throw new IllegalArgumentException("Invalid priority value " + priority); 426 } 427 mPriority = priority; 428 return this; 429 } 430 431 /** 432 * Specifies whether this network is metered. 433 * <p> 434 * <li>If not set, defaults to detect automatically.</li> 435 * 436 * @param isMetered {@code true} to indicate that the network is metered, {@code false} 437 * for not metered. 438 * @return Instance of {@link Builder} to enable chaining of the builder method. 439 */ setIsMetered(boolean isMetered)440 public @NonNull Builder setIsMetered(boolean isMetered) { 441 if (isMetered) { 442 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED; 443 } else { 444 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED; 445 } 446 return this; 447 } 448 449 /** 450 * Specifies whether the network credentials provided with this suggestion can be used by 451 * the user to explicitly (manually) connect to this network. If true this network will 452 * appear in the Wi-Fi Picker (in Settings) and the user will be able to select and connect 453 * to it with the provided credentials. If false, the user will need to enter network 454 * credentials and the resulting configuration will become a user saved network. 455 * <p> 456 * <li>Note: Only valid for secure (non-open) networks. 457 * <li>If not set, defaults to true (i.e. allow user to manually connect) for secure 458 * networks and false for open networks.</li> 459 * 460 * @param isShared {@code true} to indicate that the credentials may be used by the user to 461 * manually connect to the network, {@code false} otherwise. 462 * @return Instance of {@link Builder} to enable chaining of the builder method. 463 */ setCredentialSharedWithUser(boolean isShared)464 public @NonNull Builder setCredentialSharedWithUser(boolean isShared) { 465 mIsSharedWithUser = isShared; 466 mIsSharedWithUserSet = true; 467 return this; 468 } 469 470 /** 471 * Specifies whether the suggestion is created with auto-join enabled or disabled. The 472 * user may modify the auto-join configuration of a suggestion directly once the device 473 * associates to the network. 474 * <p> 475 * If auto-join is initialized as disabled the user may still be able to manually connect 476 * to the network. Therefore, disabling auto-join only makes sense if 477 * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which 478 * itself implies a secure (non-open) network. 479 * <p> 480 * If not set, defaults to true (i.e. auto-join is initialized as enabled). 481 * 482 * @param enabled true for initializing with auto-join enabled (the default), false to 483 * initializing with auto-join disabled. 484 * @return Instance of {@link Builder} to enable chaining of the builder method. 485 */ setIsInitialAutojoinEnabled(boolean enabled)486 public @NonNull Builder setIsInitialAutojoinEnabled(boolean enabled) { 487 mIsInitialAutojoinEnabled = enabled; 488 return this; 489 } 490 491 /** 492 * Specifies whether the system will bring up the network (if selected) as untrusted. An 493 * untrusted network has its {@link android.net.NetworkCapabilities#NET_CAPABILITY_TRUSTED} 494 * capability removed. The Wi-Fi network selection process may use this information to 495 * influence priority of the suggested network for Wi-Fi network selection (most likely to 496 * reduce it). The connectivity service may use this information to influence the overall 497 * network configuration of the device. 498 * <p> 499 * <li> An untrusted network's credentials may not be shared with the user using 500 * {@link #setCredentialSharedWithUser(boolean)}.</li> 501 * <li> If not set, defaults to false (i.e. network is trusted).</li> 502 * 503 * @param isUntrusted Boolean indicating whether the network should be brought up untrusted 504 * (if true) or trusted (if false). 505 * @return Instance of {@link Builder} to enable chaining of the builder method. 506 */ setUntrusted(boolean isUntrusted)507 public @NonNull Builder setUntrusted(boolean isUntrusted) { 508 mIsNetworkUntrusted = isUntrusted; 509 return this; 510 } 511 setSecurityParamsInWifiConfiguration( @onNull WifiConfiguration configuration)512 private void setSecurityParamsInWifiConfiguration( 513 @NonNull WifiConfiguration configuration) { 514 if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network. 515 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK); 516 // WifiConfiguration.preSharedKey needs quotes around ASCII password. 517 configuration.preSharedKey = "\"" + mWpa2PskPassphrase + "\""; 518 } else if (!TextUtils.isEmpty(mWpa3SaePassphrase)) { // WPA3-SAE network. 519 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE); 520 // WifiConfiguration.preSharedKey needs quotes around ASCII password. 521 configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\""; 522 } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network 523 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP); 524 configuration.enterpriseConfig = mWpa2EnterpriseConfig; 525 } else if (mWpa3EnterpriseConfig != null) { // WPA3-SuiteB network 526 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B); 527 configuration.enterpriseConfig = mWpa3EnterpriseConfig; 528 } else if (mIsEnhancedOpen) { // OWE network 529 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE); 530 } else if (!TextUtils.isEmpty(mWapiPskPassphrase)) { // WAPI-PSK network. 531 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_PSK); 532 // WifiConfiguration.preSharedKey needs quotes around ASCII password. 533 configuration.preSharedKey = "\"" + mWapiPskPassphrase + "\""; 534 } else if (mWapiEnterpriseConfig != null) { // WAPI-CERT network 535 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT); 536 configuration.enterpriseConfig = mWapiEnterpriseConfig; 537 } else { // Open network 538 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); 539 } 540 } 541 542 /** 543 * Helper method to build WifiConfiguration object from the builder. 544 * @return Instance of {@link WifiConfiguration}. 545 */ buildWifiConfiguration()546 private WifiConfiguration buildWifiConfiguration() { 547 final WifiConfiguration wifiConfiguration = new WifiConfiguration(); 548 // WifiConfiguration.SSID needs quotes around unicode SSID. 549 wifiConfiguration.SSID = "\"" + mSsid + "\""; 550 if (mBssid != null) { 551 wifiConfiguration.BSSID = mBssid.toString(); 552 } 553 554 setSecurityParamsInWifiConfiguration(wifiConfiguration); 555 556 wifiConfiguration.hiddenSSID = mIsHiddenSSID; 557 wifiConfiguration.priority = mPriority; 558 wifiConfiguration.meteredOverride = mMeteredOverride; 559 wifiConfiguration.carrierId = mCarrierId; 560 wifiConfiguration.trusted = !mIsNetworkUntrusted; 561 return wifiConfiguration; 562 } 563 validateSecurityParams()564 private void validateSecurityParams() { 565 int numSecurityTypes = 0; 566 numSecurityTypes += mIsEnhancedOpen ? 1 : 0; 567 numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0; 568 numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0; 569 numSecurityTypes += !TextUtils.isEmpty(mWapiPskPassphrase) ? 1 : 0; 570 numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0; 571 numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0; 572 numSecurityTypes += mWapiEnterpriseConfig != null ? 1 : 0; 573 numSecurityTypes += mPasspointConfiguration != null ? 1 : 0; 574 if (numSecurityTypes > 1) { 575 throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase," 576 + " setWpa3Passphrase, setWpa2EnterpriseConfig, setWpa3EnterpriseConfig" 577 + " setWapiPassphrase, setWapiCertSuite, setIsWapiCertSuiteAuto" 578 + " or setPasspointConfig can be invoked for network suggestion"); 579 } 580 } 581 buildWifiConfigurationForPasspoint()582 private WifiConfiguration buildWifiConfigurationForPasspoint() { 583 WifiConfiguration wifiConfiguration = new WifiConfiguration(); 584 wifiConfiguration.FQDN = mPasspointConfiguration.getHomeSp().getFqdn(); 585 wifiConfiguration.setPasspointUniqueId(mPasspointConfiguration.getUniqueId()); 586 wifiConfiguration.priority = mPriority; 587 wifiConfiguration.meteredOverride = mMeteredOverride; 588 wifiConfiguration.trusted = !mIsNetworkUntrusted; 589 mPasspointConfiguration.setCarrierId(mCarrierId); 590 mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride); 591 return wifiConfiguration; 592 } 593 594 /** 595 * Create a network suggestion object for use in 596 * {@link WifiManager#addNetworkSuggestions(List)}. 597 * 598 *<p class="note"> 599 * <b>Note:</b> Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID 600 * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to 601 * the platform. 602 * </p> 603 * 604 * For example: 605 * To provide credentials for one open, one WPA2, one WPA3 network with their 606 * corresponding SSID's and one with Passpoint config: 607 * 608 * <pre>{@code 609 * final WifiNetworkSuggestion suggestion1 = 610 * new Builder() 611 * .setSsid("test111111") 612 * .build(); 613 * final WifiNetworkSuggestion suggestion2 = 614 * new Builder() 615 * .setSsid("test222222") 616 * .setWpa2Passphrase("test123456") 617 * .build(); 618 * final WifiNetworkSuggestion suggestion3 = 619 * new Builder() 620 * .setSsid("test333333") 621 * .setWpa3Passphrase("test6789") 622 * .build(); 623 * final PasspointConfiguration passpointConfig= new PasspointConfiguration(); 624 * // configure passpointConfig to include a valid Passpoint configuration 625 * final WifiNetworkSuggestion suggestion4 = 626 * new Builder() 627 * .setPasspointConfig(passpointConfig) 628 * .build(); 629 * final List<WifiNetworkSuggestion> suggestionsList = 630 * new ArrayList<WifiNetworkSuggestion> { { 631 * add(suggestion1); 632 * add(suggestion2); 633 * add(suggestion3); 634 * add(suggestion4); 635 * } }; 636 * final WifiManager wifiManager = 637 * context.getSystemService(Context.WIFI_SERVICE); 638 * wifiManager.addNetworkSuggestions(suggestionsList); 639 * // ... 640 * }</pre> 641 * 642 * @return Instance of {@link WifiNetworkSuggestion} 643 * @throws IllegalStateException on invalid params set 644 * @see WifiNetworkSuggestion 645 */ build()646 public @NonNull WifiNetworkSuggestion build() { 647 validateSecurityParams(); 648 WifiConfiguration wifiConfiguration; 649 if (mPasspointConfiguration != null) { 650 if (mSsid != null) { 651 throw new IllegalStateException("setSsid should not be invoked for suggestion " 652 + "with Passpoint configuration"); 653 } 654 if (mIsHiddenSSID) { 655 throw new IllegalStateException("setIsHiddenSsid should not be invoked for " 656 + "suggestion with Passpoint configuration"); 657 } 658 wifiConfiguration = buildWifiConfigurationForPasspoint(); 659 } else { 660 if (mSsid == null) { 661 throw new IllegalStateException("setSsid should be invoked for suggestion"); 662 } 663 if (TextUtils.isEmpty(mSsid)) { 664 throw new IllegalStateException("invalid ssid for suggestion"); 665 } 666 if (mBssid != null 667 && (mBssid.equals(MacAddress.BROADCAST_ADDRESS) 668 || mBssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS))) { 669 throw new IllegalStateException("invalid bssid for suggestion"); 670 } 671 wifiConfiguration = buildWifiConfiguration(); 672 if (wifiConfiguration.isOpenNetwork()) { 673 if (mIsSharedWithUserSet && mIsSharedWithUser) { 674 throw new IllegalStateException("Open network should not be " 675 + "setCredentialSharedWithUser to true"); 676 } 677 mIsSharedWithUser = false; 678 } 679 } 680 if (!mIsSharedWithUser && !mIsInitialAutojoinEnabled) { 681 throw new IllegalStateException("Should have not a network with both " 682 + "setCredentialSharedWithUser and " 683 + "setIsAutojoinEnabled set to false"); 684 } 685 if (mIsNetworkUntrusted) { 686 if (mIsSharedWithUserSet && mIsSharedWithUser) { 687 throw new IllegalStateException("Should not be both" 688 + "setCredentialSharedWithUser and +" 689 + "setIsNetworkAsUntrusted to true"); 690 } 691 mIsSharedWithUser = false; 692 } 693 return new WifiNetworkSuggestion( 694 wifiConfiguration, 695 mPasspointConfiguration, 696 mIsAppInteractionRequired, 697 mIsUserInteractionRequired, 698 mIsSharedWithUser, 699 mIsInitialAutojoinEnabled); 700 } 701 } 702 703 /** 704 * Network configuration for the provided network. 705 * @hide 706 */ 707 @NonNull 708 public final WifiConfiguration wifiConfiguration; 709 710 /** 711 * Passpoint configuration for the provided network. 712 * @hide 713 */ 714 @Nullable 715 public final PasspointConfiguration passpointConfiguration; 716 717 /** 718 * Whether app needs to log in to captive portal to obtain Internet access. 719 * @hide 720 */ 721 public final boolean isAppInteractionRequired; 722 723 /** 724 * Whether user needs to log in to captive portal to obtain Internet access. 725 * @hide 726 */ 727 public final boolean isUserInteractionRequired; 728 729 /** 730 * Whether app share credential with the user, allow user use provided credential to 731 * connect network manually. 732 * @hide 733 */ 734 public final boolean isUserAllowedToManuallyConnect; 735 736 /** 737 * Whether the suggestion will be initialized as auto-joined or not. 738 * @hide 739 */ 740 public final boolean isInitialAutoJoinEnabled; 741 742 /** @hide */ WifiNetworkSuggestion()743 public WifiNetworkSuggestion() { 744 this.wifiConfiguration = new WifiConfiguration(); 745 this.passpointConfiguration = null; 746 this.isAppInteractionRequired = false; 747 this.isUserInteractionRequired = false; 748 this.isUserAllowedToManuallyConnect = true; 749 this.isInitialAutoJoinEnabled = true; 750 } 751 752 /** @hide */ WifiNetworkSuggestion(@onNull WifiConfiguration networkConfiguration, @Nullable PasspointConfiguration passpointConfiguration, boolean isAppInteractionRequired, boolean isUserInteractionRequired, boolean isUserAllowedToManuallyConnect, boolean isInitialAutoJoinEnabled)753 public WifiNetworkSuggestion(@NonNull WifiConfiguration networkConfiguration, 754 @Nullable PasspointConfiguration passpointConfiguration, 755 boolean isAppInteractionRequired, 756 boolean isUserInteractionRequired, 757 boolean isUserAllowedToManuallyConnect, 758 boolean isInitialAutoJoinEnabled) { 759 checkNotNull(networkConfiguration); 760 this.wifiConfiguration = networkConfiguration; 761 this.passpointConfiguration = passpointConfiguration; 762 763 this.isAppInteractionRequired = isAppInteractionRequired; 764 this.isUserInteractionRequired = isUserInteractionRequired; 765 this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect; 766 this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled; 767 } 768 769 public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR = 770 new Creator<WifiNetworkSuggestion>() { 771 @Override 772 public WifiNetworkSuggestion createFromParcel(Parcel in) { 773 return new WifiNetworkSuggestion( 774 in.readParcelable(null), // wifiConfiguration 775 in.readParcelable(null), // PasspointConfiguration 776 in.readBoolean(), // isAppInteractionRequired 777 in.readBoolean(), // isUserInteractionRequired 778 in.readBoolean(), // isSharedCredentialWithUser 779 in.readBoolean() // isAutojoinEnabled 780 ); 781 } 782 783 @Override 784 public WifiNetworkSuggestion[] newArray(int size) { 785 return new WifiNetworkSuggestion[size]; 786 } 787 }; 788 789 @Override describeContents()790 public int describeContents() { 791 return 0; 792 } 793 794 @Override writeToParcel(Parcel dest, int flags)795 public void writeToParcel(Parcel dest, int flags) { 796 dest.writeParcelable(wifiConfiguration, flags); 797 dest.writeParcelable(passpointConfiguration, flags); 798 dest.writeBoolean(isAppInteractionRequired); 799 dest.writeBoolean(isUserInteractionRequired); 800 dest.writeBoolean(isUserAllowedToManuallyConnect); 801 dest.writeBoolean(isInitialAutoJoinEnabled); 802 } 803 804 @Override hashCode()805 public int hashCode() { 806 return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID, 807 wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey()); 808 } 809 810 /** 811 * Equals for network suggestions. 812 */ 813 @Override equals(Object obj)814 public boolean equals(Object obj) { 815 if (this == obj) { 816 return true; 817 } 818 if (!(obj instanceof WifiNetworkSuggestion)) { 819 return false; 820 } 821 WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj; 822 if (this.passpointConfiguration == null ^ lhs.passpointConfiguration == null) { 823 return false; 824 } 825 826 return TextUtils.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID) 827 && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID) 828 && Objects.equals(this.wifiConfiguration.allowedKeyManagement, 829 lhs.wifiConfiguration.allowedKeyManagement) 830 && TextUtils.equals(this.wifiConfiguration.getKey(), 831 lhs.wifiConfiguration.getKey()); 832 } 833 834 @Override toString()835 public String toString() { 836 StringBuilder sb = new StringBuilder("WifiNetworkSuggestion[ ") 837 .append("SSID=").append(wifiConfiguration.SSID) 838 .append(", BSSID=").append(wifiConfiguration.BSSID) 839 .append(", FQDN=").append(wifiConfiguration.FQDN) 840 .append(", isAppInteractionRequired=").append(isAppInteractionRequired) 841 .append(", isUserInteractionRequired=").append(isUserInteractionRequired) 842 .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect) 843 .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled) 844 .append(", isUnTrusted=").append(!wifiConfiguration.trusted) 845 .append(" ]"); 846 return sb.toString(); 847 } 848 849 /** 850 * Get the {@link WifiConfiguration} associated with this Suggestion. 851 * @hide 852 */ 853 @SystemApi 854 @NonNull getWifiConfiguration()855 public WifiConfiguration getWifiConfiguration() { 856 return wifiConfiguration; 857 } 858 859 /** 860 * Get the BSSID, or null if unset. 861 * @see Builder#setBssid(MacAddress) 862 */ 863 @Nullable getBssid()864 public MacAddress getBssid() { 865 if (wifiConfiguration.BSSID == null) { 866 return null; 867 } 868 return MacAddress.fromString(wifiConfiguration.BSSID); 869 } 870 871 /** @see Builder#setCredentialSharedWithUser(boolean) */ isCredentialSharedWithUser()872 public boolean isCredentialSharedWithUser() { 873 return isUserAllowedToManuallyConnect; 874 } 875 876 /** @see Builder#setIsAppInteractionRequired(boolean) */ isAppInteractionRequired()877 public boolean isAppInteractionRequired() { 878 return isAppInteractionRequired; 879 } 880 881 /** @see Builder#setIsEnhancedOpen(boolean) */ isEnhancedOpen()882 public boolean isEnhancedOpen() { 883 return wifiConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE); 884 } 885 886 /** @see Builder#setIsHiddenSsid(boolean) */ isHiddenSsid()887 public boolean isHiddenSsid() { 888 return wifiConfiguration.hiddenSSID; 889 } 890 891 /** @see Builder#setIsInitialAutojoinEnabled(boolean) */ isInitialAutojoinEnabled()892 public boolean isInitialAutojoinEnabled() { 893 return isInitialAutoJoinEnabled; 894 } 895 896 /** @see Builder#setIsMetered(boolean) */ isMetered()897 public boolean isMetered() { 898 return wifiConfiguration.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED; 899 } 900 901 /** @see Builder#setIsUserInteractionRequired(boolean) */ isUserInteractionRequired()902 public boolean isUserInteractionRequired() { 903 return isUserInteractionRequired; 904 } 905 906 /** 907 * Get the {@link PasspointConfiguration} associated with this Suggestion, or null if this 908 * Suggestion is not for a Passpoint network. 909 */ 910 @Nullable getPasspointConfig()911 public PasspointConfiguration getPasspointConfig() { 912 return passpointConfiguration; 913 } 914 915 /** @see Builder#setPriority(int) */ 916 @IntRange(from = 0) getPriority()917 public int getPriority() { 918 return wifiConfiguration.priority; 919 } 920 921 /** 922 * Return the SSID of the network, or null if this is a Passpoint network. 923 * @see Builder#setSsid(String) 924 */ 925 @Nullable getSsid()926 public String getSsid() { 927 if (wifiConfiguration.SSID == null) { 928 return null; 929 } 930 return WifiInfo.sanitizeSsid(wifiConfiguration.SSID); 931 } 932 933 /** @see Builder#setUntrusted(boolean) */ isUntrusted()934 public boolean isUntrusted() { 935 return !wifiConfiguration.trusted; 936 } 937 938 /** 939 * Get the WifiEnterpriseConfig, or null if unset. 940 * @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig) 941 * @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig) 942 * @see Builder#setWpa3EnterpriseConfig(WifiEnterpriseConfig) 943 */ 944 @Nullable getEnterpriseConfig()945 public WifiEnterpriseConfig getEnterpriseConfig() { 946 return wifiConfiguration.enterpriseConfig; 947 } 948 949 /** 950 * Get the passphrase, or null if unset. 951 * @see Builder#setWapiPassphrase(String) 952 * @see Builder#setWpa2Passphrase(String) 953 * @see Builder#setWpa3Passphrase(String) 954 */ 955 @Nullable getPassphrase()956 public String getPassphrase() { 957 if (wifiConfiguration.preSharedKey == null) { 958 return null; 959 } 960 return WifiInfo.removeDoubleQuotes(wifiConfiguration.preSharedKey); 961 } 962 } 963