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.IntDef;
22 import android.annotation.IntRange;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.annotation.SystemApi;
27 import android.net.MacAddress;
28 import android.net.NetworkCapabilities;
29 import android.net.NetworkRequest;
30 import android.net.wifi.hotspot2.PasspointConfiguration;
31 import android.os.Build;
32 import android.os.Parcel;
33 import android.os.Parcelable;
34 import android.telephony.SubscriptionInfo;
35 import android.telephony.SubscriptionManager;
36 import android.telephony.TelephonyManager;
37 import android.text.TextUtils;
38 
39 import androidx.annotation.RequiresApi;
40 
41 import com.android.modules.utils.build.SdkLevel;
42 
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 import java.nio.charset.CharsetEncoder;
46 import java.nio.charset.StandardCharsets;
47 import java.util.List;
48 import java.util.Objects;
49 
50 /**
51  * The Network Suggestion object is used to provide a Wi-Fi network for consideration when
52  * auto-connecting to networks. Apps cannot directly create this object, they must use
53  * {@link WifiNetworkSuggestion.Builder#build()} to obtain an instance of this object.
54  *<p>
55  * Apps can provide a list of such networks to the platform using
56  * {@link WifiManager#addNetworkSuggestions(List)}.
57  */
58 public final class WifiNetworkSuggestion implements Parcelable {
59     /** @hide */
60     @Retention(RetentionPolicy.SOURCE)
61     @IntDef(prefix = {"RANDOMIZATION_"}, value = {
62             RANDOMIZATION_PERSISTENT,
63             RANDOMIZATION_NON_PERSISTENT})
64     public @interface MacRandomizationSetting {}
65     /**
66      * Generate a randomized MAC from a secret seed and information from the Wi-Fi configuration
67      * (SSID or Passpoint profile) and reuse it for all connections to this network. The
68      * randomized MAC address for this network will stay the same for each subsequent association
69      * until the device undergoes factory reset.
70      */
71     public static final int RANDOMIZATION_PERSISTENT = 0;
72     /**
73      * With this option, the randomized MAC address will periodically get re-randomized, and
74      * the randomized MAC address will change if the suggestion is removed and then added back.
75      */
76     public static final int RANDOMIZATION_NON_PERSISTENT = 1;
77     /**
78      * Builder used to create {@link WifiNetworkSuggestion} objects.
79      */
80     public static final class Builder {
81         private static final int UNASSIGNED_PRIORITY = -1;
82 
83         /**
84          * Set WPA Enterprise type according to certificate security level.
85          * This is for backward compatibility in R.
86          */
87         private static final int WPA3_ENTERPRISE_AUTO = 0;
88         /** Set WPA Enterprise type to standard mode only. */
89         private static final int WPA3_ENTERPRISE_STANDARD = 1;
90         /** Set WPA Enterprise type to 192 bit mode only. */
91         private static final int WPA3_ENTERPRISE_192_BIT = 2;
92 
93         /**
94          * SSID of the network.
95          */
96         private String mSsid;
97         /**
98          * Optional BSSID within the network.
99          */
100         private MacAddress mBssid;
101         /**
102          * Whether this is an OWE network or not.
103          */
104         private boolean mIsEnhancedOpen;
105         /**
106          * Pre-shared key for use with WPA-PSK networks.
107          */
108         private @Nullable String mWpa2PskPassphrase;
109         /**
110          * Pre-shared key for use with WPA3-SAE networks.
111          */
112         private @Nullable String mWpa3SaePassphrase;
113         /**
114          * The enterprise configuration details specifying the EAP method,
115          * certificates and other settings associated with the WPA/WPA2-Enterprise networks.
116          */
117         private @Nullable WifiEnterpriseConfig mWpa2EnterpriseConfig;
118         /**
119          * The enterprise configuration details specifying the EAP method,
120          * certificates and other settings associated with the WPA3-Enterprise networks.
121          */
122         private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig;
123         /**
124          * Indicate what type this WPA3-Enterprise network is.
125          */
126         private int mWpa3EnterpriseType = WPA3_ENTERPRISE_AUTO;
127         /**
128          * The passpoint config for use with Hotspot 2.0 network
129          */
130         private @Nullable PasspointConfiguration mPasspointConfiguration;
131         /**
132          * This is a network that does not broadcast its SSID, so an
133          * SSID-specific probe request must be used for scans.
134          */
135         private boolean mIsHiddenSSID;
136         /**
137          * Whether app needs to log in to captive portal to obtain Internet access.
138          */
139         private boolean mIsAppInteractionRequired;
140         /**
141          * Whether user needs to log in to captive portal to obtain Internet access.
142          */
143         private boolean mIsUserInteractionRequired;
144         /**
145          * Whether this network is metered or not.
146          */
147         private int mMeteredOverride;
148         /**
149          * Priority of this network among other network suggestions from same priority group
150          * provided by the app.
151          * The higher the number, the higher the priority (i.e value of 0 = lowest priority).
152          */
153         private int mPriority;
154         /**
155          * Priority group ID, while suggestion priority will only effect inside the priority group.
156          */
157         private int mPriorityGroup;
158 
159         /**
160          * The carrier ID identifies the operator who provides this network configuration.
161          *    see {@link TelephonyManager#getSimCarrierId()}
162          */
163         private int mCarrierId;
164 
165         /**
166          * The Subscription ID identifies the SIM card for which this network configuration is
167          * valid.
168          */
169         private int mSubscriptionId;
170 
171         /**
172          * Whether this network is shared credential with user to allow user manually connect.
173          */
174         private boolean mIsSharedWithUser;
175 
176         /**
177          * Whether the setCredentialSharedWithUser have been called.
178          */
179         private boolean mIsSharedWithUserSet;
180 
181         /**
182          * Whether this network is initialized with auto-join enabled (the default) or not.
183          */
184         private boolean mIsInitialAutojoinEnabled;
185 
186         /**
187          * Pre-shared key for use with WAPI-PSK networks.
188          */
189         private @Nullable String mWapiPskPassphrase;
190 
191         /**
192          * The enterprise configuration details specifying the EAP method,
193          * certificates and other settings associated with the WAPI networks.
194          */
195         private @Nullable WifiEnterpriseConfig mWapiEnterpriseConfig;
196 
197         /**
198          * Whether this network will be brought up as untrusted (TRUSTED capability bit removed).
199          */
200         private boolean mIsNetworkUntrusted;
201 
202         /**
203          * Whether this network will be brought up as OEM paid (OEM_PAID capability bit added).
204          */
205         private boolean mIsNetworkOemPaid;
206 
207         /**
208          * Whether this network will be brought up as OEM private (OEM_PRIVATE capability bit
209          * added).
210          */
211         private boolean mIsNetworkOemPrivate;
212 
213         /**
214          * Whether this network is a carrier merged network.
215          */
216         private boolean mIsCarrierMerged;
217 
218         /**
219          * The MAC randomization strategy.
220          */
221         @MacRandomizationSetting
222         private int mMacRandomizationSetting;
223 
224         /**
225          * The SAE Hash-to-Element only mode.
226          */
227         private boolean mSaeH2eOnlyMode;
228 
Builder()229         public Builder() {
230             mSsid = null;
231             mBssid =  null;
232             mIsEnhancedOpen = false;
233             mWpa2PskPassphrase = null;
234             mWpa3SaePassphrase = null;
235             mWpa2EnterpriseConfig = null;
236             mWpa3EnterpriseConfig = null;
237             mPasspointConfiguration = null;
238             mIsHiddenSSID = false;
239             mIsAppInteractionRequired = false;
240             mIsUserInteractionRequired = false;
241             mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NONE;
242             mIsSharedWithUser = true;
243             mIsSharedWithUserSet = false;
244             mIsInitialAutojoinEnabled = true;
245             mPriority = UNASSIGNED_PRIORITY;
246             mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
247             mWapiPskPassphrase = null;
248             mWapiEnterpriseConfig = null;
249             mIsNetworkUntrusted = false;
250             mIsNetworkOemPaid = false;
251             mIsNetworkOemPrivate = false;
252             mIsCarrierMerged = false;
253             mPriorityGroup = 0;
254             mMacRandomizationSetting = RANDOMIZATION_PERSISTENT;
255             mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
256             mSaeH2eOnlyMode = false;
257         }
258 
259         /**
260          * Set the unicode SSID for the network.
261          * <p>
262          * <li>Overrides any previous value set using {@link #setSsid(String)}.</li>
263          *
264          * @param ssid The SSID of the network. It must be valid Unicode.
265          * @return Instance of {@link Builder} to enable chaining of the builder method.
266          * @throws IllegalArgumentException if the SSID is not valid unicode.
267          */
setSsid(@onNull String ssid)268         public @NonNull Builder setSsid(@NonNull String ssid) {
269             checkNotNull(ssid);
270             final CharsetEncoder unicodeEncoder = StandardCharsets.UTF_8.newEncoder();
271             if (!unicodeEncoder.canEncode(ssid)) {
272                 throw new IllegalArgumentException("SSID is not a valid unicode string");
273             }
274             mSsid = new String(ssid);
275             return this;
276         }
277 
278         /**
279          * Set the BSSID to use for filtering networks from scan results. Will only match network
280          * whose BSSID is identical to the specified value.
281          * <p>
282          * <li Sets a specific BSSID for the network suggestion. If set, only the specified BSSID
283          * with the specified SSID will be considered for connection.
284          * <li>If set, only the specified BSSID with the specified SSID will be considered for
285          * connection.</li>
286          * <li>If not set, all BSSIDs with the specified SSID will be considered for connection.
287          * </li>
288          * <li>Overrides any previous value set using {@link #setBssid(MacAddress)}.</li>
289          *
290          * @param bssid BSSID of the network.
291          * @return Instance of {@link Builder} to enable chaining of the builder method.
292          */
setBssid(@onNull MacAddress bssid)293         public @NonNull Builder setBssid(@NonNull MacAddress bssid) {
294             checkNotNull(bssid);
295             mBssid = MacAddress.fromBytes(bssid.toByteArray());
296             return this;
297         }
298 
299         /**
300          * Specifies whether this represents an Enhanced Open (OWE) network.
301          *
302          * @param isEnhancedOpen {@code true} to indicate that the network used enhanced open,
303          *                       {@code false} otherwise.
304          * @return Instance of {@link Builder} to enable chaining of the builder method.
305          */
setIsEnhancedOpen(boolean isEnhancedOpen)306         public @NonNull Builder setIsEnhancedOpen(boolean isEnhancedOpen) {
307             mIsEnhancedOpen = isEnhancedOpen;
308             return this;
309         }
310 
311         /**
312          * Set the ASCII WPA2 passphrase for this network. Needed for authenticating to
313          * WPA2-PSK networks.
314          *
315          * @param passphrase passphrase of the network.
316          * @return Instance of {@link Builder} to enable chaining of the builder method.
317          * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
318          */
setWpa2Passphrase(@onNull String passphrase)319         public @NonNull Builder setWpa2Passphrase(@NonNull String passphrase) {
320             checkNotNull(passphrase);
321             final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
322             if (!asciiEncoder.canEncode(passphrase)) {
323                 throw new IllegalArgumentException("passphrase not ASCII encodable");
324             }
325             mWpa2PskPassphrase = passphrase;
326             return this;
327         }
328 
329         /**
330          * Set the ASCII WPA3 passphrase for this network. Needed for authenticating to WPA3-SAE
331          * networks.
332          *
333          * @param passphrase passphrase of the network.
334          * @return Instance of {@link Builder} to enable chaining of the builder method.
335          * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
336          */
setWpa3Passphrase(@onNull String passphrase)337         public @NonNull Builder setWpa3Passphrase(@NonNull String passphrase) {
338             checkNotNull(passphrase);
339             final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
340             if (!asciiEncoder.canEncode(passphrase)) {
341                 throw new IllegalArgumentException("passphrase not ASCII encodable");
342             }
343             mWpa3SaePassphrase = passphrase;
344             return this;
345         }
346 
347         /**
348          * Set the associated enterprise configuration for this network. Needed for authenticating
349          * to WPA2 enterprise networks. See {@link WifiEnterpriseConfig} for description.
350          *
351          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
352          * @return Instance of {@link Builder} to enable chaining of the builder method.
353          * @throws IllegalArgumentException If configuration uses server certificate but validation
354          *                                  is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()}
355          */
setWpa2EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)356         public @NonNull Builder setWpa2EnterpriseConfig(
357                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
358             checkNotNull(enterpriseConfig);
359             if (enterpriseConfig.isEapMethodServerCertUsed()
360                     && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) {
361                 throw new IllegalArgumentException("Enterprise configuration mandates server "
362                         + "certificate but validation is not enabled.");
363             }
364             mWpa2EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
365             return this;
366         }
367 
368         /**
369          * Set the associated enterprise configuration for this network. Needed for authenticating
370          * to WPA3-Enterprise networks (standard and 192-bit security). See
371          * {@link WifiEnterpriseConfig} for description. For 192-bit security networks, both the
372          * client and CA certificates must be provided, and must be of type of either
373          * sha384WithRSAEncryption (OID 1.2.840.113549.1.1.12) or ecdsa-with-SHA384
374          * (OID 1.2.840.10045.4.3.3).
375          *
376          * @deprecated use {@link #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig)} or
377          * {@link #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} to specify
378          * WPA3-Enterprise type explicitly.
379          *
380          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
381          * @return Instance of {@link Builder} to enable chaining of the builder method.
382          * @throws IllegalArgumentException If configuration uses server certificate but validation
383          *                                  is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()}
384          */
385         @Deprecated
setWpa3EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)386         public @NonNull Builder setWpa3EnterpriseConfig(
387                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
388             checkNotNull(enterpriseConfig);
389             if (enterpriseConfig.isEapMethodServerCertUsed()
390                     && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) {
391                 throw new IllegalArgumentException("Enterprise configuration mandates server "
392                         + "certificate but validation is not enabled.");
393             }
394             mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
395             return this;
396         }
397 
398         /**
399          * Set the associated enterprise configuration for this network. Needed for authenticating
400          * to WPA3-Enterprise standard networks. See {@link WifiEnterpriseConfig} for description.
401          * For WPA3-Enterprise in 192-bit security mode networks,
402          * see {@link #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} for description.
403          *
404          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
405          * @return Instance of {@link Builder} to enable chaining of the builder method.
406          * @throws IllegalArgumentException If configuration uses server certificate but validation
407          *                                  is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()}
408          */
setWpa3EnterpriseStandardModeConfig( @onNull WifiEnterpriseConfig enterpriseConfig)409         public @NonNull Builder setWpa3EnterpriseStandardModeConfig(
410                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
411             checkNotNull(enterpriseConfig);
412             if (enterpriseConfig.isEapMethodServerCertUsed()
413                     && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) {
414                 throw new IllegalArgumentException("Enterprise configuration mandates server "
415                         + "certificate but validation is not enabled.");
416             }
417             mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
418             mWpa3EnterpriseType = WPA3_ENTERPRISE_STANDARD;
419             return this;
420         }
421 
422         /**
423          * Set the associated enterprise configuration for this network. Needed for authenticating
424          * to WPA3-Enterprise in 192-bit security mode networks. See {@link WifiEnterpriseConfig}
425          * for description. Both the client and CA certificates must be provided,
426          * and must be of type of either sha384WithRSAEncryption with key length of 3072bit or
427          * more (OID 1.2.840.113549.1.1.12), or ecdsa-with-SHA384 with key length of 384bit or
428          * more (OID 1.2.840.10045.4.3.3).
429          *
430          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
431          * @return Instance of {@link Builder} to enable chaining of the builder method.
432          * @throws IllegalArgumentException if the EAP type or certificates do not
433          *                                  meet 192-bit mode requirements.
434          */
setWpa3Enterprise192BitModeConfig( @onNull WifiEnterpriseConfig enterpriseConfig)435         public @NonNull Builder setWpa3Enterprise192BitModeConfig(
436                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
437             checkNotNull(enterpriseConfig);
438             if (enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.TLS) {
439                 throw new IllegalArgumentException("The 192-bit mode network type must be TLS");
440             }
441             if (!WifiEnterpriseConfig.isSuiteBCipherCert(
442                     enterpriseConfig.getClientCertificate())) {
443                 throw new IllegalArgumentException(
444                     "The client certificate does not meet 192-bit mode requirements.");
445             }
446             if (!WifiEnterpriseConfig.isSuiteBCipherCert(
447                     enterpriseConfig.getCaCertificate())) {
448                 throw new IllegalArgumentException(
449                     "The CA certificate does not meet 192-bit mode requirements.");
450             }
451 
452             mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
453             mWpa3EnterpriseType = WPA3_ENTERPRISE_192_BIT;
454             return this;
455         }
456 
457         /**
458          * Set the associated Passpoint configuration for this network. Needed for authenticating
459          * to Hotspot 2.0 networks. See {@link PasspointConfiguration} for description.
460          *
461          * @param passpointConfig Instance of {@link PasspointConfiguration}.
462          * @return Instance of {@link Builder} to enable chaining of the builder method.
463          * @throws IllegalArgumentException if passpoint configuration is invalid.
464          */
setPasspointConfig( @onNull PasspointConfiguration passpointConfig)465         public @NonNull Builder setPasspointConfig(
466                 @NonNull PasspointConfiguration passpointConfig) {
467             checkNotNull(passpointConfig);
468             if (!passpointConfig.validate()) {
469                 throw new IllegalArgumentException("Passpoint configuration is invalid");
470             }
471             mPasspointConfiguration = new PasspointConfiguration(passpointConfig);
472             return this;
473         }
474 
475         /**
476          * Set the carrier ID of the network operator. The carrier ID associates a Suggested
477          * network with a specific carrier (and therefore SIM). The carrier ID must be provided
478          * for any network which uses the SIM-based authentication: e.g. EAP-SIM, EAP-AKA,
479          * EAP-AKA', and EAP-PEAP with SIM-based phase 2 authentication.
480          * @param carrierId see {@link TelephonyManager#getSimCarrierId()}.
481          * @return Instance of {@link Builder} to enable chaining of the builder method.
482          *
483          * @hide
484          */
485         @SystemApi
486         @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING)
setCarrierId(int carrierId)487         public @NonNull Builder setCarrierId(int carrierId) {
488             mCarrierId = carrierId;
489             return this;
490         }
491 
492         /**
493          * Configure the suggestion to only be used with the SIM identified by the subscription
494          * ID specified in this method. The suggested network will only be used by that SIM and
495          * no other SIM - even from the same carrier.
496          * <p>
497          * The caller is restricted to be either of:
498          * <li>A carrier provisioning app (which holds the
499          * {@code android.Manifest.permission#NETWORK_CARRIER_PROVISIONING} permission).
500          * <li>A carrier-privileged app - which is restricted to only specify a subscription ID
501          * which belong to the same carrier which signed the app, see
502          * {@link TelephonyManager#hasCarrierPrivileges()}.
503          * <p>
504          * Specifying a subscription ID which doesn't match these restriction will cause the
505          * suggestion to be rejected with the error code
506          * {@link WifiManager#STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED}.
507          *
508          * @param subscriptionId subscription ID see {@link SubscriptionInfo#getSubscriptionId()}
509          * @return Instance of {@link Builder} to enable chaining of the builder method.
510          * @throws IllegalArgumentException if subscriptionId equals to {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}
511          */
512         @RequiresApi(Build.VERSION_CODES.S)
setSubscriptionId(int subscriptionId)513         public @NonNull Builder setSubscriptionId(int subscriptionId) {
514             if (!SdkLevel.isAtLeastS()) {
515                 throw new UnsupportedOperationException();
516             }
517             if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
518                 throw new IllegalArgumentException("Subscription Id is invalid");
519             }
520             mSubscriptionId = subscriptionId;
521             return this;
522         }
523 
524         /**
525          * Suggested networks are considered as part of a pool of all suggested networks and other
526          * networks (e.g. saved networks) - one of which will be selected.
527          * <ul>
528          * <li> Any app can suggest any number of networks. </li>
529          * <li> Priority: only the highest priority (0 being the lowest) currently visible suggested
530          * network or networks (multiple suggested networks may have the same priority) are added to
531          * the network selection pool.</li>
532          * </ul>
533          * <p>
534          * However, this restricts a suggesting app to have a single group of networks which can be
535          * prioritized. In some circumstances multiple groups are useful: for instance, suggesting
536          * networks for 2 different SIMs - each of which may have its own priority order.
537          * <p>
538          * Priority group: creates a separate priority group. Only the highest priority, currently
539          * visible suggested network or networks, within each priority group are included in the
540          * network selection pool.
541          * <p>
542          * Specify an arbitrary integer only used as the priority group. Use with
543          * {@link #setPriority(int)}.
544          *
545          * @param priorityGroup priority group id, if not set default is 0.
546          * @return Instance of {@link Builder} to enable chaining of the builder method.
547          */
setPriorityGroup(@ntRangefrom = 0) int priorityGroup)548         public @NonNull Builder setPriorityGroup(@IntRange(from = 0) int priorityGroup) {
549             mPriorityGroup = priorityGroup;
550             return this;
551         }
552 
553         /**
554          * Set the ASCII WAPI passphrase for this network. Needed for authenticating to
555          * WAPI-PSK networks.
556          *
557          * @param passphrase passphrase of the network.
558          * @return Instance of {@link Builder} to enable chaining of the builder method.
559          * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
560          *
561          */
setWapiPassphrase(@onNull String passphrase)562         public @NonNull Builder setWapiPassphrase(@NonNull String passphrase) {
563             checkNotNull(passphrase);
564             final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
565             if (!asciiEncoder.canEncode(passphrase)) {
566                 throw new IllegalArgumentException("passphrase not ASCII encodable");
567             }
568             mWapiPskPassphrase = passphrase;
569             return this;
570         }
571 
572         /**
573          * Set the associated enterprise configuration for this network. Needed for authenticating
574          * to WAPI-CERT networks. See {@link WifiEnterpriseConfig} for description.
575          *
576          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
577          * @return Instance of {@link Builder} to enable chaining of the builder method.
578          */
setWapiEnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)579         public @NonNull Builder setWapiEnterpriseConfig(
580                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
581             checkNotNull(enterpriseConfig);
582             mWapiEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
583             return this;
584         }
585 
586         /**
587          * Specifies whether this represents a hidden network.
588          * <p>
589          * <li>If not set, defaults to false (i.e not a hidden network).</li>
590          *
591          * @param isHiddenSsid {@code true} to indicate that the network is hidden, {@code false}
592          *                     otherwise.
593          * @return Instance of {@link Builder} to enable chaining of the builder method.
594          */
setIsHiddenSsid(boolean isHiddenSsid)595         public @NonNull Builder setIsHiddenSsid(boolean isHiddenSsid) {
596             mIsHiddenSSID = isHiddenSsid;
597             return this;
598         }
599 
600         /**
601          * Specifies the MAC randomization method.
602          * <p>
603          * Suggested networks will never use the device (factory) MAC address to associate to the
604          * network - instead they use a locally generated random MAC address. This method controls
605          * the strategy for generating the random MAC address. If not set, defaults to
606          * {@link #RANDOMIZATION_PERSISTENT}.
607          *
608          * @param macRandomizationSetting - one of {@code RANDOMIZATION_*} values
609          * @return Instance of {@link Builder} to enable chaining of the builder method.
610          */
setMacRandomizationSetting( @acRandomizationSetting int macRandomizationSetting)611         public @NonNull Builder setMacRandomizationSetting(
612                 @MacRandomizationSetting int macRandomizationSetting) {
613             switch (macRandomizationSetting) {
614                 case RANDOMIZATION_PERSISTENT:
615                 case RANDOMIZATION_NON_PERSISTENT:
616                     mMacRandomizationSetting = macRandomizationSetting;
617                     break;
618                 default:
619                     throw new IllegalArgumentException();
620             }
621             return this;
622         }
623 
624         /**
625          * Specifies whether the app needs to log in to a captive portal to obtain Internet access.
626          * <p>
627          * This will dictate if the directed broadcast
628          * {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} will be sent to the
629          * app after successfully connecting to the network.
630          * Use this for captive portal type networks where the app needs to authenticate the user
631          * before the device can access the network.
632          * <p>
633          * <li>If not set, defaults to false (i.e no app interaction required).</li>
634          *
635          * @param isAppInteractionRequired {@code true} to indicate that app interaction is
636          *                                 required, {@code false} otherwise.
637          * @return Instance of {@link Builder} to enable chaining of the builder method.
638          */
setIsAppInteractionRequired(boolean isAppInteractionRequired)639         public @NonNull Builder setIsAppInteractionRequired(boolean isAppInteractionRequired) {
640             mIsAppInteractionRequired = isAppInteractionRequired;
641             return this;
642         }
643 
644         /**
645          * Specifies whether the user needs to log in to a captive portal to obtain Internet access.
646          * <p>
647          * <li>If not set, defaults to false (i.e no user interaction required).</li>
648          *
649          * @param isUserInteractionRequired {@code true} to indicate that user interaction is
650          *                                  required, {@code false} otherwise.
651          * @return Instance of {@link Builder} to enable chaining of the builder method.
652          */
setIsUserInteractionRequired(boolean isUserInteractionRequired)653         public @NonNull Builder setIsUserInteractionRequired(boolean isUserInteractionRequired) {
654             mIsUserInteractionRequired = isUserInteractionRequired;
655             return this;
656         }
657 
658         /**
659          * Specify the priority of this network among other network suggestions provided by the same
660          * app and within the same priority group, see {@link #setPriorityGroup(int)}. Priorities
661          * have no impact on suggestions by other apps or suggestions from the same app using a
662          * different priority group. The higher the number, the higher the priority
663          * (i.e value of 0 = lowest priority). If not set, defaults to a lower priority than any
664          * assigned priority.
665          *
666          * @param priority Integer number representing the priority among suggestions by the app.
667          * @return Instance of {@link Builder} to enable chaining of the builder method.
668          * @throws IllegalArgumentException if the priority value is negative.
669          */
setPriority(@ntRangefrom = 0) int priority)670         public @NonNull Builder setPriority(@IntRange(from = 0) int priority) {
671             if (priority < 0) {
672                 throw new IllegalArgumentException("Invalid priority value " + priority);
673             }
674             mPriority = priority;
675             return this;
676         }
677 
678         /**
679          * Specifies whether this network is metered.
680          * <p>
681          * <li>If not set, defaults to detect automatically.</li>
682          *
683          * @param isMetered {@code true} to indicate that the network is metered, {@code false}
684          *                  for not metered.
685          * @return Instance of {@link Builder} to enable chaining of the builder method.
686          */
setIsMetered(boolean isMetered)687         public @NonNull Builder setIsMetered(boolean isMetered) {
688             if (isMetered) {
689                 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
690             } else {
691                 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
692             }
693             return this;
694         }
695 
696         /**
697          * Specifies whether the network credentials provided with this suggestion can be used by
698          * the user to explicitly (manually) connect to this network. If true this network will
699          * appear in the Wi-Fi Picker (in Settings) and the user will be able to select and connect
700          * to it with the provided credentials. If false, the user will need to enter network
701          * credentials and the resulting configuration will become a user saved network.
702          * <p>
703          * <li>Note: Only valid for secure (non-open) networks.
704          * <li>If not set, defaults to true (i.e. allow user to manually connect) for secure
705          * networks and false for open networks.</li>
706          *
707          * @param isShared {@code true} to indicate that the credentials may be used by the user to
708          *                              manually connect to the network, {@code false} otherwise.
709          * @return Instance of {@link Builder} to enable chaining of the builder method.
710          */
setCredentialSharedWithUser(boolean isShared)711         public @NonNull Builder setCredentialSharedWithUser(boolean isShared) {
712             mIsSharedWithUser = isShared;
713             mIsSharedWithUserSet = true;
714             return this;
715         }
716 
717         /**
718          * Specifies whether the suggestion is created with auto-join enabled or disabled. The
719          * user may modify the auto-join configuration of a suggestion directly once the device
720          * associates to the network.
721          * <p>
722          * If auto-join is initialized as disabled the user may still be able to manually connect
723          * to the network. Therefore, disabling auto-join only makes sense if
724          * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which
725          * itself implies a secure (non-open) network.
726          * <p>
727          * If not set, defaults to true (i.e. auto-join is initialized as enabled).
728          *
729          * @param enabled true for initializing with auto-join enabled (the default), false to
730          *                initializing with auto-join disabled.
731          * @return Instance of {@link Builder} to enable chaining of the builder method.
732          */
setIsInitialAutojoinEnabled(boolean enabled)733         public @NonNull Builder setIsInitialAutojoinEnabled(boolean enabled) {
734             mIsInitialAutojoinEnabled = enabled;
735             return this;
736         }
737 
738         /**
739          * Specifies whether the system will bring up the network (if selected) as untrusted. An
740          * untrusted network has its {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED}
741          * capability removed. The Wi-Fi network selection process may use this information to
742          * influence priority of the suggested network for Wi-Fi network selection (most likely to
743          * reduce it). The connectivity service may use this information to influence the overall
744          * network configuration of the device.
745          * <p>
746          * <li> These suggestions are only considered for network selection if a
747          * {@link NetworkRequest} without {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED}
748          * capability is filed.
749          * <li> An untrusted network's credentials may not be shared with the user using
750          * {@link #setCredentialSharedWithUser(boolean)}.</li>
751          * <li> If not set, defaults to false (i.e. network is trusted).</li>
752          *
753          * @param isUntrusted Boolean indicating whether the network should be brought up untrusted
754          *                    (if true) or trusted (if false).
755          * @return Instance of {@link Builder} to enable chaining of the builder method.
756          */
setUntrusted(boolean isUntrusted)757         public @NonNull Builder setUntrusted(boolean isUntrusted) {
758             mIsNetworkUntrusted = isUntrusted;
759             return this;
760         }
761 
762         /**
763          * Specifies whether the system will bring up the network (if selected) as OEM paid. An
764          * OEM paid network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID} capability
765          * added.
766          * Note:
767          * <li>The connectivity service may use this information to influence the overall
768          * network configuration of the device. This network is typically only available to system
769          * apps.
770          * <li>On devices which do not support concurrent connection (indicated via
771          * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), Wi-Fi
772          * network selection process may use this information to influence priority of the
773          * suggested network for Wi-Fi network selection (most likely to reduce it).
774          * <li>On devices which support concurrent connections (indicated via
775          * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), these
776          * OEM paid networks may be brought up as a secondary concurrent connection (primary
777          * connection will be used for networks available to the user and all apps.
778          * <p>
779          * <li> An OEM paid network's credentials may not be shared with the user using
780          * {@link #setCredentialSharedWithUser(boolean)}.</li>
781          * <li> These suggestions are only considered for network selection if a
782          * {@link NetworkRequest} with {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}
783          * capability is filed.
784          * <li> Each suggestion can have both {@link #setOemPaid(boolean)} and
785          * {@link #setOemPrivate(boolean)} set if the app wants these suggestions considered
786          * for creating either an OEM paid network or OEM private network determined based on
787          * the {@link NetworkRequest} that is active.
788          * <li> If not set, defaults to false (i.e. network is not OEM paid).</li>
789          *
790          * @param isOemPaid Boolean indicating whether the network should be brought up as OEM paid
791          *                  (if true) or not OEM paid (if false).
792          * @return Instance of {@link Builder} to enable chaining of the builder method.
793          * @hide
794          */
795         @SystemApi
796         @RequiresApi(Build.VERSION_CODES.S)
setOemPaid(boolean isOemPaid)797         public @NonNull Builder setOemPaid(boolean isOemPaid) {
798             if (!SdkLevel.isAtLeastS()) {
799                 throw new UnsupportedOperationException();
800             }
801             mIsNetworkOemPaid = isOemPaid;
802             return this;
803         }
804 
805         /**
806          * Specifies whether the system will bring up the network (if selected) as OEM private. An
807          * OEM private network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE} capability
808          * added.
809          * Note:
810          * <li>The connectivity service may use this information to influence the overall
811          * network configuration of the device. This network is typically only available to system
812          * apps.
813          * <li>On devices which do not support concurrent connection (indicated via
814          * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), Wi-Fi
815          * network selection process may use this information to influence priority of the suggested
816          * network for Wi-Fi network selection (most likely to reduce it).
817          * <li>On devices which support concurrent connections (indicated via
818          * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), these OEM
819          * private networks may be brought up as a secondary concurrent connection (primary
820          * connection will be used for networks available to the user and all apps.
821          * <p>
822          * <li> An OEM private network's credentials may not be shared with the user using
823          * {@link #setCredentialSharedWithUser(boolean)}.</li>
824          * <li> These suggestions are only considered for network selection if a
825          * {@link NetworkRequest} with {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}
826          * capability is filed.
827          * <li> Each suggestion can have both {@link #setOemPaid(boolean)} and
828          * {@link #setOemPrivate(boolean)} set if the app wants these suggestions considered
829          * for creating either an OEM paid network or OEM private network determined based on
830          * the {@link NetworkRequest} that is active.
831          * <li> If not set, defaults to false (i.e. network is not OEM private).</li>
832          *
833          * @param isOemPrivate Boolean indicating whether the network should be brought up as OEM
834          *                     private (if true) or not OEM private (if false).
835          * @return Instance of {@link Builder} to enable chaining of the builder method.
836          * @hide
837          */
838         @SystemApi
839         @RequiresApi(Build.VERSION_CODES.S)
setOemPrivate(boolean isOemPrivate)840         public @NonNull Builder setOemPrivate(boolean isOemPrivate) {
841             if (!SdkLevel.isAtLeastS()) {
842                 throw new UnsupportedOperationException();
843             }
844             mIsNetworkOemPrivate = isOemPrivate;
845             return this;
846         }
847 
848         /**
849          * Specifies whether the suggestion represents a carrier merged network. A carrier merged
850          * Wi-Fi network is treated as part of the mobile carrier network. Such configuration may
851          * impact the user interface and data usage accounting.
852          * <p>
853          * Only carriers with the
854          * {@link android.telephony.CarrierConfigManager#KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL}
855          * flag set to {@code true} may use this API.
856          * <p>
857          * <li>A suggestion marked as carrier merged must be metered enterprise network with a valid
858          * subscription Id set.
859          * @see #setIsMetered(boolean)
860          * @see #setSubscriptionId(int)
861          * @see #setWpa2EnterpriseConfig(WifiEnterpriseConfig)
862          * @see #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)
863          * @see #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig)
864          * @see #setPasspointConfig(PasspointConfiguration)
865          * </li>
866          * <li>If not set, defaults to false (i.e. not a carrier merged network.)</li>
867          * </p>
868          * @param isCarrierMerged Boolean indicating whether the network is treated a carrier
869          *                               merged network (if true) or non-merged network (if false);
870          * @return Instance of {@link Builder} to enable chaining of the builder method.
871          */
872         @RequiresApi(Build.VERSION_CODES.S)
setCarrierMerged(boolean isCarrierMerged)873         public @NonNull Builder setCarrierMerged(boolean isCarrierMerged) {
874             if (!SdkLevel.isAtLeastS()) {
875                 throw new UnsupportedOperationException();
876             }
877             mIsCarrierMerged = isCarrierMerged;
878             return this;
879         }
880 
881         /**
882          * Specifies whether the suggestion represents an SAE network which only
883          * accepts Hash-to-Element mode.
884          * If this is enabled, Hunting & Pecking mode is disabled and only Hash-to-Element
885          * mode is used for this network.
886          * This is only valid for an SAE network which is configured using the
887          * {@link #setWpa3Passphrase}.
888          * Before calling this API, the application should check Hash-to-Element support using
889          * {@link WifiManager#isWpa3SaeH2eSupported()}.
890          *
891          * @param enable Boolean indicating whether the network only accepts Hash-to-Element mode,
892          *        default is false.
893          * @return Instance of {@link Builder} to enable chaining of the builder method.
894          */
895         @RequiresApi(Build.VERSION_CODES.S)
setIsWpa3SaeH2eOnlyModeEnabled(boolean enable)896         public @NonNull Builder setIsWpa3SaeH2eOnlyModeEnabled(boolean enable) {
897             if (!SdkLevel.isAtLeastS()) {
898                 throw new UnsupportedOperationException();
899             }
900             mSaeH2eOnlyMode = enable;
901             return this;
902         }
903 
setSecurityParamsInWifiConfiguration( @onNull WifiConfiguration configuration)904         private void setSecurityParamsInWifiConfiguration(
905                 @NonNull WifiConfiguration configuration) {
906             if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
907                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
908                 // WifiConfiguration.preSharedKey needs quotes around ASCII password.
909                 configuration.preSharedKey = "\"" + mWpa2PskPassphrase + "\"";
910             } else if (!TextUtils.isEmpty(mWpa3SaePassphrase)) { // WPA3-SAE network.
911                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
912                 // WifiConfiguration.preSharedKey needs quotes around ASCII password.
913                 configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\"";
914                 if (mSaeH2eOnlyMode) configuration.enableSaeH2eOnlyMode(mSaeH2eOnlyMode);
915             } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network
916                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
917                 configuration.enterpriseConfig = mWpa2EnterpriseConfig;
918             } else if (mWpa3EnterpriseConfig != null) { // WPA3-Enterprise
919                 if (mWpa3EnterpriseType == WPA3_ENTERPRISE_AUTO
920                         && mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS
921                         && WifiEnterpriseConfig.isSuiteBCipherCert(
922                         mWpa3EnterpriseConfig.getClientCertificate())
923                         && WifiEnterpriseConfig.isSuiteBCipherCert(
924                         mWpa3EnterpriseConfig.getCaCertificate())) {
925                     // WPA3-Enterprise in 192-bit security mode
926                     configuration.setSecurityParams(
927                             WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
928                 } else if (mWpa3EnterpriseType == WPA3_ENTERPRISE_192_BIT) {
929                     // WPA3-Enterprise in 192-bit security mode
930                     configuration.setSecurityParams(
931                             WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
932                 } else {
933                     // WPA3-Enterprise
934                     configuration.setSecurityParams(
935                             WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
936                 }
937                 configuration.enterpriseConfig = mWpa3EnterpriseConfig;
938             } else if (mIsEnhancedOpen) { // OWE network
939                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
940             } else if (!TextUtils.isEmpty(mWapiPskPassphrase)) { // WAPI-PSK network.
941                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_PSK);
942                 // WifiConfiguration.preSharedKey needs quotes around ASCII password.
943                 configuration.preSharedKey = "\"" + mWapiPskPassphrase + "\"";
944             } else if (mWapiEnterpriseConfig != null) { // WAPI-CERT network
945                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT);
946                 configuration.enterpriseConfig = mWapiEnterpriseConfig;
947             } else { // Open network
948                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
949             }
950         }
951 
952         /**
953          * Helper method to build WifiConfiguration object from the builder.
954          * @return Instance of {@link WifiConfiguration}.
955          */
buildWifiConfiguration()956         private WifiConfiguration buildWifiConfiguration() {
957             final WifiConfiguration wifiConfiguration = new WifiConfiguration();
958             // WifiConfiguration.SSID needs quotes around unicode SSID.
959             wifiConfiguration.SSID = "\"" + mSsid + "\"";
960             if (mBssid != null) {
961                 wifiConfiguration.BSSID = mBssid.toString();
962             }
963 
964             setSecurityParamsInWifiConfiguration(wifiConfiguration);
965 
966             wifiConfiguration.hiddenSSID = mIsHiddenSSID;
967             wifiConfiguration.priority = mPriority;
968             wifiConfiguration.meteredOverride = mMeteredOverride;
969             wifiConfiguration.carrierId = mCarrierId;
970             wifiConfiguration.trusted = !mIsNetworkUntrusted;
971             wifiConfiguration.oemPaid = mIsNetworkOemPaid;
972             wifiConfiguration.oemPrivate = mIsNetworkOemPrivate;
973             wifiConfiguration.carrierMerged = mIsCarrierMerged;
974             wifiConfiguration.macRandomizationSetting =
975                     mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT
976                     ? WifiConfiguration.RANDOMIZATION_NON_PERSISTENT
977                     : WifiConfiguration.RANDOMIZATION_PERSISTENT;
978             wifiConfiguration.subscriptionId = mSubscriptionId;
979             return wifiConfiguration;
980         }
981 
validateSecurityParams()982         private void validateSecurityParams() {
983             int numSecurityTypes = 0;
984             numSecurityTypes += mIsEnhancedOpen ? 1 : 0;
985             numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0;
986             numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0;
987             numSecurityTypes += !TextUtils.isEmpty(mWapiPskPassphrase) ? 1 : 0;
988             numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0;
989             numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0;
990             numSecurityTypes += mWapiEnterpriseConfig != null ? 1 : 0;
991             numSecurityTypes += mPasspointConfiguration != null ? 1 : 0;
992             if (numSecurityTypes > 1) {
993                 throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase,"
994                         + " setWpa3Passphrase, setWpa2EnterpriseConfig, setWpa3EnterpriseConfig"
995                         + " setWapiPassphrase, setWapiCertSuite, setIsWapiCertSuiteAuto"
996                         + " or setPasspointConfig can be invoked for network suggestion");
997             }
998         }
999 
buildWifiConfigurationForPasspoint()1000         private WifiConfiguration buildWifiConfigurationForPasspoint() {
1001             WifiConfiguration wifiConfiguration = new WifiConfiguration();
1002             wifiConfiguration.FQDN = mPasspointConfiguration.getHomeSp().getFqdn();
1003             wifiConfiguration.setPasspointUniqueId(mPasspointConfiguration.getUniqueId());
1004             wifiConfiguration.priority = mPriority;
1005             wifiConfiguration.meteredOverride = mMeteredOverride;
1006             wifiConfiguration.trusted = !mIsNetworkUntrusted;
1007             wifiConfiguration.oemPaid = mIsNetworkOemPaid;
1008             wifiConfiguration.oemPrivate = mIsNetworkOemPrivate;
1009             wifiConfiguration.carrierMerged = mIsCarrierMerged;
1010             wifiConfiguration.subscriptionId = mSubscriptionId;
1011             mPasspointConfiguration.setCarrierId(mCarrierId);
1012             mPasspointConfiguration.setSubscriptionId(mSubscriptionId);
1013             mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride);
1014             mPasspointConfiguration.setOemPrivate(mIsNetworkOemPrivate);
1015             mPasspointConfiguration.setOemPaid(mIsNetworkOemPaid);
1016             mPasspointConfiguration.setCarrierMerged(mIsCarrierMerged);
1017             wifiConfiguration.macRandomizationSetting =
1018                     mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT
1019                     ? WifiConfiguration.RANDOMIZATION_NON_PERSISTENT
1020                     : WifiConfiguration.RANDOMIZATION_PERSISTENT;
1021             return wifiConfiguration;
1022         }
1023 
1024         /**
1025          * Create a network suggestion object for use in
1026          * {@link WifiManager#addNetworkSuggestions(List)}.
1027          *
1028          *<p class="note">
1029          * <b>Note:</b> Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID
1030          * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to
1031          * the platform.
1032          * </p>
1033          *
1034          * For example:
1035          * To provide credentials for one open, one WPA2, one WPA3 network with their
1036          * corresponding SSID's and one with Passpoint config:
1037          *
1038          * <pre>{@code
1039          * final WifiNetworkSuggestion suggestion1 =
1040          *      new Builder()
1041          *      .setSsid("test111111")
1042          *      .build();
1043          * final WifiNetworkSuggestion suggestion2 =
1044          *      new Builder()
1045          *      .setSsid("test222222")
1046          *      .setWpa2Passphrase("test123456")
1047          *      .build();
1048          * final WifiNetworkSuggestion suggestion3 =
1049          *      new Builder()
1050          *      .setSsid("test333333")
1051          *      .setWpa3Passphrase("test6789")
1052          *      .build();
1053          * final PasspointConfiguration passpointConfig= new PasspointConfiguration();
1054          * // configure passpointConfig to include a valid Passpoint configuration
1055          * final WifiNetworkSuggestion suggestion4 =
1056          *      new Builder()
1057          *      .setPasspointConfig(passpointConfig)
1058          *      .build();
1059          * final List<WifiNetworkSuggestion> suggestionsList =
1060          *      new ArrayList<WifiNetworkSuggestion> { {
1061          *          add(suggestion1);
1062          *          add(suggestion2);
1063          *          add(suggestion3);
1064          *          add(suggestion4);
1065          *      } };
1066          * final WifiManager wifiManager =
1067          *      context.getSystemService(Context.WIFI_SERVICE);
1068          * wifiManager.addNetworkSuggestions(suggestionsList);
1069          * // ...
1070          * }</pre>
1071          *
1072          * @return Instance of {@link WifiNetworkSuggestion}
1073          * @throws IllegalStateException on invalid params set
1074          * @see WifiNetworkSuggestion
1075          */
build()1076         public @NonNull WifiNetworkSuggestion build() {
1077             validateSecurityParams();
1078             WifiConfiguration wifiConfiguration;
1079             if (mPasspointConfiguration != null) {
1080                 if (mSsid != null) {
1081                     throw new IllegalStateException("setSsid should not be invoked for suggestion "
1082                             + "with Passpoint configuration");
1083                 }
1084                 if (mIsHiddenSSID) {
1085                     throw new IllegalStateException("setIsHiddenSsid should not be invoked for "
1086                             + "suggestion with Passpoint configuration");
1087                 }
1088                 wifiConfiguration = buildWifiConfigurationForPasspoint();
1089                 mPasspointConfiguration.setEnhancedMacRandomizationEnabled(
1090                         mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT);
1091             } else {
1092                 if (mSsid == null) {
1093                     throw new IllegalStateException("setSsid should be invoked for suggestion");
1094                 }
1095                 if (TextUtils.isEmpty(mSsid)) {
1096                     throw new IllegalStateException("invalid ssid for suggestion");
1097                 }
1098                 if (mBssid != null
1099                         && (mBssid.equals(MacAddress.BROADCAST_ADDRESS)
1100                         || mBssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS))) {
1101                     throw new IllegalStateException("invalid bssid for suggestion");
1102                 }
1103                 if (TextUtils.isEmpty(mWpa3SaePassphrase) && mSaeH2eOnlyMode) {
1104                     throw new IllegalStateException(
1105                             "Hash-to-Element only mode is only allowed for the SAE network");
1106                 }
1107 
1108                 wifiConfiguration = buildWifiConfiguration();
1109                 if (wifiConfiguration.isOpenNetwork()) {
1110                     if (mIsSharedWithUserSet && mIsSharedWithUser) {
1111                         throw new IllegalStateException("Open network should not be "
1112                                 + "setCredentialSharedWithUser to true");
1113                     }
1114                     mIsSharedWithUser = false;
1115                 }
1116             }
1117             if (!mIsSharedWithUser && !mIsInitialAutojoinEnabled) {
1118                 throw new IllegalStateException("Should have not a network with both "
1119                         + "setCredentialSharedWithUser and "
1120                         + "setIsAutojoinEnabled set to false");
1121             }
1122             if (mIsNetworkUntrusted) {
1123                 if (mIsSharedWithUserSet && mIsSharedWithUser) {
1124                     throw new IllegalStateException("Should not be both"
1125                             + "setCredentialSharedWithUser and +"
1126                             + "setUntrusted to true");
1127                 }
1128                 mIsSharedWithUser = false;
1129             }
1130             if (mIsNetworkOemPaid) {
1131                 if (mIsSharedWithUserSet && mIsSharedWithUser) {
1132                     throw new IllegalStateException("Should not be both"
1133                             + "setCredentialSharedWithUser and +"
1134                             + "setOemPaid to true");
1135                 }
1136                 mIsSharedWithUser = false;
1137             }
1138             if (mIsNetworkOemPrivate) {
1139                 if (mIsSharedWithUserSet && mIsSharedWithUser) {
1140                     throw new IllegalStateException("Should not be both"
1141                             + "setCredentialSharedWithUser and +"
1142                             + "setOemPrivate to true");
1143                 }
1144                 mIsSharedWithUser = false;
1145             }
1146             if (mIsCarrierMerged) {
1147                 if (mSubscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
1148                         || mMeteredOverride != WifiConfiguration.METERED_OVERRIDE_METERED
1149                         || !isEnterpriseSuggestion()) {
1150                     throw new IllegalStateException("A carrier merged network must be a metered, "
1151                             + "enterprise network with valid subscription Id");
1152                 }
1153             }
1154             return new WifiNetworkSuggestion(
1155                     wifiConfiguration,
1156                     mPasspointConfiguration,
1157                     mIsAppInteractionRequired,
1158                     mIsUserInteractionRequired,
1159                     mIsSharedWithUser,
1160                     mIsInitialAutojoinEnabled,
1161                     mPriorityGroup);
1162         }
1163 
isEnterpriseSuggestion()1164         private boolean isEnterpriseSuggestion() {
1165             return !(mWpa2EnterpriseConfig == null && mWpa3EnterpriseConfig == null
1166                     && mWapiEnterpriseConfig == null && mPasspointConfiguration == null);
1167         }
1168     }
1169 
1170 
1171 
1172     /**
1173      * Network configuration for the provided network.
1174      * @hide
1175      */
1176     @NonNull
1177     public final WifiConfiguration wifiConfiguration;
1178 
1179     /**
1180      * Passpoint configuration for the provided network.
1181      * @hide
1182      */
1183     @Nullable
1184     public final PasspointConfiguration passpointConfiguration;
1185 
1186     /**
1187      * Whether app needs to log in to captive portal to obtain Internet access.
1188      * @hide
1189      */
1190     public final boolean isAppInteractionRequired;
1191 
1192     /**
1193      * Whether user needs to log in to captive portal to obtain Internet access.
1194      * @hide
1195      */
1196     public final boolean isUserInteractionRequired;
1197 
1198     /**
1199      * Whether app share credential with the user, allow user use provided credential to
1200      * connect network manually.
1201      * @hide
1202      */
1203     public final boolean isUserAllowedToManuallyConnect;
1204 
1205     /**
1206      * Whether the suggestion will be initialized as auto-joined or not.
1207      * @hide
1208      */
1209     public final boolean isInitialAutoJoinEnabled;
1210 
1211     /**
1212      * Priority group ID.
1213      * @hide
1214      */
1215     public final int priorityGroup;
1216 
1217     /** @hide */
WifiNetworkSuggestion()1218     public WifiNetworkSuggestion() {
1219         this.wifiConfiguration = new WifiConfiguration();
1220         this.passpointConfiguration = null;
1221         this.isAppInteractionRequired = false;
1222         this.isUserInteractionRequired = false;
1223         this.isUserAllowedToManuallyConnect = true;
1224         this.isInitialAutoJoinEnabled = true;
1225         this.priorityGroup = 0;
1226     }
1227 
1228     /** @hide */
WifiNetworkSuggestion(@onNull WifiConfiguration networkConfiguration, @Nullable PasspointConfiguration passpointConfiguration, boolean isAppInteractionRequired, boolean isUserInteractionRequired, boolean isUserAllowedToManuallyConnect, boolean isInitialAutoJoinEnabled, int priorityGroup)1229     public WifiNetworkSuggestion(@NonNull WifiConfiguration networkConfiguration,
1230                                  @Nullable PasspointConfiguration passpointConfiguration,
1231                                  boolean isAppInteractionRequired,
1232                                  boolean isUserInteractionRequired,
1233                                  boolean isUserAllowedToManuallyConnect,
1234                                  boolean isInitialAutoJoinEnabled, int priorityGroup) {
1235         checkNotNull(networkConfiguration);
1236         this.wifiConfiguration = networkConfiguration;
1237         this.passpointConfiguration = passpointConfiguration;
1238 
1239         this.isAppInteractionRequired = isAppInteractionRequired;
1240         this.isUserInteractionRequired = isUserInteractionRequired;
1241         this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect;
1242         this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled;
1243         this.priorityGroup = priorityGroup;
1244     }
1245 
1246     public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
1247             new Creator<WifiNetworkSuggestion>() {
1248                 @Override
1249                 public WifiNetworkSuggestion createFromParcel(Parcel in) {
1250                     return new WifiNetworkSuggestion(
1251                             in.readParcelable(null), // wifiConfiguration
1252                             in.readParcelable(null), // PasspointConfiguration
1253                             in.readBoolean(), // isAppInteractionRequired
1254                             in.readBoolean(), // isUserInteractionRequired
1255                             in.readBoolean(), // isSharedCredentialWithUser
1256                             in.readBoolean(),  // isAutojoinEnabled
1257                             in.readInt() // priorityGroup
1258                     );
1259                 }
1260 
1261                 @Override
1262                 public WifiNetworkSuggestion[] newArray(int size) {
1263                     return new WifiNetworkSuggestion[size];
1264                 }
1265             };
1266 
1267     @Override
describeContents()1268     public int describeContents() {
1269         return 0;
1270     }
1271 
1272     @Override
writeToParcel(Parcel dest, int flags)1273     public void writeToParcel(Parcel dest, int flags) {
1274         dest.writeParcelable(wifiConfiguration, flags);
1275         dest.writeParcelable(passpointConfiguration, flags);
1276         dest.writeBoolean(isAppInteractionRequired);
1277         dest.writeBoolean(isUserInteractionRequired);
1278         dest.writeBoolean(isUserAllowedToManuallyConnect);
1279         dest.writeBoolean(isInitialAutoJoinEnabled);
1280         dest.writeInt(priorityGroup);
1281     }
1282 
1283     @Override
hashCode()1284     public int hashCode() {
1285         return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
1286                 wifiConfiguration.getDefaultSecurityType(),
1287                 wifiConfiguration.getPasspointUniqueId(),
1288                 wifiConfiguration.subscriptionId, wifiConfiguration.carrierId);
1289     }
1290 
1291     @Override
equals(Object obj)1292     public boolean equals(Object obj) {
1293         if (this == obj) {
1294             return true;
1295         }
1296         if (!(obj instanceof WifiNetworkSuggestion)) {
1297             return false;
1298         }
1299         WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj;
1300         if (this.passpointConfiguration == null ^ lhs.passpointConfiguration == null) {
1301             return false;
1302         }
1303 
1304         return TextUtils.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID)
1305                 && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID)
1306                 && TextUtils.equals(this.wifiConfiguration.getDefaultSecurityType(),
1307                 lhs.wifiConfiguration.getDefaultSecurityType())
1308                 && TextUtils.equals(this.wifiConfiguration.getPasspointUniqueId(),
1309                 lhs.wifiConfiguration.getPasspointUniqueId())
1310                 && this.wifiConfiguration.carrierId == lhs.wifiConfiguration.carrierId
1311                 && this.wifiConfiguration.subscriptionId == lhs.wifiConfiguration.subscriptionId;
1312     }
1313 
1314     @Override
toString()1315     public String toString() {
1316         StringBuilder sb = new StringBuilder("WifiNetworkSuggestion[ ")
1317                 .append("SSID=").append(wifiConfiguration.SSID)
1318                 .append(", BSSID=").append(wifiConfiguration.BSSID)
1319                 .append(", FQDN=").append(wifiConfiguration.FQDN)
1320                 .append(", SecurityParams=");
1321         wifiConfiguration.getSecurityParamsList().stream()
1322                 .forEach(param -> {
1323                     sb.append(" ");
1324                     sb.append(WifiConfiguration.getSecurityTypeName(param.getSecurityType()));
1325                     if (param.isAddedByAutoUpgrade()) sb.append("^");
1326                 });
1327         sb.append(", isAppInteractionRequired=").append(isAppInteractionRequired)
1328                 .append(", isUserInteractionRequired=").append(isUserInteractionRequired)
1329                 .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
1330                 .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
1331                 .append(", isUnTrusted=").append(!wifiConfiguration.trusted)
1332                 .append(", isOemPaid=").append(wifiConfiguration.oemPaid)
1333                 .append(", isOemPrivate=").append(wifiConfiguration.oemPrivate)
1334                 .append(", isCarrierMerged=").append(wifiConfiguration.carrierMerged)
1335                 .append(", isHiddenSsid=").append(wifiConfiguration.hiddenSSID)
1336                 .append(", priorityGroup=").append(priorityGroup)
1337                 .append(", subscriptionId=").append(wifiConfiguration.subscriptionId)
1338                 .append(", carrierId=").append(wifiConfiguration.carrierId)
1339                 .append(", priority=").append(wifiConfiguration.priority)
1340                 .append(", meteredness=").append(wifiConfiguration.meteredOverride)
1341                 .append(" ]");
1342         return sb.toString();
1343     }
1344 
1345     /**
1346      * Get the {@link WifiConfiguration} associated with this Suggestion.
1347      * @hide
1348      */
1349     @SystemApi
1350     @NonNull
getWifiConfiguration()1351     public WifiConfiguration getWifiConfiguration() {
1352         return wifiConfiguration;
1353     }
1354 
1355     /**
1356      * Get the BSSID, or null if unset.
1357      * @see Builder#setBssid(MacAddress)
1358      */
1359     @Nullable
getBssid()1360     public MacAddress getBssid() {
1361         if (wifiConfiguration.BSSID == null) {
1362             return null;
1363         }
1364         return MacAddress.fromString(wifiConfiguration.BSSID);
1365     }
1366 
1367     /** @see Builder#setCredentialSharedWithUser(boolean) */
isCredentialSharedWithUser()1368     public boolean isCredentialSharedWithUser() {
1369         return isUserAllowedToManuallyConnect;
1370     }
1371 
1372     /** @see Builder#setIsAppInteractionRequired(boolean) */
isAppInteractionRequired()1373     public boolean isAppInteractionRequired() {
1374         return isAppInteractionRequired;
1375     }
1376 
1377     /** @see Builder#setIsEnhancedOpen(boolean)  */
isEnhancedOpen()1378     public boolean isEnhancedOpen() {
1379         return wifiConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE);
1380     }
1381 
1382     /** @see Builder#setIsHiddenSsid(boolean)  */
isHiddenSsid()1383     public boolean isHiddenSsid() {
1384         return wifiConfiguration.hiddenSSID;
1385     }
1386 
1387     /** @see Builder#setIsInitialAutojoinEnabled(boolean)  */
isInitialAutojoinEnabled()1388     public boolean isInitialAutojoinEnabled() {
1389         return isInitialAutoJoinEnabled;
1390     }
1391 
1392     /** @see Builder#setIsMetered(boolean)  */
isMetered()1393     public boolean isMetered() {
1394         return wifiConfiguration.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED;
1395     }
1396 
1397     /** @see Builder#setIsUserInteractionRequired(boolean)  */
isUserInteractionRequired()1398     public boolean isUserInteractionRequired() {
1399         return isUserInteractionRequired;
1400     }
1401 
1402     /**
1403      * Get the {@link PasspointConfiguration} associated with this Suggestion, or null if this
1404      * Suggestion is not for a Passpoint network.
1405      */
1406     @Nullable
getPasspointConfig()1407     public PasspointConfiguration getPasspointConfig() {
1408         return passpointConfiguration;
1409     }
1410 
1411     /** @see Builder#setPriority(int)  */
1412     @IntRange(from = 0)
getPriority()1413     public int getPriority() {
1414         return wifiConfiguration.priority;
1415     }
1416 
1417     /**
1418      * Return the SSID of the network, or null if this is a Passpoint network.
1419      * @see Builder#setSsid(String)
1420      */
1421     @Nullable
getSsid()1422     public String getSsid() {
1423         if (wifiConfiguration.SSID == null) {
1424             return null;
1425         }
1426         return WifiInfo.sanitizeSsid(wifiConfiguration.SSID);
1427     }
1428 
1429     /** @see Builder#setUntrusted(boolean)  */
isUntrusted()1430     public boolean isUntrusted() {
1431         return !wifiConfiguration.trusted;
1432     }
1433 
1434     /**
1435      * @see Builder#setOemPaid(boolean)
1436      * @hide
1437      */
1438     @SystemApi
1439     @RequiresApi(Build.VERSION_CODES.S)
isOemPaid()1440     public boolean isOemPaid() {
1441         if (!SdkLevel.isAtLeastS()) {
1442             throw new UnsupportedOperationException();
1443         }
1444         return wifiConfiguration.oemPaid;
1445     }
1446 
1447     /**
1448      * @see Builder#setOemPrivate(boolean)
1449      * @hide
1450      */
1451     @SystemApi
1452     @RequiresApi(Build.VERSION_CODES.S)
isOemPrivate()1453     public boolean isOemPrivate() {
1454         if (!SdkLevel.isAtLeastS()) {
1455             throw new UnsupportedOperationException();
1456         }
1457         return wifiConfiguration.oemPrivate;
1458     }
1459 
1460     /**
1461      * @see Builder#setCarrierMerged(boolean)
1462      */
1463     @RequiresApi(Build.VERSION_CODES.S)
isCarrierMerged()1464     public boolean isCarrierMerged() {
1465         if (!SdkLevel.isAtLeastS()) {
1466             throw new UnsupportedOperationException();
1467         }
1468         return wifiConfiguration.carrierMerged;
1469     }
1470 
1471     /**
1472      * Get the WifiEnterpriseConfig, or null if unset.
1473      * @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig)
1474      * @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig)
1475      * @see Builder#setWpa3EnterpriseConfig(WifiEnterpriseConfig)
1476      */
1477     @Nullable
getEnterpriseConfig()1478     public WifiEnterpriseConfig getEnterpriseConfig() {
1479         if (!wifiConfiguration.isEnterprise()) {
1480             return null;
1481         }
1482         return wifiConfiguration.enterpriseConfig;
1483     }
1484 
1485     /**
1486      * Get the passphrase, or null if unset.
1487      * @see Builder#setWapiPassphrase(String)
1488      * @see Builder#setWpa2Passphrase(String)
1489      * @see Builder#setWpa3Passphrase(String)
1490      */
1491     @Nullable
getPassphrase()1492     public String getPassphrase() {
1493         if (wifiConfiguration.preSharedKey == null) {
1494             return null;
1495         }
1496         return WifiInfo.removeDoubleQuotes(wifiConfiguration.preSharedKey);
1497     }
1498 
1499     /**
1500      * @see Builder#setPriorityGroup(int)
1501      */
1502     @IntRange(from = 0)
getPriorityGroup()1503     public int getPriorityGroup() {
1504         return priorityGroup;
1505     }
1506 
1507     /**
1508      * @see Builder#setSubscriptionId(int)
1509      */
1510     @RequiresApi(Build.VERSION_CODES.S)
getSubscriptionId()1511     public int getSubscriptionId() {
1512         if (!SdkLevel.isAtLeastS()) {
1513             throw new UnsupportedOperationException();
1514         }
1515         return wifiConfiguration.subscriptionId;
1516     }
1517 
1518     /**
1519      * @see Builder#setCarrierId(int)
1520      * @hide
1521      */
1522     @SystemApi
getCarrierId()1523     public int getCarrierId() {
1524         return wifiConfiguration.carrierId;
1525     }
1526 }
1527