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