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