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