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