• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.wifi;
18 
19 import static com.android.internal.util.Preconditions.checkNotNull;
20 
21 import android.annotation.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/WPA2-Enterprise 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 WPA3-Enterprise 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 (standard and 192-bit security). See
280          * {@link WifiEnterpriseConfig} for description. For 192-bit security networks, both the
281          * client and CA certificates must be provided, and must be of type of either
282          * sha384WithRSAEncryption (OID 1.2.840.113549.1.1.12) or ecdsa-with-SHA384
283          * (OID 1.2.840.10045.4.3.3).
284          *
285          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
286          * @return Instance of {@link Builder} to enable chaining of the builder method.
287          * @throws IllegalArgumentException if configuration CA certificate or
288          *                                  AltSubjectMatch/DomainSuffixMatch is not set.
289          */
setWpa3EnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)290         public @NonNull Builder setWpa3EnterpriseConfig(
291                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
292             checkNotNull(enterpriseConfig);
293             if (enterpriseConfig.isInsecure()) {
294                 throw new IllegalArgumentException("Enterprise configuration is insecure");
295             }
296             mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
297             return this;
298         }
299 
300         /**
301          * Set the associated Passpoint configuration for this network. Needed for authenticating
302          * to Hotspot 2.0 networks. See {@link PasspointConfiguration} for description.
303          *
304          * @param passpointConfig Instance of {@link PasspointConfiguration}.
305          * @return Instance of {@link Builder} to enable chaining of the builder method.
306          * @throws IllegalArgumentException if passpoint configuration is invalid.
307          */
setPasspointConfig( @onNull PasspointConfiguration passpointConfig)308         public @NonNull Builder setPasspointConfig(
309                 @NonNull PasspointConfiguration passpointConfig) {
310             checkNotNull(passpointConfig);
311             if (!passpointConfig.validate()) {
312                 throw new IllegalArgumentException("Passpoint configuration is invalid");
313             }
314             mPasspointConfiguration = passpointConfig;
315             return this;
316         }
317 
318         /**
319          * Set the carrier ID of the network operator. The carrier ID associates a Suggested
320          * network with a specific carrier (and therefore SIM). The carrier ID must be provided
321          * for any network which uses the SIM-based authentication: e.g. EAP-SIM, EAP-AKA,
322          * EAP-AKA', and EAP-PEAP with SIM-based phase 2 authentication.
323          * @param carrierId see {@link TelephonyManager#getSimCarrierId()}.
324          * @return Instance of {@link Builder} to enable chaining of the builder method.
325          *
326          * @hide
327          */
328         @SystemApi
329         @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING)
setCarrierId(int carrierId)330         public @NonNull Builder setCarrierId(int carrierId) {
331             mCarrierId = carrierId;
332             return this;
333         }
334 
335         /**
336          * Set the ASCII WAPI passphrase for this network. Needed for authenticating to
337          * WAPI-PSK networks.
338          *
339          * @param passphrase passphrase of the network.
340          * @return Instance of {@link Builder} to enable chaining of the builder method.
341          * @throws IllegalArgumentException if the passphrase is not ASCII encodable.
342          *
343          */
setWapiPassphrase(@onNull String passphrase)344         public @NonNull Builder setWapiPassphrase(@NonNull String passphrase) {
345             checkNotNull(passphrase);
346             final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
347             if (!asciiEncoder.canEncode(passphrase)) {
348                 throw new IllegalArgumentException("passphrase not ASCII encodable");
349             }
350             mWapiPskPassphrase = passphrase;
351             return this;
352         }
353 
354         /**
355          * Set the associated enterprise configuration for this network. Needed for authenticating
356          * to WAPI-CERT networks. See {@link WifiEnterpriseConfig} for description.
357          *
358          * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}.
359          * @return Instance of {@link Builder} to enable chaining of the builder method.
360          */
setWapiEnterpriseConfig( @onNull WifiEnterpriseConfig enterpriseConfig)361         public @NonNull Builder setWapiEnterpriseConfig(
362                 @NonNull WifiEnterpriseConfig enterpriseConfig) {
363             checkNotNull(enterpriseConfig);
364             mWapiEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
365             return this;
366         }
367 
368         /**
369          * Specifies whether this represents a hidden network.
370          * <p>
371          * <li>If not set, defaults to false (i.e not a hidden network).</li>
372          *
373          * @param isHiddenSsid {@code true} to indicate that the network is hidden, {@code false}
374          *                     otherwise.
375          * @return Instance of {@link Builder} to enable chaining of the builder method.
376          */
setIsHiddenSsid(boolean isHiddenSsid)377         public @NonNull Builder setIsHiddenSsid(boolean isHiddenSsid) {
378             mIsHiddenSSID = isHiddenSsid;
379             return this;
380         }
381 
382         /**
383          * Specifies whether the app needs to log in to a captive portal to obtain Internet access.
384          * <p>
385          * This will dictate if the directed broadcast
386          * {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} will be sent to the
387          * app after successfully connecting to the network.
388          * Use this for captive portal type networks where the app needs to authenticate the user
389          * before the device can access the network.
390          * <p>
391          * <li>If not set, defaults to false (i.e no app interaction required).</li>
392          *
393          * @param isAppInteractionRequired {@code true} to indicate that app interaction is
394          *                                 required, {@code false} otherwise.
395          * @return Instance of {@link Builder} to enable chaining of the builder method.
396          */
setIsAppInteractionRequired(boolean isAppInteractionRequired)397         public @NonNull Builder setIsAppInteractionRequired(boolean isAppInteractionRequired) {
398             mIsAppInteractionRequired = isAppInteractionRequired;
399             return this;
400         }
401 
402         /**
403          * Specifies whether the user needs to log in to a captive portal to obtain Internet access.
404          * <p>
405          * <li>If not set, defaults to false (i.e no user interaction required).</li>
406          *
407          * @param isUserInteractionRequired {@code true} to indicate that user interaction is
408          *                                  required, {@code false} otherwise.
409          * @return Instance of {@link Builder} to enable chaining of the builder method.
410          */
setIsUserInteractionRequired(boolean isUserInteractionRequired)411         public @NonNull Builder setIsUserInteractionRequired(boolean isUserInteractionRequired) {
412             mIsUserInteractionRequired = isUserInteractionRequired;
413             return this;
414         }
415 
416         /**
417          * Specify the priority of this network among other network suggestions provided by the same
418          * app (priorities have no impact on suggestions by different apps). The higher the number,
419          * the higher the priority (i.e value of 0 = lowest priority).
420          * <p>
421          * <li>If not set, defaults a lower priority than any assigned priority.</li>
422          *
423          * @param priority Integer number representing the priority among suggestions by the app.
424          * @return Instance of {@link Builder} to enable chaining of the builder method.
425          * @throws IllegalArgumentException if the priority value is negative.
426          */
setPriority(@ntRangefrom = 0) int priority)427         public @NonNull Builder setPriority(@IntRange(from = 0) int priority) {
428             if (priority < 0) {
429                 throw new IllegalArgumentException("Invalid priority value " + priority);
430             }
431             mPriority = priority;
432             return this;
433         }
434 
435         /**
436          * Specifies whether this network is metered.
437          * <p>
438          * <li>If not set, defaults to detect automatically.</li>
439          *
440          * @param isMetered {@code true} to indicate that the network is metered, {@code false}
441          *                  for not metered.
442          * @return Instance of {@link Builder} to enable chaining of the builder method.
443          */
setIsMetered(boolean isMetered)444         public @NonNull Builder setIsMetered(boolean isMetered) {
445             if (isMetered) {
446                 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED;
447             } else {
448                 mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED;
449             }
450             return this;
451         }
452 
453         /**
454          * Specifies whether the network credentials provided with this suggestion can be used by
455          * the user to explicitly (manually) connect to this network. If true this network will
456          * appear in the Wi-Fi Picker (in Settings) and the user will be able to select and connect
457          * to it with the provided credentials. If false, the user will need to enter network
458          * credentials and the resulting configuration will become a user saved network.
459          * <p>
460          * <li>Note: Only valid for secure (non-open) networks.
461          * <li>If not set, defaults to true (i.e. allow user to manually connect) for secure
462          * networks and false for open networks.</li>
463          *
464          * @param isShared {@code true} to indicate that the credentials may be used by the user to
465          *                              manually connect to the network, {@code false} otherwise.
466          * @return Instance of {@link Builder} to enable chaining of the builder method.
467          */
setCredentialSharedWithUser(boolean isShared)468         public @NonNull Builder setCredentialSharedWithUser(boolean isShared) {
469             mIsSharedWithUser = isShared;
470             mIsSharedWithUserSet = true;
471             return this;
472         }
473 
474         /**
475          * Specifies whether the suggestion is created with auto-join enabled or disabled. The
476          * user may modify the auto-join configuration of a suggestion directly once the device
477          * associates to the network.
478          * <p>
479          * If auto-join is initialized as disabled the user may still be able to manually connect
480          * to the network. Therefore, disabling auto-join only makes sense if
481          * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which
482          * itself implies a secure (non-open) network.
483          * <p>
484          * If not set, defaults to true (i.e. auto-join is initialized as enabled).
485          *
486          * @param enabled true for initializing with auto-join enabled (the default), false to
487          *                initializing with auto-join disabled.
488          * @return Instance of {@link Builder} to enable chaining of the builder method.
489          */
setIsInitialAutojoinEnabled(boolean enabled)490         public @NonNull Builder setIsInitialAutojoinEnabled(boolean enabled) {
491             mIsInitialAutojoinEnabled = enabled;
492             return this;
493         }
494 
495         /**
496          * Specifies whether the system will bring up the network (if selected) as untrusted. An
497          * untrusted network has its {@link android.net.NetworkCapabilities#NET_CAPABILITY_TRUSTED}
498          * capability removed. The Wi-Fi network selection process may use this information to
499          * influence priority of the suggested network for Wi-Fi network selection (most likely to
500          * reduce it). The connectivity service may use this information to influence the overall
501          * network configuration of the device.
502          * <p>
503          * <li> An untrusted network's credentials may not be shared with the user using
504          * {@link #setCredentialSharedWithUser(boolean)}.</li>
505          * <li> If not set, defaults to false (i.e. network is trusted).</li>
506          *
507          * @param isUntrusted Boolean indicating whether the network should be brought up untrusted
508          *                    (if true) or trusted (if false).
509          * @return Instance of {@link Builder} to enable chaining of the builder method.
510          */
setUntrusted(boolean isUntrusted)511         public @NonNull Builder setUntrusted(boolean isUntrusted) {
512             mIsNetworkUntrusted = isUntrusted;
513             return this;
514         }
515 
setSecurityParamsInWifiConfiguration( @onNull WifiConfiguration configuration)516         private void setSecurityParamsInWifiConfiguration(
517                 @NonNull WifiConfiguration configuration) {
518             if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network.
519                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
520                 // WifiConfiguration.preSharedKey needs quotes around ASCII password.
521                 configuration.preSharedKey = "\"" + mWpa2PskPassphrase + "\"";
522             } else if (!TextUtils.isEmpty(mWpa3SaePassphrase)) { // WPA3-SAE network.
523                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
524                 // WifiConfiguration.preSharedKey needs quotes around ASCII password.
525                 configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\"";
526             } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network
527                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
528                 configuration.enterpriseConfig = mWpa2EnterpriseConfig;
529             } else if (mWpa3EnterpriseConfig != null) { // WPA3-Enterprise
530                 if (mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS
531                         && WifiEnterpriseConfig.isSuiteBCipherCert(
532                         mWpa3EnterpriseConfig.getClientCertificate())
533                         && WifiEnterpriseConfig.isSuiteBCipherCert(
534                         mWpa3EnterpriseConfig.getCaCertificate())) {
535                     // WPA3-Enterprise in 192-bit security mode (Suite-B)
536                     configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
537                 } else {
538                     // WPA3-Enterprise
539                     configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
540                     configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
541                     configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
542                     configuration.allowedPairwiseCiphers.set(
543                             WifiConfiguration.PairwiseCipher.GCMP_256);
544                     configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
545                     configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
546                     configuration.requirePmf = true;
547                 }
548                 configuration.enterpriseConfig = mWpa3EnterpriseConfig;
549             } else if (mIsEnhancedOpen) { // OWE network
550                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
551             } else if (!TextUtils.isEmpty(mWapiPskPassphrase)) { // WAPI-PSK network.
552                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_PSK);
553                 // WifiConfiguration.preSharedKey needs quotes around ASCII password.
554                 configuration.preSharedKey = "\"" + mWapiPskPassphrase + "\"";
555             } else if (mWapiEnterpriseConfig != null) { // WAPI-CERT network
556                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT);
557                 configuration.enterpriseConfig = mWapiEnterpriseConfig;
558             } else { // Open network
559                 configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
560             }
561         }
562 
563         /**
564          * Helper method to build WifiConfiguration object from the builder.
565          * @return Instance of {@link WifiConfiguration}.
566          */
buildWifiConfiguration()567         private WifiConfiguration buildWifiConfiguration() {
568             final WifiConfiguration wifiConfiguration = new WifiConfiguration();
569             // WifiConfiguration.SSID needs quotes around unicode SSID.
570             wifiConfiguration.SSID = "\"" + mSsid + "\"";
571             if (mBssid != null) {
572                 wifiConfiguration.BSSID = mBssid.toString();
573             }
574 
575             setSecurityParamsInWifiConfiguration(wifiConfiguration);
576 
577             wifiConfiguration.hiddenSSID = mIsHiddenSSID;
578             wifiConfiguration.priority = mPriority;
579             wifiConfiguration.meteredOverride = mMeteredOverride;
580             wifiConfiguration.carrierId = mCarrierId;
581             wifiConfiguration.trusted = !mIsNetworkUntrusted;
582             return wifiConfiguration;
583         }
584 
validateSecurityParams()585         private void validateSecurityParams() {
586             int numSecurityTypes = 0;
587             numSecurityTypes += mIsEnhancedOpen ? 1 : 0;
588             numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0;
589             numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0;
590             numSecurityTypes += !TextUtils.isEmpty(mWapiPskPassphrase) ? 1 : 0;
591             numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0;
592             numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0;
593             numSecurityTypes += mWapiEnterpriseConfig != null ? 1 : 0;
594             numSecurityTypes += mPasspointConfiguration != null ? 1 : 0;
595             if (numSecurityTypes > 1) {
596                 throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase,"
597                         + " setWpa3Passphrase, setWpa2EnterpriseConfig, setWpa3EnterpriseConfig"
598                         + " setWapiPassphrase, setWapiCertSuite, setIsWapiCertSuiteAuto"
599                         + " or setPasspointConfig can be invoked for network suggestion");
600             }
601         }
602 
buildWifiConfigurationForPasspoint()603         private WifiConfiguration buildWifiConfigurationForPasspoint() {
604             WifiConfiguration wifiConfiguration = new WifiConfiguration();
605             wifiConfiguration.FQDN = mPasspointConfiguration.getHomeSp().getFqdn();
606             wifiConfiguration.setPasspointUniqueId(mPasspointConfiguration.getUniqueId());
607             wifiConfiguration.priority = mPriority;
608             wifiConfiguration.meteredOverride = mMeteredOverride;
609             wifiConfiguration.trusted = !mIsNetworkUntrusted;
610             mPasspointConfiguration.setCarrierId(mCarrierId);
611             mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride);
612             return wifiConfiguration;
613         }
614 
615         /**
616          * Create a network suggestion object for use in
617          * {@link WifiManager#addNetworkSuggestions(List)}.
618          *
619          *<p class="note">
620          * <b>Note:</b> Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID
621          * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to
622          * the platform.
623          * </p>
624          *
625          * For example:
626          * To provide credentials for one open, one WPA2, one WPA3 network with their
627          * corresponding SSID's and one with Passpoint config:
628          *
629          * <pre>{@code
630          * final WifiNetworkSuggestion suggestion1 =
631          *      new Builder()
632          *      .setSsid("test111111")
633          *      .build();
634          * final WifiNetworkSuggestion suggestion2 =
635          *      new Builder()
636          *      .setSsid("test222222")
637          *      .setWpa2Passphrase("test123456")
638          *      .build();
639          * final WifiNetworkSuggestion suggestion3 =
640          *      new Builder()
641          *      .setSsid("test333333")
642          *      .setWpa3Passphrase("test6789")
643          *      .build();
644          * final PasspointConfiguration passpointConfig= new PasspointConfiguration();
645          * // configure passpointConfig to include a valid Passpoint configuration
646          * final WifiNetworkSuggestion suggestion4 =
647          *      new Builder()
648          *      .setPasspointConfig(passpointConfig)
649          *      .build();
650          * final List<WifiNetworkSuggestion> suggestionsList =
651          *      new ArrayList<WifiNetworkSuggestion> { {
652          *          add(suggestion1);
653          *          add(suggestion2);
654          *          add(suggestion3);
655          *          add(suggestion4);
656          *      } };
657          * final WifiManager wifiManager =
658          *      context.getSystemService(Context.WIFI_SERVICE);
659          * wifiManager.addNetworkSuggestions(suggestionsList);
660          * // ...
661          * }</pre>
662          *
663          * @return Instance of {@link WifiNetworkSuggestion}
664          * @throws IllegalStateException on invalid params set
665          * @see WifiNetworkSuggestion
666          */
build()667         public @NonNull WifiNetworkSuggestion build() {
668             validateSecurityParams();
669             WifiConfiguration wifiConfiguration;
670             if (mPasspointConfiguration != null) {
671                 if (mSsid != null) {
672                     throw new IllegalStateException("setSsid should not be invoked for suggestion "
673                             + "with Passpoint configuration");
674                 }
675                 if (mIsHiddenSSID) {
676                     throw new IllegalStateException("setIsHiddenSsid should not be invoked for "
677                             + "suggestion with Passpoint configuration");
678                 }
679                 wifiConfiguration = buildWifiConfigurationForPasspoint();
680             } else {
681                 if (mSsid == null) {
682                     throw new IllegalStateException("setSsid should be invoked for suggestion");
683                 }
684                 if (TextUtils.isEmpty(mSsid)) {
685                     throw new IllegalStateException("invalid ssid for suggestion");
686                 }
687                 if (mBssid != null
688                         && (mBssid.equals(MacAddress.BROADCAST_ADDRESS)
689                         || mBssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS))) {
690                     throw new IllegalStateException("invalid bssid for suggestion");
691                 }
692                 wifiConfiguration = buildWifiConfiguration();
693                 if (wifiConfiguration.isOpenNetwork()) {
694                     if (mIsSharedWithUserSet && mIsSharedWithUser) {
695                         throw new IllegalStateException("Open network should not be "
696                                 + "setCredentialSharedWithUser to true");
697                     }
698                     mIsSharedWithUser = false;
699                 }
700             }
701             if (!mIsSharedWithUser && !mIsInitialAutojoinEnabled) {
702                 throw new IllegalStateException("Should have not a network with both "
703                         + "setCredentialSharedWithUser and "
704                         + "setIsAutojoinEnabled set to false");
705             }
706             if (mIsNetworkUntrusted) {
707                 if (mIsSharedWithUserSet && mIsSharedWithUser) {
708                     throw new IllegalStateException("Should not be both"
709                             + "setCredentialSharedWithUser and +"
710                             + "setIsNetworkAsUntrusted to true");
711                 }
712                 mIsSharedWithUser = false;
713             }
714             return new WifiNetworkSuggestion(
715                     wifiConfiguration,
716                     mPasspointConfiguration,
717                     mIsAppInteractionRequired,
718                     mIsUserInteractionRequired,
719                     mIsSharedWithUser,
720                     mIsInitialAutojoinEnabled);
721         }
722     }
723 
724     /**
725      * Network configuration for the provided network.
726      * @hide
727      */
728     @NonNull
729     public final WifiConfiguration wifiConfiguration;
730 
731     /**
732      * Passpoint configuration for the provided network.
733      * @hide
734      */
735     @Nullable
736     public final PasspointConfiguration passpointConfiguration;
737 
738     /**
739      * Whether app needs to log in to captive portal to obtain Internet access.
740      * @hide
741      */
742     public final boolean isAppInteractionRequired;
743 
744     /**
745      * Whether user needs to log in to captive portal to obtain Internet access.
746      * @hide
747      */
748     public final boolean isUserInteractionRequired;
749 
750     /**
751      * Whether app share credential with the user, allow user use provided credential to
752      * connect network manually.
753      * @hide
754      */
755     public final boolean isUserAllowedToManuallyConnect;
756 
757     /**
758      * Whether the suggestion will be initialized as auto-joined or not.
759      * @hide
760      */
761     public final boolean isInitialAutoJoinEnabled;
762 
763     /** @hide */
WifiNetworkSuggestion()764     public WifiNetworkSuggestion() {
765         this.wifiConfiguration = new WifiConfiguration();
766         this.passpointConfiguration = null;
767         this.isAppInteractionRequired = false;
768         this.isUserInteractionRequired = false;
769         this.isUserAllowedToManuallyConnect = true;
770         this.isInitialAutoJoinEnabled = true;
771     }
772 
773     /** @hide */
WifiNetworkSuggestion(@onNull WifiConfiguration networkConfiguration, @Nullable PasspointConfiguration passpointConfiguration, boolean isAppInteractionRequired, boolean isUserInteractionRequired, boolean isUserAllowedToManuallyConnect, boolean isInitialAutoJoinEnabled)774     public WifiNetworkSuggestion(@NonNull WifiConfiguration networkConfiguration,
775                                  @Nullable PasspointConfiguration passpointConfiguration,
776                                  boolean isAppInteractionRequired,
777                                  boolean isUserInteractionRequired,
778                                  boolean isUserAllowedToManuallyConnect,
779                                  boolean isInitialAutoJoinEnabled) {
780         checkNotNull(networkConfiguration);
781         this.wifiConfiguration = networkConfiguration;
782         this.passpointConfiguration = passpointConfiguration;
783 
784         this.isAppInteractionRequired = isAppInteractionRequired;
785         this.isUserInteractionRequired = isUserInteractionRequired;
786         this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect;
787         this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled;
788     }
789 
790     public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
791             new Creator<WifiNetworkSuggestion>() {
792                 @Override
793                 public WifiNetworkSuggestion createFromParcel(Parcel in) {
794                     return new WifiNetworkSuggestion(
795                             in.readParcelable(null), // wifiConfiguration
796                             in.readParcelable(null), // PasspointConfiguration
797                             in.readBoolean(), // isAppInteractionRequired
798                             in.readBoolean(), // isUserInteractionRequired
799                             in.readBoolean(), // isSharedCredentialWithUser
800                             in.readBoolean()  // isAutojoinEnabled
801                     );
802                 }
803 
804                 @Override
805                 public WifiNetworkSuggestion[] newArray(int size) {
806                     return new WifiNetworkSuggestion[size];
807                 }
808             };
809 
810     @Override
describeContents()811     public int describeContents() {
812         return 0;
813     }
814 
815     @Override
writeToParcel(Parcel dest, int flags)816     public void writeToParcel(Parcel dest, int flags) {
817         dest.writeParcelable(wifiConfiguration, flags);
818         dest.writeParcelable(passpointConfiguration, flags);
819         dest.writeBoolean(isAppInteractionRequired);
820         dest.writeBoolean(isUserInteractionRequired);
821         dest.writeBoolean(isUserAllowedToManuallyConnect);
822         dest.writeBoolean(isInitialAutoJoinEnabled);
823     }
824 
825     @Override
hashCode()826     public int hashCode() {
827         return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID,
828                 wifiConfiguration.allowedKeyManagement, wifiConfiguration.getKey());
829     }
830 
831     /**
832      * Equals for network suggestions.
833      */
834     @Override
equals(Object obj)835     public boolean equals(Object obj) {
836         if (this == obj) {
837             return true;
838         }
839         if (!(obj instanceof WifiNetworkSuggestion)) {
840             return false;
841         }
842         WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj;
843         if (this.passpointConfiguration == null ^ lhs.passpointConfiguration == null) {
844             return false;
845         }
846 
847         return TextUtils.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID)
848                 && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID)
849                 && Objects.equals(this.wifiConfiguration.allowedKeyManagement,
850                 lhs.wifiConfiguration.allowedKeyManagement)
851                 && TextUtils.equals(this.wifiConfiguration.getKey(),
852                 lhs.wifiConfiguration.getKey());
853     }
854 
855     @Override
toString()856     public String toString() {
857         StringBuilder sb = new StringBuilder("WifiNetworkSuggestion[ ")
858                 .append("SSID=").append(wifiConfiguration.SSID)
859                 .append(", BSSID=").append(wifiConfiguration.BSSID)
860                 .append(", FQDN=").append(wifiConfiguration.FQDN)
861                 .append(", isAppInteractionRequired=").append(isAppInteractionRequired)
862                 .append(", isUserInteractionRequired=").append(isUserInteractionRequired)
863                 .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect)
864                 .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
865                 .append(", isUnTrusted=").append(!wifiConfiguration.trusted)
866                 .append(" ]");
867         return sb.toString();
868     }
869 
870     /**
871      * Get the {@link WifiConfiguration} associated with this Suggestion.
872      * @hide
873      */
874     @SystemApi
875     @NonNull
getWifiConfiguration()876     public WifiConfiguration getWifiConfiguration() {
877         return wifiConfiguration;
878     }
879 
880     /**
881      * Get the BSSID, or null if unset.
882      * @see Builder#setBssid(MacAddress)
883      */
884     @Nullable
getBssid()885     public MacAddress getBssid() {
886         if (wifiConfiguration.BSSID == null) {
887             return null;
888         }
889         return MacAddress.fromString(wifiConfiguration.BSSID);
890     }
891 
892     /** @see Builder#setCredentialSharedWithUser(boolean) */
isCredentialSharedWithUser()893     public boolean isCredentialSharedWithUser() {
894         return isUserAllowedToManuallyConnect;
895     }
896 
897     /** @see Builder#setIsAppInteractionRequired(boolean) */
isAppInteractionRequired()898     public boolean isAppInteractionRequired() {
899         return isAppInteractionRequired;
900     }
901 
902     /** @see Builder#setIsEnhancedOpen(boolean)  */
isEnhancedOpen()903     public boolean isEnhancedOpen() {
904         return wifiConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE);
905     }
906 
907     /** @see Builder#setIsHiddenSsid(boolean)  */
isHiddenSsid()908     public boolean isHiddenSsid() {
909         return wifiConfiguration.hiddenSSID;
910     }
911 
912     /** @see Builder#setIsInitialAutojoinEnabled(boolean)  */
isInitialAutojoinEnabled()913     public boolean isInitialAutojoinEnabled() {
914         return isInitialAutoJoinEnabled;
915     }
916 
917     /** @see Builder#setIsMetered(boolean)  */
isMetered()918     public boolean isMetered() {
919         return wifiConfiguration.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED;
920     }
921 
922     /** @see Builder#setIsUserInteractionRequired(boolean)  */
isUserInteractionRequired()923     public boolean isUserInteractionRequired() {
924         return isUserInteractionRequired;
925     }
926 
927     /**
928      * Get the {@link PasspointConfiguration} associated with this Suggestion, or null if this
929      * Suggestion is not for a Passpoint network.
930      */
931     @Nullable
getPasspointConfig()932     public PasspointConfiguration getPasspointConfig() {
933         return passpointConfiguration;
934     }
935 
936     /** @see Builder#setPriority(int)  */
937     @IntRange(from = 0)
getPriority()938     public int getPriority() {
939         return wifiConfiguration.priority;
940     }
941 
942     /**
943      * Return the SSID of the network, or null if this is a Passpoint network.
944      * @see Builder#setSsid(String)
945      */
946     @Nullable
getSsid()947     public String getSsid() {
948         if (wifiConfiguration.SSID == null) {
949             return null;
950         }
951         return WifiInfo.sanitizeSsid(wifiConfiguration.SSID);
952     }
953 
954     /** @see Builder#setUntrusted(boolean)  */
isUntrusted()955     public boolean isUntrusted() {
956         return !wifiConfiguration.trusted;
957     }
958 
959     /**
960      * Get the WifiEnterpriseConfig, or null if unset.
961      * @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig)
962      * @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig)
963      * @see Builder#setWpa3EnterpriseConfig(WifiEnterpriseConfig)
964      */
965     @Nullable
getEnterpriseConfig()966     public WifiEnterpriseConfig getEnterpriseConfig() {
967         if (!wifiConfiguration.isEnterprise()) {
968             return null;
969         }
970         return wifiConfiguration.enterpriseConfig;
971     }
972 
973     /**
974      * Get the passphrase, or null if unset.
975      * @see Builder#setWapiPassphrase(String)
976      * @see Builder#setWpa2Passphrase(String)
977      * @see Builder#setWpa3Passphrase(String)
978      */
979     @Nullable
getPassphrase()980     public String getPassphrase() {
981         if (wifiConfiguration.preSharedKey == null) {
982             return null;
983         }
984         return WifiInfo.removeDoubleQuotes(wifiConfiguration.preSharedKey);
985     }
986 }
987