• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_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