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.IntRange;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SystemApi;
26 import android.net.MacAddress;
27 import android.net.wifi.hotspot2.PasspointConfiguration;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 import android.telephony.TelephonyManager;
31 import android.text.TextUtils;
32 
33 import java.nio.charset.CharsetEncoder;
34 import java.nio.charset.StandardCharsets;
35 import java.util.List;
36 import java.util.Objects;
37 
38 /**
39  * The Network Suggestion object is used to provide a Wi-Fi network for consideration when
40  * auto-connecting to networks. Apps cannot directly create this object, they must use
41  * {@link WifiNetworkSuggestion.Builder#build()} to obtain an instance of this object.
42  *<p>
43  * Apps can provide a list of such networks to the platform using
44  * {@link WifiManager#addNetworkSuggestions(List)}.
45  */
46 public final class WifiNetworkSuggestion implements Parcelable {
47     /**
48      * Builder used to create {@link WifiNetworkSuggestion} objects.
49      */
50     public static final class Builder {
51         private static final int UNASSIGNED_PRIORITY = -1;
52 
53         /**
54          * SSID of the network.
55          */
56         private String mSsid;
57         /**
58          * Optional BSSID within the network.
59          */
60         private MacAddress mBssid;
61         /**
62          * Whether this is an OWE network or not.
63          */
64         private boolean mIsEnhancedOpen;
65         /**
66          * Pre-shared key for use with WPA-PSK networks.
67          */
68         private @Nullable String mWpa2PskPassphrase;
69         /**
70          * Pre-shared key for use with WPA3-SAE networks.
71          */
72         private @Nullable String mWpa3SaePassphrase;
73         /**
74          * The enterprise configuration details specifying the EAP method,
75          * certificates and other settings associated with the WPA-EAP networks.
76          */
77         private @Nullable WifiEnterpriseConfig mWpa2EnterpriseConfig;
78         /**
79          * The enterprise configuration details specifying the EAP method,
80          * certificates and other settings associated with the SuiteB networks.
81          */
82         private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig;
83         /**
84          * The passpoint config for use with Hotspot 2.0 network
85          */
86         private @Nullable PasspointConfiguration mPasspointConfiguration;
87         /**
88          * This is a network that does not broadcast its SSID, so an
89          * SSID-specific probe request must be used for scans.
90          */
91         private boolean mIsHiddenSSID;
92         /**
93          * Whether app needs to log in to captive portal to obtain Internet access.
94          */
95         private boolean mIsAppInteractionRequired;
96         /**
97          * Whether user needs to log in to captive portal to obtain Internet access.
98          */
99         private boolean mIsUserInteractionRequired;
100         /**
101          * Whether this network is metered or not.
102          */
103         private int mMeteredOverride;
104         /**
105          * Priority of this network among other network suggestions provided by the app.
106          * The lower the number, the higher the priority (i.e value of 0 = highest priority).
107          */
108         private int mPriority;
109 
110         /**
111          * The carrier ID identifies the operator who provides this network configuration.
112          *    see {@link TelephonyManager#getSimCarrierId()}
113          */
114         private int mCarrierId;
115 
116         /**
117          * Whether this network is shared credential with user to allow user manually connect.
118          */
119         private boolean mIsSharedWithUser;
120 
121         /**
122          * Whether the setCredentialSharedWithUser have been called.
123          */
124         private boolean mIsSharedWithUserSet;
125 
126         /**
127          * Whether this network is initialized with auto-join enabled (the default) or not.
128          */
129         private boolean mIsInitialAutojoinEnabled;
130 
131         /**
132          * Pre-shared key for use with WAPI-PSK networks.
133          */
134         private @Nullable String mWapiPskPassphrase;
135 
136         /**
137          * The enterprise configuration details specifying the EAP method,
138          * certificates and other settings associated with the WAPI networks.
139          */
140         private @Nullable WifiEnterpriseConfig mWapiEnterpriseConfig;
141 
142         /**
143          * Whether this network will be brought up as untrusted (TRUSTED capability bit removed).
144          */
145         private boolean mIsNetworkUntrusted;
146 
Builder()147         public Builder() {
148             mSsid = null;
149             mBssid =  null;
150             mIsEnhancedOpen = false;
151             mWpa2PskPassphrase = null;
152             mWpa3SaePassphrase = null;
153             mWpa2EnterpriseConfig = null;
154             mWpa3EnterpriseConfig = null;
155             mPasspointConfiguration = null;
156             mIsHiddenSSID = false;
157             mIsAppInteractionRequired = false;
158             mIsUserInteractionRequired = false;
159             mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NONE;
160             mIsSharedWithUser = true;
161             mIsSharedWithUserSet = false;
162             mIsInitialAutojoinEnabled = true;
163             mPriority = UNASSIGNED_PRIORITY;
164             mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
165             mWapiPskPassphrase = null;
166             mWapiEnterpriseConfig = null;
167             mIsNetworkUntrusted = false;
168         }
169 
170         /**
171          * Set the unicode SSID for the network.
172          * <p>
173          * <li>Overrides any previous value set using {@link #setSsid(String)}.</li>
174          *
175          * @param ssid The SSID of the network. It must be valid Unicode.
176          * @return Instance of {@link Builder} to enable chaining of the builder method.
177          * @throws IllegalArgumentException if the SSID is not valid unicode.
178          */
setSsid(@onNull String ssid)179         public @NonNull Builder setSsid(@NonNull String ssid) {
180             checkNotNull(ssid);
181             final CharsetEncoder unicodeEncoder = StandardCharsets.UTF_8.newEncoder();
182             if (!unicodeEncoder.canEncode(ssid)) {
183                 throw new IllegalArgumentException("SSID is not a valid unicode string");
184             }
185             mSsid = new String(ssid);
186             return this;
187         }
188 
189         /**
190          * Set the BSSID to use for filtering networks from scan results. Will only match network
191          * whose BSSID is identical to the specified value.
192          * <p>
193          * <li Sets a specific BSSID for the network suggestion. If set, only the specified BSSID
194          * with the specified SSID will be considered for connection.
195          * <li>If set, only the specified BSSID with the specified SSID will be considered for
196          * connection.</li>
197          * <li>If not set, all BSSIDs with the specified SSID will be considered for connection.
198          * </li>
199          * <li>Overrides any previous value set using {@link #setBssid(MacAddress)}.</li>
200          *
201          * @param bssid BSSID of the network.
202          * @return Instance of {@link Builder} to enable chaining of the builder method.
203          */
setBssid(@onNull MacAddress bssid)204         public @NonNull Builder setBssid(@NonNull MacAddress bssid) {
205             checkNotNull(bssid);
206             mBssid = MacAddress.fromBytes(bssid.toByteArray());
207             return this;
208         }
209 
210         /**
211          * Specifies whether this represents an Enhanced Open (OWE) network.
212          *
213          * @param isEnhancedOpen {@code true} to indicate that the network used enhanced open,
214          *                       {@code false} otherwise.
215          * @return Instance of {@link Builder} to enable chaining of the builder method.
216          */
setIsEnhancedOpen(boolean isEnhancedOpen)217         public @NonNull Builder setIsEnhancedOpen(boolean isEnhancedOpen) {
218             mIsEnhancedOpen = isEnhancedOpen;
219             return this;
220         }
221 
222         /**
223          * Set the ASCII WPA2 passphrase for this network. Needed for authenticating to
224          * WPA2-PSK networks.
225          *
226          * @param passphrase passphrase of the network.
227          * @return Instance of {@link Builder} to enable chaining of the builder method.
228          * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
229          */
setWpa2Passphrase(@onNull String passphrase)230         public @NonNull Builder setWpa2Passphrase(@NonNull String passphrase) {
231             checkNotNull(passphrase);
232             final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
233             if (!asciiEncoder.canEncode(passphrase)) {
234                 throw new IllegalArgumentException("passphrase not ASCII encodable");
235             }
236             mWpa2PskPassphrase = passphrase;
237             return this;
238         }
239 
240         /**
241          * Set the ASCII WPA3 passphrase for this network. Needed for authenticating to WPA3-SAE
242          * networks.
243          *
244          * @param passphrase passphrase of the network.
245          * @return Instance of {@link Builder} to enable chaining of the builder method.
246          * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
247          */
setWpa3Passphrase(@onNull String passphrase)248         public @NonNull Builder setWpa3Passphrase(@NonNull String passphrase) {
249             checkNotNull(passphrase);
250             final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
251             if (!asciiEncoder.canEncode(passphrase)) {
252                 throw new IllegalArgumentException("passphrase not ASCII encodable");
253             }
254             mWpa3SaePassphrase = passphrase;
255             return this;
256         }
257 
258         /**
259          * Set the associated enterprise configuration for this network. Needed for authenticating
260          * to WPA2 enterprise networks. See {@link WifiEnterpriseConfig} for description.
261          *
262          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
263          * @return Instance of {@link Builder} to enable chaining of the builder method.
264          * @throws IllegalArgumentException if configuration CA certificate or
265          *                                  AltSubjectMatch/DomainSuffixMatch is not set.
266          */
setWpa2EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)267         public @NonNull Builder setWpa2EnterpriseConfig(
268                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
269             checkNotNull(enterpriseConfig);
270             if (enterpriseConfig.isInsecure()) {
271                 throw new IllegalArgumentException("Enterprise configuration is insecure");
272             }
273             mWpa2EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
274             return this;
275         }
276 
277         /**
278          * Set the associated enterprise configuration for this network. Needed for authenticating
279          * to WPA3 enterprise networks. See {@link WifiEnterpriseConfig} for description.
280          *
281          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
282          * @return Instance of {@link Builder} to enable chaining of the builder method.
283          * @throws IllegalArgumentException if configuration CA certificate or
284          *                                  AltSubjectMatch/DomainSuffixMatch is not set.
285          */
setWpa3EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)286         public @NonNull Builder setWpa3EnterpriseConfig(
287                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
288             checkNotNull(enterpriseConfig);
289             if (enterpriseConfig.isInsecure()) {
290                 throw new IllegalArgumentException("Enterprise configuration is insecure");
291             }
292             mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
293             return this;
294         }
295 
296         /**
297          * Set the associated Passpoint configuration for this network. Needed for authenticating
298          * to Hotspot 2.0 networks. See {@link PasspointConfiguration} for description.
299          *
300          * @param passpointConfig Instance of {@link PasspointConfiguration}.
301          * @return Instance of {@link Builder} to enable chaining of the builder method.
302          * @throws IllegalArgumentException if passpoint configuration is invalid.
303          */
setPasspointConfig( @onNull PasspointConfiguration passpointConfig)304         public @NonNull Builder setPasspointConfig(
305                 @NonNull PasspointConfiguration passpointConfig) {
306             checkNotNull(passpointConfig);
307             if (!passpointConfig.validate()) {
308                 throw new IllegalArgumentException("Passpoint configuration is invalid");
309             }
310             mPasspointConfiguration = passpointConfig;
311             return this;
312         }
313 
314         /**
315          * Set the carrier ID of the network operator. The carrier ID associates a Suggested
316          * network with a specific carrier (and therefore SIM). The carrier ID must be provided
317          * for any network which uses the SIM-based authentication: e.g. EAP-SIM, EAP-AKA,
318          * EAP-AKA', and EAP-PEAP with SIM-based phase 2 authentication.
319          * @param carrierId see {@link TelephonyManager#getSimCarrierId()}.
320          * @return Instance of {@link Builder} to enable chaining of the builder method.
321          *
322          * @hide
323          */
324         @SystemApi
325         @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING)
setCarrierId(int carrierId)326         public @NonNull Builder setCarrierId(int carrierId) {
327             mCarrierId = carrierId;
328             return this;
329         }
330 
331         /**
332          * Set the ASCII WAPI passphrase for this network. Needed for authenticating to
333          * WAPI-PSK networks.
334          *
335          * @param passphrase passphrase of the network.
336          * @return Instance of {@link Builder} to enable chaining of the builder method.
337          * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
338          *
339          */
setWapiPassphrase(@onNull String passphrase)340         public @NonNull Builder setWapiPassphrase(@NonNull String passphrase) {
341             checkNotNull(passphrase);
342             final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
343             if (!asciiEncoder.canEncode(passphrase)) {
344                 throw new IllegalArgumentException("passphrase not ASCII encodable");
345             }
346             mWapiPskPassphrase = passphrase;
347             return this;
348         }
349 
350         /**
351          * Set the associated enterprise configuration for this network. Needed for authenticating
352          * to WAPI-CERT networks. See {@link WifiEnterpriseConfig} for description.
353          *
354          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
355          * @return Instance of {@link Builder} to enable chaining of the builder method.
356          */
setWapiEnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)357         public @NonNull Builder setWapiEnterpriseConfig(
358                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
359             checkNotNull(enterpriseConfig);
360             mWapiEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
361             return this;
362         }
363 
364         /**
365          * Specifies whether this represents a hidden network.
366          * <p>
367          * <li>If not set, defaults to false (i.e not a hidden network).</li>
368          *
369          * @param isHiddenSsid {@code true} to indicate that the network is hidden, {@code false}
370          *                     otherwise.
371          * @return Instance of {@link Builder} to enable chaining of the builder method.
372          */
setIsHiddenSsid(boolean isHiddenSsid)373         public @NonNull Builder setIsHiddenSsid(boolean isHiddenSsid) {
374             mIsHiddenSSID = isHiddenSsid;
375             return this;
376         }
377 
378         /**
379          * Specifies whether the app needs to log in to a captive portal to obtain Internet access.
380          * <p>
381          * This will dictate if the directed broadcast
382          * {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} will be sent to the
383          * app after successfully connecting to the network.
384          * Use this for captive portal type networks where the app needs to authenticate the user
385          * before the device can access the network.
386          * <p>
387          * <li>If not set, defaults to false (i.e no app interaction required).</li>
388          *
389          * @param isAppInteractionRequired {@code true} to indicate that app interaction is
390          *                                 required, {@code false} otherwise.
391          * @return Instance of {@link Builder} to enable chaining of the builder method.
392          */
setIsAppInteractionRequired(boolean isAppInteractionRequired)393         public @NonNull Builder setIsAppInteractionRequired(boolean isAppInteractionRequired) {
394             mIsAppInteractionRequired = isAppInteractionRequired;
395             return this;
396         }
397 
398         /**
399          * Specifies whether the user needs to log in to a captive portal to obtain Internet access.
400          * <p>
401          * <li>If not set, defaults to false (i.e no user interaction required).</li>
402          *
403          * @param isUserInteractionRequired {@code true} to indicate that user interaction is
404          *                                  required, {@code false} otherwise.
405          * @return Instance of {@link Builder} to enable chaining of the builder method.
406          */
setIsUserInteractionRequired(boolean isUserInteractionRequired)407         public @NonNull Builder setIsUserInteractionRequired(boolean isUserInteractionRequired) {
408             mIsUserInteractionRequired = isUserInteractionRequired;
409             return this;
410         }
411 
412         /**
413          * Specify the priority of this network among other network suggestions provided by the same
414          * app (priorities have no impact on suggestions by different apps). The higher the number,
415          * the higher the priority (i.e value of 0 = lowest priority).
416          * <p>
417          * <li>If not set, defaults a lower priority than any assigned priority.</li>
418          *
419          * @param priority Integer number representing the priority among suggestions by the app.
420          * @return Instance of {@link Builder} to enable chaining of the builder method.
421          * @throws IllegalArgumentException if the priority value is negative.
422          */
setPriority(@ntRangefrom = 0) int priority)423         public @NonNull Builder setPriority(@IntRange(from = 0) int priority) {
424             if (priority < 0) {
425                 throw new IllegalArgumentException("Invalid priority value " + priority);
426             }
427             mPriority = priority;
428             return this;
429         }
430 
431         /**
432          * Specifies whether this network is metered.
433          * <p>
434          * <li>If not set, defaults to detect automatically.</li>
435          *
436          * @param isMetered {@code true} to indicate that the network is metered, {@code false}
437          *                  for not metered.
438          * @return Instance of {@link Builder} to enable chaining of the builder method.
439          */
setIsMetered(boolean isMetered)440         public @NonNull Builder setIsMetered(boolean isMetered) {
441             if (isMetered) {
442                 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
443             } else {
444                 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
445             }
446             return this;
447         }
448 
449         /**
450          * Specifies whether the network credentials provided with this suggestion can be used by
451          * the user to explicitly (manually) connect to this network. If true this network will
452          * appear in the Wi-Fi Picker (in Settings) and the user will be able to select and connect
453          * to it with the provided credentials. If false, the user will need to enter network
454          * credentials and the resulting configuration will become a user saved network.
455          * <p>
456          * <li>Note: Only valid for secure (non-open) networks.
457          * <li>If not set, defaults to true (i.e. allow user to manually connect) for secure
458          * networks and false for open networks.</li>
459          *
460          * @param isShared {@code true} to indicate that the credentials may be used by the user to
461          *                              manually connect to the network, {@code false} otherwise.
462          * @return Instance of {@link Builder} to enable chaining of the builder method.
463          */
setCredentialSharedWithUser(boolean isShared)464         public @NonNull Builder setCredentialSharedWithUser(boolean isShared) {
465             mIsSharedWithUser = isShared;
466             mIsSharedWithUserSet = true;
467             return this;
468         }
469 
470         /**
471          * Specifies whether the suggestion is created with auto-join enabled or disabled. The
472          * user may modify the auto-join configuration of a suggestion directly once the device
473          * associates to the network.
474          * <p>
475          * If auto-join is initialized as disabled the user may still be able to manually connect
476          * to the network. Therefore, disabling auto-join only makes sense if
477          * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which
478          * itself implies a secure (non-open) network.
479          * <p>
480          * If not set, defaults to true (i.e. auto-join is initialized as enabled).
481          *
482          * @param enabled true for initializing with auto-join enabled (the default), false to
483          *                initializing with auto-join disabled.
484          * @return Instance of {@link Builder} to enable chaining of the builder method.
485          */
setIsInitialAutojoinEnabled(boolean enabled)486         public @NonNull Builder setIsInitialAutojoinEnabled(boolean enabled) {
487             mIsInitialAutojoinEnabled = enabled;
488             return this;
489         }
490 
491         /**
492          * Specifies whether the system will bring up the network (if selected) as untrusted. An
493          * untrusted network has its {@link android.net.NetworkCapabilities#NET_CAPABILITY_TRUSTED}
494          * capability removed. The Wi-Fi network selection process may use this information to
495          * influence priority of the suggested network for Wi-Fi network selection (most likely to
496          * reduce it). The connectivity service may use this information to influence the overall
497          * network configuration of the device.
498          * <p>
499          * <li> An untrusted network's credentials may not be shared with the user using
500          * {@link #setCredentialSharedWithUser(boolean)}.</li>
501          * <li> If not set, defaults to false (i.e. network is trusted).</li>
502          *
503          * @param isUntrusted Boolean indicating whether the network should be brought up untrusted
504          *                    (if true) or trusted (if false).
505          * @return Instance of {@link Builder} to enable chaining of the builder method.
506          */
setUntrusted(boolean isUntrusted)507         public @NonNull Builder setUntrusted(boolean isUntrusted) {
508             mIsNetworkUntrusted = isUntrusted;
509             return this;
510         }
511 
setSecurityParamsInWifiConfiguration( @onNull WifiConfiguration configuration)512         private void setSecurityParamsInWifiConfiguration(
513                 @NonNull WifiConfiguration configuration) {
514             if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
515                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
516                 // WifiConfiguration.preSharedKey needs quotes around ASCII password.
517                 configuration.preSharedKey = "\"" + mWpa2PskPassphrase + "\"";
518             } else if (!TextUtils.isEmpty(mWpa3SaePassphrase)) { // WPA3-SAE network.
519                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
520                 // WifiConfiguration.preSharedKey needs quotes around ASCII password.
521                 configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\"";
522             } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network
523                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
524                 configuration.enterpriseConfig = mWpa2EnterpriseConfig;
525             } else if (mWpa3EnterpriseConfig != null) { // WPA3-SuiteB network
526                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
527                 configuration.enterpriseConfig = mWpa3EnterpriseConfig;
528             } else if (mIsEnhancedOpen) { // OWE network
529                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
530             } else if (!TextUtils.isEmpty(mWapiPskPassphrase)) { // WAPI-PSK network.
531                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_PSK);
532                 // WifiConfiguration.preSharedKey needs quotes around ASCII password.
533                 configuration.preSharedKey = "\"" + mWapiPskPassphrase + "\"";
534             } else if (mWapiEnterpriseConfig != null) { // WAPI-CERT network
535                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT);
536                 configuration.enterpriseConfig = mWapiEnterpriseConfig;
537             } else { // Open network
538                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
539             }
540         }
541 
542         /**
543          * Helper method to build WifiConfiguration object from the builder.
544          * @return Instance of {@link WifiConfiguration}.
545          */
buildWifiConfiguration()546         private WifiConfiguration buildWifiConfiguration() {
547             final WifiConfiguration wifiConfiguration = new WifiConfiguration();
548             // WifiConfiguration.SSID needs quotes around unicode SSID.
549             wifiConfiguration.SSID = "\"" + mSsid + "\"";
550             if (mBssid != null) {
551                 wifiConfiguration.BSSID = mBssid.toString();
552             }
553 
554             setSecurityParamsInWifiConfiguration(wifiConfiguration);
555 
556             wifiConfiguration.hiddenSSID = mIsHiddenSSID;
557             wifiConfiguration.priority = mPriority;
558             wifiConfiguration.meteredOverride = mMeteredOverride;
559             wifiConfiguration.carrierId = mCarrierId;
560             wifiConfiguration.trusted = !mIsNetworkUntrusted;
561             return wifiConfiguration;
562         }
563 
validateSecurityParams()564         private void validateSecurityParams() {
565             int numSecurityTypes = 0;
566             numSecurityTypes += mIsEnhancedOpen ? 1 : 0;
567             numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0;
568             numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0;
569             numSecurityTypes += !TextUtils.isEmpty(mWapiPskPassphrase) ? 1 : 0;
570             numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0;
571             numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0;
572             numSecurityTypes += mWapiEnterpriseConfig != null ? 1 : 0;
573             numSecurityTypes += mPasspointConfiguration != null ? 1 : 0;
574             if (numSecurityTypes > 1) {
575                 throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase,"
576                         + " setWpa3Passphrase, setWpa2EnterpriseConfig, setWpa3EnterpriseConfig"
577                         + " setWapiPassphrase, setWapiCertSuite, setIsWapiCertSuiteAuto"
578                         + " or setPasspointConfig can be invoked for network suggestion");
579             }
580         }
581 
buildWifiConfigurationForPasspoint()582         private WifiConfiguration buildWifiConfigurationForPasspoint() {
583             WifiConfiguration wifiConfiguration = new WifiConfiguration();
584             wifiConfiguration.FQDN = mPasspointConfiguration.getHomeSp().getFqdn();
585             wifiConfiguration.setPasspointUniqueId(mPasspointConfiguration.getUniqueId());
586             wifiConfiguration.priority = mPriority;
587             wifiConfiguration.meteredOverride = mMeteredOverride;
588             wifiConfiguration.trusted = !mIsNetworkUntrusted;
589             mPasspointConfiguration.setCarrierId(mCarrierId);
590             mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride);
591             return wifiConfiguration;
592         }
593 
594         /**
595          * Create a network suggestion object for use in
596          * {@link WifiManager#addNetworkSuggestions(List)}.
597          *
598          *<p class="note">
599          * <b>Note:</b> Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID
600          * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to
601          * the platform.
602          * </p>
603          *
604          * For example:
605          * To provide credentials for one open, one WPA2, one WPA3 network with their
606          * corresponding SSID's and one with Passpoint config:
607          *
608          * <pre>{@code
609          * final WifiNetworkSuggestion suggestion1 =
610          *      new Builder()
611          *      .setSsid("test111111")
612          *      .build();
613          * final WifiNetworkSuggestion suggestion2 =
614          *      new Builder()
615          *      .setSsid("test222222")
616          *      .setWpa2Passphrase("test123456")
617          *      .build();
618          * final WifiNetworkSuggestion suggestion3 =
619          *      new Builder()
620          *      .setSsid("test333333")
621          *      .setWpa3Passphrase("test6789")
622          *      .build();
623          * final PasspointConfiguration passpointConfig= new PasspointConfiguration();
624          * // configure passpointConfig to include a valid Passpoint configuration
625          * final WifiNetworkSuggestion suggestion4 =
626          *      new Builder()
627          *      .setPasspointConfig(passpointConfig)
628          *      .build();
629          * final List<WifiNetworkSuggestion> suggestionsList =
630          *      new ArrayList<WifiNetworkSuggestion> { {
631          *          add(suggestion1);
632          *          add(suggestion2);
633          *          add(suggestion3);
634          *          add(suggestion4);
635          *      } };
636          * final WifiManager wifiManager =
637          *      context.getSystemService(Context.WIFI_SERVICE);
638          * wifiManager.addNetworkSuggestions(suggestionsList);
639          * // ...
640          * }</pre>
641          *
642          * @return Instance of {@link WifiNetworkSuggestion}
643          * @throws IllegalStateException on invalid params set
644          * @see WifiNetworkSuggestion
645          */
build()646         public @NonNull WifiNetworkSuggestion build() {
647             validateSecurityParams();
648             WifiConfiguration wifiConfiguration;
649             if (mPasspointConfiguration != null) {
650                 if (mSsid != null) {
651                     throw new IllegalStateException("setSsid should not be invoked for suggestion "
652                             + "with Passpoint configuration");
653                 }
654                 if (mIsHiddenSSID) {
655                     throw new IllegalStateException("setIsHiddenSsid should not be invoked for "
656                             + "suggestion with Passpoint configuration");
657                 }
658                 wifiConfiguration = buildWifiConfigurationForPasspoint();
659             } else {
660                 if (mSsid == null) {
661                     throw new IllegalStateException("setSsid should be invoked for suggestion");
662                 }
663                 if (TextUtils.isEmpty(mSsid)) {
664                     throw new IllegalStateException("invalid ssid for suggestion");
665                 }
666                 if (mBssid != null
667                         && (mBssid.equals(MacAddress.BROADCAST_ADDRESS)
668                         || mBssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS))) {
669                     throw new IllegalStateException("invalid bssid for suggestion");
670                 }
671                 wifiConfiguration = buildWifiConfiguration();
672                 if (wifiConfiguration.isOpenNetwork()) {
673                     if (mIsSharedWithUserSet && mIsSharedWithUser) {
674                         throw new IllegalStateException("Open network should not be "
675                                 + "setCredentialSharedWithUser to true");
676                     }
677                     mIsSharedWithUser = false;
678                 }
679             }
680             if (!mIsSharedWithUser && !mIsInitialAutojoinEnabled) {
681                 throw new IllegalStateException("Should have not a network with both "
682                         + "setCredentialSharedWithUser and "
683                         + "setIsAutojoinEnabled set to false");
684             }
685             if (mIsNetworkUntrusted) {
686                 if (mIsSharedWithUserSet && mIsSharedWithUser) {
687                     throw new IllegalStateException("Should not be both"
688                             + "setCredentialSharedWithUser and +"
689                             + "setIsNetworkAsUntrusted to true");
690                 }
691                 mIsSharedWithUser = false;
692             }
693             return new WifiNetworkSuggestion(
694                     wifiConfiguration,
695                     mPasspointConfiguration,
696                     mIsAppInteractionRequired,
697                     mIsUserInteractionRequired,
698                     mIsSharedWithUser,
699                     mIsInitialAutojoinEnabled);
700         }
701     }
702 
703     /**
704      * Network configuration for the provided network.
705      * @hide
706      */
707     @NonNull
708     public final WifiConfiguration wifiConfiguration;
709 
710     /**
711      * Passpoint configuration for the provided network.
712      * @hide
713      */
714     @Nullable
715     public final PasspointConfiguration passpointConfiguration;
716 
717     /**
718      * Whether app needs to log in to captive portal to obtain Internet access.
719      * @hide
720      */
721     public final boolean isAppInteractionRequired;
722 
723     /**
724      * Whether user needs to log in to captive portal to obtain Internet access.
725      * @hide
726      */
727     public final boolean isUserInteractionRequired;
728 
729     /**
730      * Whether app share credential with the user, allow user use provided credential to
731      * connect network manually.
732      * @hide
733      */
734     public final boolean isUserAllowedToManuallyConnect;
735 
736     /**
737      * Whether the suggestion will be initialized as auto-joined or not.
738      * @hide
739      */
740     public final boolean isInitialAutoJoinEnabled;
741 
742     /** @hide */
WifiNetworkSuggestion()743     public WifiNetworkSuggestion() {
744         this.wifiConfiguration = new WifiConfiguration();
745         this.passpointConfiguration = null;
746         this.isAppInteractionRequired = false;
747         this.isUserInteractionRequired = false;
748         this.isUserAllowedToManuallyConnect = true;
749         this.isInitialAutoJoinEnabled = true;
750     }
751 
752     /** @hide */
WifiNetworkSuggestion(@onNull WifiConfiguration networkConfiguration, @Nullable PasspointConfiguration passpointConfiguration, boolean isAppInteractionRequired, boolean isUserInteractionRequired, boolean isUserAllowedToManuallyConnect, boolean isInitialAutoJoinEnabled)753     public WifiNetworkSuggestion(@NonNull WifiConfiguration networkConfiguration,
754                                  @Nullable PasspointConfiguration passpointConfiguration,
755                                  boolean isAppInteractionRequired,
756                                  boolean isUserInteractionRequired,
757                                  boolean isUserAllowedToManuallyConnect,
758                                  boolean isInitialAutoJoinEnabled) {
759         checkNotNull(networkConfiguration);
760         this.wifiConfiguration = networkConfiguration;
761         this.passpointConfiguration = passpointConfiguration;
762 
763         this.isAppInteractionRequired = isAppInteractionRequired;
764         this.isUserInteractionRequired = isUserInteractionRequired;
765         this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect;
766         this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled;
767     }
768 
769     public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
770             new Creator<WifiNetworkSuggestion>() {
771                 @Override
772                 public WifiNetworkSuggestion createFromParcel(Parcel in) {
773                     return new WifiNetworkSuggestion(
774                             in.readParcelable(null), // wifiConfiguration
775                             in.readParcelable(null), // PasspointConfiguration
776                             in.readBoolean(), // isAppInteractionRequired
777                             in.readBoolean(), // isUserInteractionRequired
778                             in.readBoolean(), // isSharedCredentialWithUser
779                             in.readBoolean()  // isAutojoinEnabled
780                     );
781                 }
782 
783                 @Override
784                 public WifiNetworkSuggestion[] newArray(int size) {
785                     return new WifiNetworkSuggestion[size];
786                 }
787             };
788 
789     @Override
describeContents()790     public int describeContents() {
791         return 0;
792     }
793 
794     @Override
writeToParcel(Parcel dest, int flags)795     public void writeToParcel(Parcel dest, int flags) {
796         dest.writeParcelable(wifiConfiguration, flags);
797         dest.writeParcelable(passpointConfiguration, flags);
798         dest.writeBoolean(isAppInteractionRequired);
799         dest.writeBoolean(isUserInteractionRequired);
800         dest.writeBoolean(isUserAllowedToManuallyConnect);
801         dest.writeBoolean(isInitialAutoJoinEnabled);
802     }
803 
804     @Override
hashCode()805     public int hashCode() {
806         return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
807                 wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey());
808     }
809 
810     /**
811      * Equals for network suggestions.
812      */
813     @Override
equals(Object obj)814     public boolean equals(Object obj) {
815         if (this == obj) {
816             return true;
817         }
818         if (!(obj instanceof WifiNetworkSuggestion)) {
819             return false;
820         }
821         WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj;
822         if (this.passpointConfiguration == null ^ lhs.passpointConfiguration == null) {
823             return false;
824         }
825 
826         return TextUtils.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID)
827                 && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID)
828                 && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
829                 lhs.wifiConfiguration.allowedKeyManagement)
830                 && TextUtils.equals(this.wifiConfiguration.getKey(),
831                 lhs.wifiConfiguration.getKey());
832     }
833 
834     @Override
toString()835     public String toString() {
836         StringBuilder sb = new StringBuilder("WifiNetworkSuggestion[ ")
837                 .append("SSID=").append(wifiConfiguration.SSID)
838                 .append(", BSSID=").append(wifiConfiguration.BSSID)
839                 .append(", FQDN=").append(wifiConfiguration.FQDN)
840                 .append(", isAppInteractionRequired=").append(isAppInteractionRequired)
841                 .append(", isUserInteractionRequired=").append(isUserInteractionRequired)
842                 .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
843                 .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
844                 .append(", isUnTrusted=").append(!wifiConfiguration.trusted)
845                 .append(" ]");
846         return sb.toString();
847     }
848 
849     /**
850      * Get the {@link WifiConfiguration} associated with this Suggestion.
851      * @hide
852      */
853     @SystemApi
854     @NonNull
getWifiConfiguration()855     public WifiConfiguration getWifiConfiguration() {
856         return wifiConfiguration;
857     }
858 
859     /**
860      * Get the BSSID, or null if unset.
861      * @see Builder#setBssid(MacAddress)
862      */
863     @Nullable
getBssid()864     public MacAddress getBssid() {
865         if (wifiConfiguration.BSSID == null) {
866             return null;
867         }
868         return MacAddress.fromString(wifiConfiguration.BSSID);
869     }
870 
871     /** @see Builder#setCredentialSharedWithUser(boolean) */
isCredentialSharedWithUser()872     public boolean isCredentialSharedWithUser() {
873         return isUserAllowedToManuallyConnect;
874     }
875 
876     /** @see Builder#setIsAppInteractionRequired(boolean) */
isAppInteractionRequired()877     public boolean isAppInteractionRequired() {
878         return isAppInteractionRequired;
879     }
880 
881     /** @see Builder#setIsEnhancedOpen(boolean)  */
isEnhancedOpen()882     public boolean isEnhancedOpen() {
883         return wifiConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE);
884     }
885 
886     /** @see Builder#setIsHiddenSsid(boolean)  */
isHiddenSsid()887     public boolean isHiddenSsid() {
888         return wifiConfiguration.hiddenSSID;
889     }
890 
891     /** @see Builder#setIsInitialAutojoinEnabled(boolean)  */
isInitialAutojoinEnabled()892     public boolean isInitialAutojoinEnabled() {
893         return isInitialAutoJoinEnabled;
894     }
895 
896     /** @see Builder#setIsMetered(boolean)  */
isMetered()897     public boolean isMetered() {
898         return wifiConfiguration.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED;
899     }
900 
901     /** @see Builder#setIsUserInteractionRequired(boolean)  */
isUserInteractionRequired()902     public boolean isUserInteractionRequired() {
903         return isUserInteractionRequired;
904     }
905 
906     /**
907      * Get the {@link PasspointConfiguration} associated with this Suggestion, or null if this
908      * Suggestion is not for a Passpoint network.
909      */
910     @Nullable
getPasspointConfig()911     public PasspointConfiguration getPasspointConfig() {
912         return passpointConfiguration;
913     }
914 
915     /** @see Builder#setPriority(int)  */
916     @IntRange(from = 0)
getPriority()917     public int getPriority() {
918         return wifiConfiguration.priority;
919     }
920 
921     /**
922      * Return the SSID of the network, or null if this is a Passpoint network.
923      * @see Builder#setSsid(String)
924      */
925     @Nullable
getSsid()926     public String getSsid() {
927         if (wifiConfiguration.SSID == null) {
928             return null;
929         }
930         return WifiInfo.sanitizeSsid(wifiConfiguration.SSID);
931     }
932 
933     /** @see Builder#setUntrusted(boolean)  */
isUntrusted()934     public boolean isUntrusted() {
935         return !wifiConfiguration.trusted;
936     }
937 
938     /**
939      * Get the WifiEnterpriseConfig, or null if unset.
940      * @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig)
941      * @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig)
942      * @see Builder#setWpa3EnterpriseConfig(WifiEnterpriseConfig)
943      */
944     @Nullable
getEnterpriseConfig()945     public WifiEnterpriseConfig getEnterpriseConfig() {
946         return wifiConfiguration.enterpriseConfig;
947     }
948 
949     /**
950      * Get the passphrase, or null if unset.
951      * @see Builder#setWapiPassphrase(String)
952      * @see Builder#setWpa2Passphrase(String)
953      * @see Builder#setWpa3Passphrase(String)
954      */
955     @Nullable
getPassphrase()956     public String getPassphrase() {
957         if (wifiConfiguration.preSharedKey == null) {
958             return null;
959         }
960         return WifiInfo.removeDoubleQuotes(wifiConfiguration.preSharedKey);
961     }
962 }
963