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