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