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 android.net.wifi.ScanResult.UNSPECIFIED; 20 21 import static com.android.internal.util.Preconditions.checkNotNull; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.net.ConnectivityManager; 26 import android.net.ConnectivityManager.NetworkCallback; 27 import android.net.MacAddress; 28 import android.net.NetworkCapabilities; 29 import android.net.NetworkRequest; 30 import android.net.NetworkSpecifier; 31 import android.net.wifi.ScanResult.WifiBand; 32 import android.os.Parcel; 33 import android.os.Parcelable; 34 import android.os.PatternMatcher; 35 import android.text.TextUtils; 36 import android.util.Pair; 37 38 import com.android.modules.utils.build.SdkLevel; 39 40 import java.nio.charset.CharsetEncoder; 41 import java.nio.charset.StandardCharsets; 42 import java.util.Arrays; 43 import java.util.Objects; 44 45 /** 46 * Network specifier object used to request a Wi-Fi network. Apps should use the 47 * {@link WifiNetworkSpecifier.Builder} class to create an instance. 48 * <p> 49 * This specifier can be used to request a local-only connection on devices that support concurrent 50 * connections (indicated via 51 * {@link WifiManager#isStaConcurrencyForLocalOnlyConnectionsSupported()} and if the initiating app 52 * targets SDK ≥ {@link android.os.Build.VERSION_CODES#S} or is a system app. These local-only 53 * connections may be brought up as a secondary concurrent connection (primary connection will be 54 * used for networks with internet connectivity available to the user and all apps). 55 * </p> 56 * <p> 57 * This specifier can also be used to listen for connected Wi-Fi networks on a particular band. 58 * Additionally, some devices may support requesting a connection to a particular band. If the 59 * device does not support such a request, it will send {@link NetworkCallback#onUnavailable()} 60 * upon request to the callback passed to 61 * {@link ConnectivityManager#requestNetwork(NetworkRequest, NetworkCallback)} or equivalent. 62 * See {@link Builder#build()} for details. 63 * </p> 64 */ 65 public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parcelable { 66 67 private static final String TAG = "WifiNetworkSpecifier"; 68 69 /** 70 * Returns the band for a given frequency in MHz. 71 * @hide 72 */ getBand(final int freqMHz)73 @WifiBand public static int getBand(final int freqMHz) { 74 if (ScanResult.is24GHz(freqMHz)) { 75 return ScanResult.WIFI_BAND_24_GHZ; 76 } else if (ScanResult.is5GHz(freqMHz)) { 77 return ScanResult.WIFI_BAND_5_GHZ; 78 } else if (ScanResult.is6GHz(freqMHz)) { 79 return ScanResult.WIFI_BAND_6_GHZ; 80 } else if (ScanResult.is60GHz(freqMHz)) { 81 return ScanResult.WIFI_BAND_60_GHZ; 82 } 83 return UNSPECIFIED; 84 } 85 86 /** 87 * Check the channel in the array is valid. 88 * @hide 89 */ validateChannelFrequencyInMhz(@onNull int[] channels)90 public static boolean validateChannelFrequencyInMhz(@NonNull int[] channels) { 91 if (channels == null) { 92 return false; 93 } 94 for (int channel : channels) { 95 if (ScanResult.convertFrequencyMhzToChannelIfSupported(channel) == UNSPECIFIED) { 96 return false; 97 } 98 } 99 return true; 100 } 101 102 /** 103 * Validates that the passed band is a valid band 104 * @param band the band to check 105 * @return true if the band is valid, false otherwise 106 * @hide 107 */ validateBand(@ifiBand int band)108 public static boolean validateBand(@WifiBand int band) { 109 switch (band) { 110 case UNSPECIFIED: 111 case ScanResult.WIFI_BAND_24_GHZ: 112 case ScanResult.WIFI_BAND_5_GHZ: 113 case ScanResult.WIFI_BAND_5_GHZ_LOW: 114 case ScanResult.WIFI_BAND_5_GHZ_HIGH: 115 case ScanResult.WIFI_BAND_6_GHZ: 116 case ScanResult.WIFI_BAND_60_GHZ: 117 return true; 118 default: 119 return false; 120 } 121 } 122 123 /** 124 * Builder used to create {@link WifiNetworkSpecifier} objects. 125 */ 126 public static final class Builder { 127 private static final String MATCH_ALL_SSID_PATTERN_PATH = ".*"; 128 private static final String MATCH_EMPTY_SSID_PATTERN_PATH = ""; 129 private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN1 = 130 new Pair<>(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS); 131 private static final Pair<MacAddress, MacAddress> MATCH_NO_BSSID_PATTERN2 = 132 new Pair<>(WifiManager.ALL_ZEROS_MAC_ADDRESS, MacAddress.BROADCAST_ADDRESS); 133 private static final Pair<MacAddress, MacAddress> MATCH_ALL_BSSID_PATTERN = 134 new Pair<>(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS); 135 private static final MacAddress MATCH_EXACT_BSSID_PATTERN_MASK = 136 MacAddress.BROADCAST_ADDRESS; 137 138 /** 139 * Set WPA Enterprise type according to certificate security level. 140 * This is for backward compatibility in R. 141 */ 142 private static final int WPA3_ENTERPRISE_AUTO = 0; 143 /** Set WPA Enterprise type to standard mode only. */ 144 private static final int WPA3_ENTERPRISE_STANDARD = 1; 145 /** Set WPA Enterprise type to 192 bit mode only. */ 146 private static final int WPA3_ENTERPRISE_192_BIT = 2; 147 148 /** 149 * SSID pattern match specified by the app. 150 */ 151 private @Nullable PatternMatcher mSsidPatternMatcher; 152 /** 153 * BSSID pattern match specified by the app. 154 * Pair of <BaseAddress, Mask>. 155 */ 156 private @Nullable Pair<MacAddress, MacAddress> mBssidPatternMatcher; 157 /** 158 * Whether this is an OWE network or not. 159 */ 160 private boolean mIsEnhancedOpen; 161 /** 162 * Pre-shared key for use with WPA-PSK networks. 163 */ 164 private @Nullable String mWpa2PskPassphrase; 165 /** 166 * Pre-shared key for use with WPA3-SAE networks. 167 */ 168 private @Nullable String mWpa3SaePassphrase; 169 /** 170 * The enterprise configuration details specifying the EAP method, 171 * certificates and other settings associated with the WPA/WPA2-Enterprise networks. 172 */ 173 private @Nullable WifiEnterpriseConfig mWpa2EnterpriseConfig; 174 /** 175 * The enterprise configuration details specifying the EAP method, 176 * certificates and other settings associated with the WPA3-Enterprise networks. 177 */ 178 private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig; 179 /** 180 * Indicate what type this WPA3-Enterprise network is. 181 */ 182 private int mWpa3EnterpriseType = WPA3_ENTERPRISE_AUTO; 183 /** 184 * This is a network that does not broadcast its SSID, so an 185 * SSID-specific probe request must be used for scans. 186 */ 187 private boolean mIsHiddenSSID; 188 /** 189 * The requested band for this connection, or BAND_UNSPECIFIED. 190 */ 191 @WifiBand private int mBand; 192 193 private int[] mChannels; 194 Builder()195 public Builder() { 196 mSsidPatternMatcher = null; 197 mBssidPatternMatcher = null; 198 mIsEnhancedOpen = false; 199 mWpa2PskPassphrase = null; 200 mWpa3SaePassphrase = null; 201 mWpa2EnterpriseConfig = null; 202 mWpa3EnterpriseConfig = null; 203 mIsHiddenSSID = false; 204 mBand = UNSPECIFIED; 205 mChannels = new int[0]; 206 } 207 208 /** 209 * Set the unicode SSID match pattern to use for filtering networks from scan results. 210 * <p> 211 * <li>Overrides any previous value set using {@link #setSsid(String)} or 212 * {@link #setSsidPattern(PatternMatcher)}.</li> 213 * 214 * @param ssidPattern Instance of {@link PatternMatcher} containing the UTF-8 encoded 215 * string pattern to use for matching the network's SSID. 216 * @return Instance of {@link Builder} to enable chaining of the builder method. 217 */ setSsidPattern(@onNull PatternMatcher ssidPattern)218 public @NonNull Builder setSsidPattern(@NonNull PatternMatcher ssidPattern) { 219 checkNotNull(ssidPattern); 220 mSsidPatternMatcher = ssidPattern; 221 return this; 222 } 223 224 /** 225 * Set the unicode SSID for the network. 226 * <p> 227 * <li>Sets the SSID to use for filtering networks from scan results. Will only match 228 * networks whose SSID is identical to the UTF-8 encoding of the specified value.</li> 229 * <li>Overrides any previous value set using {@link #setSsid(String)} or 230 * {@link #setSsidPattern(PatternMatcher)}.</li> 231 * 232 * @param ssid The SSID of the network. It must be valid Unicode. 233 * @return Instance of {@link Builder} to enable chaining of the builder method. 234 * @throws IllegalArgumentException if the SSID is not valid unicode. 235 */ setSsid(@onNull String ssid)236 public @NonNull Builder setSsid(@NonNull String ssid) { 237 checkNotNull(ssid); 238 final CharsetEncoder unicodeEncoder = StandardCharsets.UTF_8.newEncoder(); 239 if (!unicodeEncoder.canEncode(ssid)) { 240 throw new IllegalArgumentException("SSID is not a valid unicode string"); 241 } 242 mSsidPatternMatcher = new PatternMatcher(ssid, PatternMatcher.PATTERN_LITERAL); 243 return this; 244 } 245 246 /** 247 * Set the BSSID match pattern to use for filtering networks from scan results. 248 * Will match all networks with BSSID which satisfies the following: 249 * {@code BSSID & mask == baseAddress}. 250 * <p> 251 * <li>Overrides any previous value set using {@link #setBssid(MacAddress)} or 252 * {@link #setBssidPattern(MacAddress, MacAddress)}.</li> 253 * 254 * @param baseAddress Base address for BSSID pattern. 255 * @param mask Mask for BSSID pattern. 256 * @return Instance of {@link Builder} to enable chaining of the builder method. 257 */ setBssidPattern( @onNull MacAddress baseAddress, @NonNull MacAddress mask)258 public @NonNull Builder setBssidPattern( 259 @NonNull MacAddress baseAddress, @NonNull MacAddress mask) { 260 checkNotNull(baseAddress); 261 checkNotNull(mask); 262 mBssidPatternMatcher = Pair.create(baseAddress, mask); 263 return this; 264 } 265 266 /** 267 * Set the BSSID to use for filtering networks from scan results. Will only match network 268 * whose BSSID is identical to the specified value. 269 * <p> 270 * <li>Sets the BSSID to use for filtering networks from scan results. Will only match 271 * networks whose BSSID is identical to specified value.</li> 272 * <li>Overrides any previous value set using {@link #setBssid(MacAddress)} or 273 * {@link #setBssidPattern(MacAddress, MacAddress)}.</li> 274 * 275 * @param bssid BSSID of the network. 276 * @return Instance of {@link Builder} to enable chaining of the builder method. 277 */ setBssid(@onNull MacAddress bssid)278 public @NonNull Builder setBssid(@NonNull MacAddress bssid) { 279 checkNotNull(bssid); 280 mBssidPatternMatcher = Pair.create(bssid, MATCH_EXACT_BSSID_PATTERN_MASK); 281 return this; 282 } 283 284 /** 285 * Specifies whether this represents an Enhanced Open (OWE) network. 286 * 287 * @param isEnhancedOpen {@code true} to indicate that the network uses enhanced open, 288 * {@code false} otherwise. 289 * @return Instance of {@link Builder} to enable chaining of the builder method. 290 */ setIsEnhancedOpen(boolean isEnhancedOpen)291 public @NonNull Builder setIsEnhancedOpen(boolean isEnhancedOpen) { 292 mIsEnhancedOpen = isEnhancedOpen; 293 return this; 294 } 295 296 /** 297 * Set the ASCII WPA2 passphrase for this network. Needed for authenticating to 298 * WPA2-PSK networks. 299 * 300 * @param passphrase passphrase of the network. 301 * @return Instance of {@link Builder} to enable chaining of the builder method. 302 * @throws IllegalArgumentException if the passphrase is not ASCII encodable. 303 */ setWpa2Passphrase(@onNull String passphrase)304 public @NonNull Builder setWpa2Passphrase(@NonNull String passphrase) { 305 checkNotNull(passphrase); 306 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); 307 if (!asciiEncoder.canEncode(passphrase)) { 308 throw new IllegalArgumentException("passphrase not ASCII encodable"); 309 } 310 mWpa2PskPassphrase = passphrase; 311 return this; 312 } 313 314 /** 315 * Set the ASCII WPA3 passphrase for this network. Needed for authenticating to WPA3-SAE 316 * networks. 317 * 318 * @param passphrase passphrase of the network. 319 * @return Instance of {@link Builder} to enable chaining of the builder method. 320 * @throws IllegalArgumentException if the passphrase is not ASCII encodable. 321 */ setWpa3Passphrase(@onNull String passphrase)322 public @NonNull Builder setWpa3Passphrase(@NonNull String passphrase) { 323 checkNotNull(passphrase); 324 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); 325 if (!asciiEncoder.canEncode(passphrase)) { 326 throw new IllegalArgumentException("passphrase not ASCII encodable"); 327 } 328 mWpa3SaePassphrase = passphrase; 329 return this; 330 } 331 332 /** 333 * Set the associated enterprise configuration for this network. Needed for authenticating 334 * to WPA2-EAP networks. See {@link WifiEnterpriseConfig} for description. Local-only 335 * connection will not support Trust On First Use (TOFU). If TOFU is enabled on this 336 * Enterprise Config, framework will reject the connection. See {@link 337 * WifiEnterpriseConfig#enableTrustOnFirstUse} 338 * 339 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 340 * @return Instance of {@link Builder} to enable chaining of the builder method. 341 */ setWpa2EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)342 public @NonNull Builder setWpa2EnterpriseConfig( 343 @NonNull WifiEnterpriseConfig enterpriseConfig) { 344 checkNotNull(enterpriseConfig); 345 mWpa2EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 346 return this; 347 } 348 349 /** 350 * Set the associated enterprise configuration for this network. Needed for authenticating 351 * to WPA3-Enterprise networks (standard and 192-bit security). See 352 * {@link WifiEnterpriseConfig} for description. For 192-bit security networks, both the 353 * client and CA certificates must be provided, and must be of type of either 354 * sha384WithRSAEncryption (OID 1.2.840.113549.1.1.12) or ecdsa-with-SHA384 355 * (OID 1.2.840.10045.4.3.3). 356 * 357 * @deprecated use {@link #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig)} or 358 * {@link #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} to specify 359 * WPA3-Enterprise type explicitly. 360 * 361 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 362 * @return Instance of {@link Builder} to enable chaining of the builder method. 363 */ 364 @Deprecated setWpa3EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)365 public @NonNull Builder setWpa3EnterpriseConfig( 366 @NonNull WifiEnterpriseConfig enterpriseConfig) { 367 checkNotNull(enterpriseConfig); 368 mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 369 return this; 370 } 371 372 /** 373 * Set the associated enterprise configuration for this network. Needed for authenticating 374 * to standard WPA3-Enterprise networks. See {@link WifiEnterpriseConfig} for description. 375 * For WPA3-Enterprise in 192-bit security mode networks, see {@link 376 * #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} for description. Local-only 377 * connection will not support Trust On First Use (TOFU). If TOFU is enabled on this 378 * Enterprise Config, framework will reject the connection. See {@link 379 * WifiEnterpriseConfig#enableTrustOnFirstUse} 380 * 381 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 382 * @return Instance of {@link Builder} to enable chaining of the builder method. 383 */ setWpa3EnterpriseStandardModeConfig( @onNull WifiEnterpriseConfig enterpriseConfig)384 public @NonNull Builder setWpa3EnterpriseStandardModeConfig( 385 @NonNull WifiEnterpriseConfig enterpriseConfig) { 386 checkNotNull(enterpriseConfig); 387 mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 388 mWpa3EnterpriseType = WPA3_ENTERPRISE_STANDARD; 389 return this; 390 } 391 392 /** 393 * Set the associated enterprise configuration for this network. Needed for authenticating 394 * to WPA3-Enterprise in 192-bit security mode networks. See {@link WifiEnterpriseConfig} 395 * for description. Both the client and CA certificates must be provided, and must be of 396 * type of either sha384WithRSAEncryption with key length of 3072bit or more (OID 397 * 1.2.840.113549.1.1.12), or ecdsa-with-SHA384 with key length of 384bit or more (OID 398 * 1.2.840.10045.4.3.3). Local-only connection will not support Trust On First Use (TOFU). 399 * If TOFU is enabled on this Enterprise Config, framework will reject the connection. See 400 * {@link WifiEnterpriseConfig#enableTrustOnFirstUse} 401 * 402 * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. 403 * @return Instance of {@link Builder} to enable chaining of the builder method. 404 * @throws IllegalArgumentException if the EAP type or certificates do not meet 192-bit mode 405 * requirements. 406 */ setWpa3Enterprise192BitModeConfig( @onNull WifiEnterpriseConfig enterpriseConfig)407 public @NonNull Builder setWpa3Enterprise192BitModeConfig( 408 @NonNull WifiEnterpriseConfig enterpriseConfig) { 409 checkNotNull(enterpriseConfig); 410 if (enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.TLS) { 411 throw new IllegalArgumentException("The 192-bit mode network type must be TLS"); 412 } 413 if (!WifiEnterpriseConfig.isSuiteBCipherCert( 414 enterpriseConfig.getClientCertificate())) { 415 throw new IllegalArgumentException( 416 "The client certificate does not meet 192-bit mode requirements."); 417 } 418 if (!WifiEnterpriseConfig.isSuiteBCipherCert( 419 enterpriseConfig.getCaCertificate())) { 420 throw new IllegalArgumentException( 421 "The CA certificate does not meet 192-bit mode requirements."); 422 } 423 424 mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); 425 mWpa3EnterpriseType = WPA3_ENTERPRISE_192_BIT; 426 return this; 427 } 428 429 /** 430 * Specifies whether this represents a hidden network. 431 * <p> 432 * <li>Setting this disallows the usage of {@link #setSsidPattern(PatternMatcher)} since 433 * hidden networks need to be explicitly probed for.</li> 434 * <li>If not set, defaults to false (i.e not a hidden network).</li> 435 * 436 * @param isHiddenSsid {@code true} to indicate that the network is hidden, {@code false} 437 * otherwise. 438 * @return Instance of {@link Builder} to enable chaining of the builder method. 439 */ setIsHiddenSsid(boolean isHiddenSsid)440 public @NonNull Builder setIsHiddenSsid(boolean isHiddenSsid) { 441 mIsHiddenSSID = isHiddenSsid; 442 return this; 443 } 444 445 /** 446 * Specifies the band requested for this network. 447 * 448 * Only a single band can be requested. An app can file multiple callbacks concurrently 449 * if they need to know about multiple bands. 450 * 451 * @param band The requested band. 452 * @return Instance of {@link Builder} to enable chaining of the builder method. 453 */ setBand(@ifiBand int band)454 public @NonNull Builder setBand(@WifiBand int band) { 455 if (!validateBand(band)) { 456 throw new IllegalArgumentException("Unexpected band in setBand : " + band); 457 } 458 mBand = band; 459 return this; 460 } 461 462 /** 463 * Specifies the preferred channels for this network. The channels set in the request will 464 * be used to optimize the scan and connection. 465 * <p> 466 * <li>Should only be set to request local-only network</li> 467 * <li>If not set, defaults to an empty array and device will do a full band scan.</li> 468 * 469 * @param channelFreqs an Array of the channels in MHz. The length of the array must not 470 * exceed {@link WifiManager#getMaxNumberOfChannelsPerNetworkSpecifierRequest()} 471 * 472 * @return Instance of {@link Builder} to enable chaining of the builder method. 473 */ setPreferredChannelsFrequenciesMhz(@onNull int[] channelFreqs)474 @NonNull public Builder setPreferredChannelsFrequenciesMhz(@NonNull int[] channelFreqs) { 475 Objects.requireNonNull(channelFreqs); 476 if (!validateChannelFrequencyInMhz(channelFreqs)) { 477 throw new IllegalArgumentException("Invalid channel frequency in the input array"); 478 } 479 mChannels = channelFreqs.clone(); 480 return this; 481 } 482 setSecurityParamsInWifiConfiguration( @onNull WifiConfiguration configuration)483 private void setSecurityParamsInWifiConfiguration( 484 @NonNull WifiConfiguration configuration) { 485 if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network. 486 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK); 487 // WifiConfiguration.preSharedKey needs quotes around ASCII password. 488 configuration.preSharedKey = "\"" + mWpa2PskPassphrase + "\""; 489 } else if (!TextUtils.isEmpty(mWpa3SaePassphrase)) { // WPA3-SAE network. 490 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE); 491 // WifiConfiguration.preSharedKey needs quotes around ASCII password. 492 configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\""; 493 } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network 494 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP); 495 configuration.enterpriseConfig = mWpa2EnterpriseConfig; 496 } else if (mWpa3EnterpriseConfig != null) { // WPA3-Enterprise 497 if (mWpa3EnterpriseType == WPA3_ENTERPRISE_AUTO 498 && mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS 499 && WifiEnterpriseConfig.isSuiteBCipherCert( 500 mWpa3EnterpriseConfig.getClientCertificate()) 501 && WifiEnterpriseConfig.isSuiteBCipherCert( 502 mWpa3EnterpriseConfig.getCaCertificate())) { 503 // WPA3-Enterprise in 192-bit security mode 504 configuration.setSecurityParams( 505 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT); 506 } else if (mWpa3EnterpriseType == WPA3_ENTERPRISE_192_BIT) { 507 // WPA3-Enterprise in 192-bit security mode 508 configuration.setSecurityParams( 509 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT); 510 } else { 511 // WPA3-Enterprise 512 configuration.setSecurityParams( 513 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE); 514 } 515 configuration.enterpriseConfig = mWpa3EnterpriseConfig; 516 } else if (mIsEnhancedOpen) { // OWE network 517 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE); 518 } else { // Open network 519 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); 520 } 521 } 522 523 /** 524 * Helper method to build WifiConfiguration object from the builder. 525 * @return Instance of {@link WifiConfiguration}. 526 */ buildWifiConfiguration()527 private WifiConfiguration buildWifiConfiguration() { 528 final WifiConfiguration wifiConfiguration = new WifiConfiguration(); 529 // WifiConfiguration.SSID needs quotes around unicode SSID. 530 if (mSsidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL) { 531 wifiConfiguration.SSID = "\"" + mSsidPatternMatcher.getPath() + "\""; 532 } 533 if (mBssidPatternMatcher.second == MATCH_EXACT_BSSID_PATTERN_MASK) { 534 wifiConfiguration.BSSID = mBssidPatternMatcher.first.toString(); 535 } 536 setSecurityParamsInWifiConfiguration(wifiConfiguration); 537 wifiConfiguration.hiddenSSID = mIsHiddenSSID; 538 return wifiConfiguration; 539 } 540 hasSetAnyPattern()541 private boolean hasSetAnyPattern() { 542 return mSsidPatternMatcher != null || mBssidPatternMatcher != null; 543 } 544 setMatchAnyPatternIfUnset()545 private void setMatchAnyPatternIfUnset() { 546 if (mSsidPatternMatcher == null) { 547 mSsidPatternMatcher = new PatternMatcher(MATCH_ALL_SSID_PATTERN_PATH, 548 PatternMatcher.PATTERN_SIMPLE_GLOB); 549 } 550 if (mBssidPatternMatcher == null) { 551 mBssidPatternMatcher = MATCH_ALL_BSSID_PATTERN; 552 } 553 } 554 hasSetMatchNonePattern()555 private boolean hasSetMatchNonePattern() { 556 if (mSsidPatternMatcher.getType() != PatternMatcher.PATTERN_PREFIX 557 && mSsidPatternMatcher.getPath().equals(MATCH_EMPTY_SSID_PATTERN_PATH)) { 558 return true; 559 } 560 if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN1)) { 561 return true; 562 } 563 if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN2)) { 564 return true; 565 } 566 return false; 567 } 568 hasSetMatchAllPattern()569 private boolean hasSetMatchAllPattern() { 570 if ((mSsidPatternMatcher.match(MATCH_EMPTY_SSID_PATTERN_PATH)) 571 && mBssidPatternMatcher.equals(MATCH_ALL_BSSID_PATTERN)) { 572 return true; 573 } 574 return false; 575 } 576 validateSecurityParams()577 private void validateSecurityParams() { 578 int numSecurityTypes = 0; 579 numSecurityTypes += mIsEnhancedOpen ? 1 : 0; 580 numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0; 581 numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0; 582 numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0; 583 numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0; 584 if (numSecurityTypes > 1) { 585 throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase," 586 + "setWpa3Passphrase, setWpa2EnterpriseConfig or setWpa3EnterpriseConfig" 587 + " can be invoked for network specifier"); 588 } 589 } 590 591 /** 592 * Create a specifier object used to request a Wi-Fi network. The generated 593 * {@link NetworkSpecifier} should be used in 594 * {@link NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} when building 595 * the {@link NetworkRequest}. 596 * 597 *<p> 598 * When using with {@link ConnectivityManager#requestNetwork(NetworkRequest, 599 * NetworkCallback)} or variants, note that some devices may not support requesting a 600 * network with all combinations of specifier members. For example, some devices may only 601 * support requesting local-only networks (networks without the 602 * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability), or not support 603 * requesting a particular band. However, there are no restrictions when using 604 * {@link ConnectivityManager#registerNetworkCallback(NetworkRequest, NetworkCallback)} 605 * or other similar methods which monitor but do not request networks. 606 * 607 * If the device can't support a request, the app will receive a call to 608 * {@link NetworkCallback#onUnavailable()}. 609 *</p> 610 * 611 *<p> 612 * When requesting a local-only network, apps can set a combination of network match params: 613 * <li> SSID Pattern using {@link #setSsidPattern(PatternMatcher)} OR Specific SSID using 614 * {@link #setSsid(String)}. </li> 615 * AND/OR 616 * <li> BSSID Pattern using {@link #setBssidPattern(MacAddress, MacAddress)} OR Specific 617 * BSSID using {@link #setBssid(MacAddress)} </li> 618 * to trigger connection to a network that matches the set params. 619 * The system will find the set of networks matching the request and present the user 620 * with a system dialog which will allow the user to select a specific Wi-Fi network to 621 * connect to or to deny the request. 622 * 623 * To protect user privacy, some limitations to the ability of matching patterns apply. 624 * In particular, when the system brings up a network to satisfy a {@link NetworkRequest} 625 * from some app, the system reserves the right to decline matching the SSID pattern to 626 * the real SSID of the network for other apps than the app that requested the network, and 627 * not send those callbacks even if the SSID matches the requested pattern. 628 *</p> 629 * 630 * For example: 631 * To connect to an open network with a SSID prefix of "test" and a BSSID OUI of "10:03:23": 632 * 633 * <pre>{@code 634 * final NetworkSpecifier specifier = 635 * new Builder() 636 * .setSsidPattern(new PatternMatcher("test", PatternMatcher.PATTERN_PREFIX)) 637 * .setBssidPattern(MacAddress.fromString("10:03:23:00:00:00"), 638 * MacAddress.fromString("ff:ff:ff:00:00:00")) 639 * .build() 640 * final NetworkRequest request = 641 * new NetworkRequest.Builder() 642 * .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) 643 * .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) 644 * .setNetworkSpecifier(specifier) 645 * .build(); 646 * final ConnectivityManager connectivityManager = 647 * context.getSystemService(Context.CONNECTIVITY_SERVICE); 648 * final NetworkCallback networkCallback = new NetworkCallback() { 649 * ... 650 * {@literal @}Override 651 * void onAvailable(...) {} 652 * // etc. 653 * }; 654 * connectivityManager.requestNetwork(request, networkCallback); 655 * }</pre> 656 * 657 * @return Instance of {@link NetworkSpecifier}. 658 * @throws IllegalStateException on invalid params set. 659 */ build()660 public @NonNull WifiNetworkSpecifier build() { 661 if (!hasSetAnyPattern() && mBand == UNSPECIFIED) { 662 throw new IllegalStateException("one of setSsidPattern/setSsid/setBssidPattern/" 663 + "setBssid/setBand should be invoked for specifier"); 664 } 665 setMatchAnyPatternIfUnset(); 666 if (hasSetMatchNonePattern()) { 667 throw new IllegalStateException("cannot set match-none pattern for specifier"); 668 } 669 if (hasSetMatchAllPattern() && mBand == UNSPECIFIED) { 670 throw new IllegalStateException("cannot set match-all pattern for specifier"); 671 } 672 if (mIsHiddenSSID && mSsidPatternMatcher.getType() != PatternMatcher.PATTERN_LITERAL) { 673 throw new IllegalStateException("setSsid should also be invoked when " 674 + "setIsHiddenSsid is invoked for network specifier"); 675 } 676 if (mChannels.length != 0 && mBand != UNSPECIFIED) { 677 throw new IllegalStateException("cannot setPreferredChannelsFrequencyInMhz with " 678 + "setBand together"); 679 } 680 validateSecurityParams(); 681 682 return new WifiNetworkSpecifier( 683 mSsidPatternMatcher, 684 mBssidPatternMatcher, 685 mBand, 686 buildWifiConfiguration(), 687 mChannels); 688 } 689 } 690 691 /** 692 * SSID pattern match specified by the app. 693 * @hide 694 */ 695 public final PatternMatcher ssidPatternMatcher; 696 697 /** 698 * BSSID pattern match specified by the app. 699 * Pair of <BaseAddress, Mask>. 700 * @hide 701 */ 702 public final Pair<MacAddress, MacAddress> bssidPatternMatcher; 703 704 /** 705 * The band for this Wi-Fi network. 706 */ 707 @WifiBand private final int mBand; 708 709 private final int[] mChannelFreqs; 710 711 /** 712 * Security credentials for the network. 713 * <p> 714 * Note: {@link WifiConfiguration#SSID} & {@link WifiConfiguration#BSSID} fields from 715 * WifiConfiguration are not used. Instead we use the {@link #ssidPatternMatcher} & 716 * {@link #bssidPatternMatcher} fields embedded directly 717 * within {@link WifiNetworkSpecifier}. 718 * @hide 719 */ 720 public final WifiConfiguration wifiConfiguration; 721 722 /** @hide */ WifiNetworkSpecifier()723 public WifiNetworkSpecifier() throws IllegalAccessException { 724 throw new IllegalAccessException("Use the builder to create an instance"); 725 } 726 727 /** @hide */ WifiNetworkSpecifier(@onNull PatternMatcher ssidPatternMatcher, @NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher, @WifiBand int band, @NonNull WifiConfiguration wifiConfiguration, @NonNull int[] channelFreqs)728 public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher, 729 @NonNull Pair<MacAddress, MacAddress> bssidPatternMatcher, 730 @WifiBand int band, 731 @NonNull WifiConfiguration wifiConfiguration, 732 @NonNull int[] channelFreqs) { 733 checkNotNull(ssidPatternMatcher); 734 checkNotNull(bssidPatternMatcher); 735 checkNotNull(wifiConfiguration); 736 737 this.ssidPatternMatcher = ssidPatternMatcher; 738 this.bssidPatternMatcher = bssidPatternMatcher; 739 this.mBand = band; 740 this.wifiConfiguration = wifiConfiguration; 741 this.mChannelFreqs = channelFreqs; 742 } 743 744 /** 745 * The band for this Wi-Fi network specifier. 746 */ getBand()747 @WifiBand public int getBand() { 748 return mBand; 749 } 750 751 /** 752 * The preferred channels fot this network specifier. 753 * @see Builder#setPreferredChannelsFrequenciesMhz(int[]) 754 */ getPreferredChannelFrequenciesMhz()755 @NonNull public int[] getPreferredChannelFrequenciesMhz() { 756 return mChannelFreqs.clone(); 757 } 758 759 public static final @NonNull Creator<WifiNetworkSpecifier> CREATOR = 760 new Creator<WifiNetworkSpecifier>() { 761 @Override 762 public WifiNetworkSpecifier createFromParcel(Parcel in) { 763 PatternMatcher ssidPatternMatcher = in.readParcelable(/* classLoader */null); 764 MacAddress baseAddress = in.readParcelable(null); 765 MacAddress mask = in.readParcelable(null); 766 Pair<MacAddress, MacAddress> bssidPatternMatcher = 767 Pair.create(baseAddress, mask); 768 int band = in.readInt(); 769 WifiConfiguration wifiConfiguration = in.readParcelable(null); 770 int[] mChannels = in.createIntArray(); 771 return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher, band, 772 wifiConfiguration, mChannels); 773 } 774 775 @Override 776 public WifiNetworkSpecifier[] newArray(int size) { 777 return new WifiNetworkSpecifier[size]; 778 } 779 }; 780 781 @Override describeContents()782 public int describeContents() { 783 return 0; 784 } 785 786 @Override writeToParcel(Parcel dest, int flags)787 public void writeToParcel(Parcel dest, int flags) { 788 dest.writeParcelable(ssidPatternMatcher, flags); 789 dest.writeParcelable(bssidPatternMatcher.first, flags); 790 dest.writeParcelable(bssidPatternMatcher.second, flags); 791 dest.writeInt(mBand); 792 dest.writeParcelable(wifiConfiguration, flags); 793 dest.writeIntArray(mChannelFreqs); 794 } 795 796 @Override hashCode()797 public int hashCode() { 798 return Objects.hash( 799 ssidPatternMatcher.getPath(), ssidPatternMatcher.getType(), bssidPatternMatcher, 800 mBand, wifiConfiguration.allowedKeyManagement, Arrays.hashCode(mChannelFreqs)); 801 } 802 803 @Override equals(Object obj)804 public boolean equals(Object obj) { 805 if (this == obj) { 806 return true; 807 } 808 if (!(obj instanceof WifiNetworkSpecifier)) { 809 return false; 810 } 811 WifiNetworkSpecifier lhs = (WifiNetworkSpecifier) obj; 812 return Objects.equals(this.ssidPatternMatcher.getPath(), 813 lhs.ssidPatternMatcher.getPath()) 814 && Objects.equals(this.ssidPatternMatcher.getType(), 815 lhs.ssidPatternMatcher.getType()) 816 && Objects.equals(this.bssidPatternMatcher, 817 lhs.bssidPatternMatcher) 818 && this.mBand == lhs.mBand 819 && Objects.equals(this.wifiConfiguration.allowedKeyManagement, 820 lhs.wifiConfiguration.allowedKeyManagement) 821 && Arrays.equals(mChannelFreqs, lhs.mChannelFreqs); 822 } 823 824 @Override toString()825 public String toString() { 826 return new StringBuilder() 827 .append("WifiNetworkSpecifier [") 828 .append(", SSID Match pattern=").append(ssidPatternMatcher) 829 .append(", BSSID Match pattern=").append(bssidPatternMatcher) 830 .append(", SSID=").append(wifiConfiguration.SSID) 831 .append(", BSSID=").append(wifiConfiguration.BSSID) 832 .append(", band=").append(mBand) 833 .append("]") 834 .toString(); 835 } 836 837 /** @hide */ 838 @Override canBeSatisfiedBy(NetworkSpecifier other)839 public boolean canBeSatisfiedBy(NetworkSpecifier other) { 840 if (other instanceof WifiNetworkAgentSpecifier) { 841 return ((WifiNetworkAgentSpecifier) other).satisfiesNetworkSpecifier(this); 842 } 843 // Specific requests are checked for equality although testing for equality of 2 patterns do 844 // not make much sense! 845 return equals(other); 846 } 847 848 /** @hide */ 849 @Override 850 @Nullable redact()851 public NetworkSpecifier redact() { 852 if (!SdkLevel.isAtLeastS()) return this; 853 854 return new Builder().setBand(mBand).build(); 855 } 856 } 857