/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net.wifi; import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.net.MacAddress; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.wifi.hotspot2.PasspointConfiguration; import android.os.Build; import android.os.Parcel; import android.os.ParcelUuid; import android.os.Parcelable; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; import com.android.wifi.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; /** * The Network Suggestion object is used to provide a Wi-Fi network for consideration when * auto-connecting to networks. Apps cannot directly create this object, they must use * {@link WifiNetworkSuggestion.Builder#build()} to obtain an instance of this object. *

* Apps can provide a list of such networks to the platform using * {@link WifiManager#addNetworkSuggestions(List)}. */ public final class WifiNetworkSuggestion implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"RANDOMIZATION_"}, value = { RANDOMIZATION_PERSISTENT, RANDOMIZATION_NON_PERSISTENT}) public @interface MacRandomizationSetting {} /** * Generate a randomized MAC from a secret seed and information from the Wi-Fi configuration * (SSID or Passpoint profile) and reuse it for all connections to this network. The * randomized MAC address for this network will stay the same for each subsequent association * until the device undergoes factory reset. */ public static final int RANDOMIZATION_PERSISTENT = 0; /** * With this option, the randomized MAC address will periodically get re-randomized, and * the randomized MAC address will change if the suggestion is removed and then added back. */ public static final int RANDOMIZATION_NON_PERSISTENT = 1; /** * Builder used to create {@link WifiNetworkSuggestion} objects. */ public static final class Builder { private static final int UNASSIGNED_PRIORITY = -1; /** * Set WPA Enterprise type according to certificate security level. * This is for backward compatibility in R. */ private static final int WPA3_ENTERPRISE_AUTO = 0; /** Set WPA Enterprise type to standard mode only. */ private static final int WPA3_ENTERPRISE_STANDARD = 1; /** Set WPA Enterprise type to 192 bit mode only. */ private static final int WPA3_ENTERPRISE_192_BIT = 2; /** * SSID of the network. */ private WifiSsid mWifiSsid; /** * Optional BSSID within the network. */ private MacAddress mBssid; /** * Whether this is an OWE network or not. */ private boolean mIsEnhancedOpen; /** * Pre-shared key for use with WPA-PSK networks. */ private @Nullable String mWpa2PskPassphrase; /** * Pre-shared key for use with WPA3-SAE networks. */ private @Nullable String mWpa3SaePassphrase; /** * The enterprise configuration details specifying the EAP method, * certificates and other settings associated with the WPA/WPA2-Enterprise networks. */ private @Nullable WifiEnterpriseConfig mWpa2EnterpriseConfig; /** * The enterprise configuration details specifying the EAP method, * certificates and other settings associated with the WPA3-Enterprise networks. */ private @Nullable WifiEnterpriseConfig mWpa3EnterpriseConfig; /** * Indicate what type this WPA3-Enterprise network is. */ private int mWpa3EnterpriseType = WPA3_ENTERPRISE_AUTO; /** * The passpoint config for use with Hotspot 2.0 network */ private @Nullable PasspointConfiguration mPasspointConfiguration; /** * This is a network that does not broadcast its SSID, so an * SSID-specific probe request must be used for scans. */ private boolean mIsHiddenSSID; /** * Whether app needs to log in to captive portal to obtain Internet access. */ private boolean mIsAppInteractionRequired; /** * Whether user needs to log in to captive portal to obtain Internet access. */ private boolean mIsUserInteractionRequired; /** * Whether this network is metered or not. */ private int mMeteredOverride; /** * Priority of this network among other network suggestions from same priority group * provided by the app. * The higher the number, the higher the priority (i.e value of 0 = lowest priority). */ private int mPriority; /** * Priority group ID, while suggestion priority will only effect inside the priority group. */ private int mPriorityGroup; /** * The carrier ID identifies the operator who provides this network configuration. * see {@link TelephonyManager#getSimCarrierId()} */ private int mCarrierId; /** * The Subscription ID identifies the SIM card for which this network configuration is * valid. */ private int mSubscriptionId; /** * Whether this network is shared credential with user to allow user manually connect. */ private boolean mIsSharedWithUser; /** * Whether the setCredentialSharedWithUser have been called. */ private boolean mIsSharedWithUserSet; /** * Whether this network is initialized with auto-join enabled (the default) or not. */ private boolean mIsInitialAutojoinEnabled; /** * Pre-shared key for use with WAPI-PSK networks. */ private @Nullable String mWapiPskPassphrase; /** * The enterprise configuration details specifying the EAP method, * certificates and other settings associated with the WAPI networks. */ private @Nullable WifiEnterpriseConfig mWapiEnterpriseConfig; /** * Whether this network will be brought up as untrusted (TRUSTED capability bit removed). */ private boolean mIsNetworkUntrusted; /** * Whether this network will be brought up as OEM paid (OEM_PAID capability bit added). */ private boolean mIsNetworkOemPaid; /** * Whether this network will be brought up as OEM private (OEM_PRIVATE capability bit * added). */ private boolean mIsNetworkOemPrivate; /** * Whether this network is a carrier merged network. */ private boolean mIsCarrierMerged; /** * The MAC randomization strategy. */ @MacRandomizationSetting private int mMacRandomizationSetting; /** * The SAE Hash-to-Element only mode. */ private boolean mSaeH2eOnlyMode; /** * Whether this network will be brought up as restricted */ private boolean mIsNetworkRestricted; /** * Whether enable Wi-Fi 7 for this network */ private boolean mIsWifi7Enabled; /** * The Subscription group UUID identifies the SIM cards for which this network configuration * is valid. */ private ParcelUuid mSubscriptionGroup; public Builder() { mBssid = null; mIsEnhancedOpen = false; mWpa2PskPassphrase = null; mWpa3SaePassphrase = null; mWpa2EnterpriseConfig = null; mWpa3EnterpriseConfig = null; mPasspointConfiguration = null; mIsHiddenSSID = false; mIsAppInteractionRequired = false; mIsUserInteractionRequired = false; mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NONE; mIsSharedWithUser = true; mIsSharedWithUserSet = false; mIsInitialAutojoinEnabled = true; mPriority = UNASSIGNED_PRIORITY; mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID; mWapiPskPassphrase = null; mWapiEnterpriseConfig = null; mIsNetworkUntrusted = false; mIsNetworkOemPaid = false; mIsNetworkOemPrivate = false; mIsCarrierMerged = false; mPriorityGroup = 0; mMacRandomizationSetting = RANDOMIZATION_PERSISTENT; mSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; mSaeH2eOnlyMode = false; mIsNetworkRestricted = false; mSubscriptionGroup = null; mWifiSsid = null; mIsWifi7Enabled = true; } /** * Set the unicode SSID for the network. *

*

  • Overrides any previous value set using {@link #setSsid(String)}.
  • * *

    * Note: use {@link #setWifiSsid(WifiSsid)} which supports both unicode and non-unicode * SSID. * * @param ssid The SSID of the network. It must be valid Unicode. * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException if the SSID is not valid unicode. */ public @NonNull Builder setSsid(@NonNull String ssid) { checkNotNull(ssid); final CharsetEncoder unicodeEncoder = StandardCharsets.UTF_8.newEncoder(); if (!unicodeEncoder.canEncode(ssid)) { throw new IllegalArgumentException("SSID is not a valid unicode string"); } mWifiSsid = WifiSsid.fromUtf8Text(ssid); return this; } /** * Set the SSID for the network. {@link WifiSsid} support both unicode and non-unicode SSID. *

    *

  • Overrides any previous value set using {@link #setWifiSsid(WifiSsid)} * or {@link #setSsid(String)}.
  • *

    * Note: this method is the superset of the {@link #setSsid(String)} * * @param wifiSsid The SSID of the network, in {@link WifiSsid} format. * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException if the wifiSsid is invalid. */ public @NonNull Builder setWifiSsid(@NonNull WifiSsid wifiSsid) { checkNotNull(wifiSsid); if (wifiSsid.getBytes().length == 0) { throw new IllegalArgumentException("Empty WifiSsid is invalid"); } mWifiSsid = WifiSsid.fromBytes(wifiSsid.getBytes()); return this; } /** * Set the BSSID to use for filtering networks from scan results. Will only match network * whose BSSID is identical to the specified value. *

    *

  • If set, only the specified BSSID with the specified SSID will be considered for * connection.
  • *
  • If not set, all BSSIDs with the specified SSID will be considered for connection. *
  • *
  • Overrides any previous value set using {@link #setBssid(MacAddress)}.
  • * * @param bssid BSSID of the network. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setBssid(@NonNull MacAddress bssid) { checkNotNull(bssid); mBssid = MacAddress.fromBytes(bssid.toByteArray()); return this; } /** * Specifies whether this represents an Enhanced Open (OWE) network. * * @param isEnhancedOpen {@code true} to indicate that the network used enhanced open, * {@code false} otherwise. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setIsEnhancedOpen(boolean isEnhancedOpen) { mIsEnhancedOpen = isEnhancedOpen; return this; } /** * Set the ASCII WPA2 passphrase for this network. Needed for authenticating to * WPA2-PSK networks. * * @param passphrase passphrase of the network. * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException if the passphrase is not ASCII encodable. */ public @NonNull Builder setWpa2Passphrase(@NonNull String passphrase) { checkNotNull(passphrase); final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); if (!asciiEncoder.canEncode(passphrase)) { throw new IllegalArgumentException("passphrase not ASCII encodable"); } mWpa2PskPassphrase = passphrase; return this; } /** * Set the ASCII WPA3 passphrase for this network. Needed for authenticating to WPA3-SAE * networks. * * @param passphrase passphrase of the network. * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException if the passphrase is not ASCII encodable. */ public @NonNull Builder setWpa3Passphrase(@NonNull String passphrase) { checkNotNull(passphrase); final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); if (!asciiEncoder.canEncode(passphrase)) { throw new IllegalArgumentException("passphrase not ASCII encodable"); } mWpa3SaePassphrase = passphrase; return this; } /** * Set the associated enterprise configuration for this network. Needed for authenticating * to WPA2 enterprise networks. See {@link WifiEnterpriseConfig} for description. * * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException If configuration uses server certificate but validation * is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()} */ public @NonNull Builder setWpa2EnterpriseConfig( @NonNull WifiEnterpriseConfig enterpriseConfig) { checkNotNull(enterpriseConfig); if (enterpriseConfig.isEapMethodServerCertUsed() && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) { throw new IllegalArgumentException("Enterprise configuration mandates server " + "certificate but validation is not enabled."); } mWpa2EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); return this; } /** * Set the associated enterprise configuration for this network. Needed for authenticating * to WPA3-Enterprise networks (standard and 192-bit security). See * {@link WifiEnterpriseConfig} for description. For 192-bit security networks, both the * client and CA certificates must be provided, and must be of type of either * sha384WithRSAEncryption (OID 1.2.840.113549.1.1.12) or ecdsa-with-SHA384 * (OID 1.2.840.10045.4.3.3). * * @deprecated use {@link #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig)} or * {@link #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} to specify * WPA3-Enterprise type explicitly. * * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException If configuration uses server certificate but validation * is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()} */ @Deprecated public @NonNull Builder setWpa3EnterpriseConfig( @NonNull WifiEnterpriseConfig enterpriseConfig) { checkNotNull(enterpriseConfig); if (enterpriseConfig.isEapMethodServerCertUsed() && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) { throw new IllegalArgumentException("Enterprise configuration mandates server " + "certificate but validation is not enabled."); } mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); return this; } /** * Set the associated enterprise configuration for this network. Needed for authenticating * to WPA3-Enterprise standard networks. See {@link WifiEnterpriseConfig} for description. * For WPA3-Enterprise in 192-bit security mode networks, * see {@link #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} for description. * * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException If configuration uses server certificate but validation * is not enabled. See {@link WifiEnterpriseConfig#isServerCertValidationEnabled()} */ public @NonNull Builder setWpa3EnterpriseStandardModeConfig( @NonNull WifiEnterpriseConfig enterpriseConfig) { checkNotNull(enterpriseConfig); if (enterpriseConfig.isEapMethodServerCertUsed() && !enterpriseConfig.isMandatoryParameterSetForServerCertValidation()) { throw new IllegalArgumentException("Enterprise configuration mandates server " + "certificate but validation is not enabled."); } mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); mWpa3EnterpriseType = WPA3_ENTERPRISE_STANDARD; return this; } /** * Set the associated enterprise configuration for this network. Needed for authenticating * to WPA3-Enterprise in 192-bit security mode networks. See {@link WifiEnterpriseConfig} * for description. Both the client and CA certificates must be provided, * and must be of type of either sha384WithRSAEncryption with key length of 3072bit or * more (OID 1.2.840.113549.1.1.12), or ecdsa-with-SHA384 with key length of 384bit or * more (OID 1.2.840.10045.4.3.3). * * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException if the EAP type or certificates do not * meet 192-bit mode requirements. */ public @NonNull Builder setWpa3Enterprise192BitModeConfig( @NonNull WifiEnterpriseConfig enterpriseConfig) { checkNotNull(enterpriseConfig); if (enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.TLS) { throw new IllegalArgumentException("The 192-bit mode network type must be TLS"); } if (!WifiEnterpriseConfig.isSuiteBCipherCert( enterpriseConfig.getClientCertificate())) { throw new IllegalArgumentException( "The client certificate does not meet 192-bit mode requirements."); } if (!WifiEnterpriseConfig.isSuiteBCipherCert( enterpriseConfig.getCaCertificate())) { throw new IllegalArgumentException( "The CA certificate does not meet 192-bit mode requirements."); } mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); mWpa3EnterpriseType = WPA3_ENTERPRISE_192_BIT; return this; } /** * Set the associated Passpoint configuration for this network. Needed for authenticating * to Hotspot 2.0 networks. See {@link PasspointConfiguration} for description. * * @param passpointConfig Instance of {@link PasspointConfiguration}. * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException if passpoint configuration is invalid. */ public @NonNull Builder setPasspointConfig( @NonNull PasspointConfiguration passpointConfig) { checkNotNull(passpointConfig); if (!passpointConfig.validate()) { throw new IllegalArgumentException("Passpoint configuration is invalid"); } mPasspointConfiguration = new PasspointConfiguration(passpointConfig); return this; } /** * Set the carrier ID of the network operator. The carrier ID associates a Suggested * network with a specific carrier (and therefore SIM). The carrier ID must be provided * for any network which uses the SIM-based authentication: e.g. EAP-SIM, EAP-AKA, * EAP-AKA', and EAP-PEAP with SIM-based phase 2 authentication. * @param carrierId see {@link TelephonyManager#getSimCarrierId()}. * @return Instance of {@link Builder} to enable chaining of the builder method. * * @hide */ @SystemApi @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public @NonNull Builder setCarrierId(int carrierId) { mCarrierId = carrierId; return this; } /** * Configure the suggestion to only be used with the SIM identified by the subscription * ID specified in this method. The suggested network will only be used by that SIM and * no other SIM - even from the same carrier. *

    * The caller is restricted to be either of: *

  • A carrier provisioning app (which holds the * {@code android.Manifest.permission#NETWORK_CARRIER_PROVISIONING} permission). *
  • A carrier-privileged app - which is restricted to only specify a subscription ID * which belong to the same carrier which signed the app, see * {@link TelephonyManager#hasCarrierPrivileges()}. *

    * Specifying a subscription ID which doesn't match these restriction will cause the * suggestion to be rejected with the error code * {@link WifiManager#STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED}. * * Only one of the {@link #setSubscriptionGroup(ParcelUuid)} and * {@link #setSubscriptionId(int)} should be called for a suggestion. * * @param subscriptionId subscription ID see {@link SubscriptionInfo#getSubscriptionId()} * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException if subscriptionId equals to {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} */ @RequiresApi(Build.VERSION_CODES.S) public @NonNull Builder setSubscriptionId(int subscriptionId) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { throw new IllegalArgumentException("Subscription Id is invalid"); } mSubscriptionId = subscriptionId; return this; } /** * Configure the suggestion to only be used with the SIMs that belong to the Subscription * Group specified in this method. The suggested network will only be used by the SIM in * this Subscription Group and no other SIMs - even from the same carrier. *

    * The caller is restricted to be either of: *

  • A carrier provisioning app. *
  • A carrier-privileged app - which is restricted to only specify a subscription ID * which belong to the same carrier which signed the app, see * {@link TelephonyManager#hasCarrierPrivileges()}. *

    * Specifying a subscription group which doesn't match these restriction will cause the * suggestion to be rejected with the error code * {@link WifiManager#STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED}. * * Only one of the {@link #setSubscriptionGroup(ParcelUuid)} and * {@link #setSubscriptionId(int)} should be called for a suggestion. * * @param groupUuid Subscription group UUID see * {@link SubscriptionManager#createSubscriptionGroup(List)} * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException if group UUID is {@code null}. */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) public @NonNull Builder setSubscriptionGroup(@NonNull ParcelUuid groupUuid) { if (!SdkLevel.isAtLeastT()) { throw new UnsupportedOperationException(); } if (groupUuid == null) { throw new IllegalArgumentException("SubscriptionGroup is invalid"); } mSubscriptionGroup = groupUuid; return this; } /** * Suggested networks are considered as part of a pool of all suggested networks and other * networks (e.g. saved networks) - one of which will be selected. *

    *

    * However, this restricts a suggesting app to have a single group of networks which can be * prioritized. In some circumstances multiple groups are useful: for instance, suggesting * networks for 2 different SIMs - each of which may have its own priority order. *

    * Priority group: creates a separate priority group. Only the highest priority, currently * visible suggested network or networks, within each priority group are included in the * network selection pool. *

    * Specify an arbitrary integer only used as the priority group. Use with * {@link #setPriority(int)}. * * @param priorityGroup priority group id, if not set default is 0. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setPriorityGroup(@IntRange(from = 0) int priorityGroup) { mPriorityGroup = priorityGroup; return this; } /** * Set the ASCII WAPI passphrase for this network. Needed for authenticating to * WAPI-PSK networks. * * @param passphrase passphrase of the network. * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException if the passphrase is not ASCII encodable. * */ public @NonNull Builder setWapiPassphrase(@NonNull String passphrase) { checkNotNull(passphrase); final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); if (!asciiEncoder.canEncode(passphrase)) { throw new IllegalArgumentException("passphrase not ASCII encodable"); } mWapiPskPassphrase = passphrase; return this; } /** * Set the associated enterprise configuration for this network. Needed for authenticating * to WAPI-CERT networks. See {@link WifiEnterpriseConfig} for description. * * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setWapiEnterpriseConfig( @NonNull WifiEnterpriseConfig enterpriseConfig) { checkNotNull(enterpriseConfig); mWapiEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); return this; } /** * Specifies whether this represents a hidden network. *

    *

  • If not set, defaults to false (i.e not a hidden network).
  • * * @param isHiddenSsid {@code true} to indicate that the network is hidden, {@code false} * otherwise. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setIsHiddenSsid(boolean isHiddenSsid) { mIsHiddenSSID = isHiddenSsid; return this; } /** * Specifies the MAC randomization method. *

    * Suggested networks will never use the device (factory) MAC address to associate to the * network - instead they use a locally generated random MAC address. This method controls * the strategy for generating the random MAC address. If not set, defaults to * {@link #RANDOMIZATION_PERSISTENT}. * * @param macRandomizationSetting - one of {@code RANDOMIZATION_*} values * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setMacRandomizationSetting( @MacRandomizationSetting int macRandomizationSetting) { switch (macRandomizationSetting) { case RANDOMIZATION_PERSISTENT: case RANDOMIZATION_NON_PERSISTENT: mMacRandomizationSetting = macRandomizationSetting; break; default: throw new IllegalArgumentException(); } return this; } /** * Specifies whether the app needs to log in to a captive portal to obtain Internet access. *

    * This will dictate if the directed broadcast * {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} will be sent to the * app after successfully connecting to the network. * Use this for captive portal type networks where the app needs to authenticate the user * before the device can access the network. *

    *

  • If not set, defaults to false (i.e no app interaction required).
  • * * @param isAppInteractionRequired {@code true} to indicate that app interaction is * required, {@code false} otherwise. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setIsAppInteractionRequired(boolean isAppInteractionRequired) { mIsAppInteractionRequired = isAppInteractionRequired; return this; } /** * Specifies whether the user needs to log in to a captive portal to obtain Internet access. *

    *

  • If not set, defaults to false (i.e no user interaction required).
  • * * @param isUserInteractionRequired {@code true} to indicate that user interaction is * required, {@code false} otherwise. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setIsUserInteractionRequired(boolean isUserInteractionRequired) { mIsUserInteractionRequired = isUserInteractionRequired; return this; } /** * Specify the priority of this network among other network suggestions provided by the same * app and within the same priority group, see {@link #setPriorityGroup(int)}. Priorities * have no impact on suggestions by other apps or suggestions from the same app using a * different priority group. The higher the number, the higher the priority * (i.e value of 0 = lowest priority). If not set, defaults to a lower priority than any * assigned priority. * * @param priority Integer number representing the priority among suggestions by the app. * @return Instance of {@link Builder} to enable chaining of the builder method. * @throws IllegalArgumentException if the priority value is negative. */ public @NonNull Builder setPriority(@IntRange(from = 0) int priority) { if (priority < 0) { throw new IllegalArgumentException("Invalid priority value " + priority); } mPriority = priority; return this; } /** * Specifies whether this network is metered. *

    *

  • If not set, defaults to detect automatically.
  • * * @param isMetered {@code true} to indicate that the network is metered, {@code false} * for not metered. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setIsMetered(boolean isMetered) { if (isMetered) { mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED; } else { mMeteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED; } return this; } /** * Specifies whether the network credentials provided with this suggestion can be used by * the user to explicitly (manually) connect to this network. If true this network will * appear in the Wi-Fi Picker (in Settings) and the user will be able to select and connect * to it with the provided credentials. If false, the user will need to enter network * credentials and the resulting configuration will become a user saved network. *

    *

  • Note: Only valid for secure (non-open) networks. *
  • If not set, defaults to true (i.e. allow user to manually connect) for secure * networks and false for open networks.
  • * * @param isShared {@code true} to indicate that the credentials may be used by the user to * manually connect to the network, {@code false} otherwise. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setCredentialSharedWithUser(boolean isShared) { mIsSharedWithUser = isShared; mIsSharedWithUserSet = true; return this; } /** * Specifies whether the suggestion is created with auto-join enabled or disabled. The * user may modify the auto-join configuration of a suggestion directly once the device * associates to the network. *

    * If auto-join is initialized as disabled the user may still be able to manually connect * to the network. Therefore, disabling auto-join only makes sense if * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which * itself implies a secure (non-open) network. *

    * If not set, defaults to true (i.e. auto-join is initialized as enabled). * * @param enabled true for initializing with auto-join enabled (the default), false to * initializing with auto-join disabled. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setIsInitialAutojoinEnabled(boolean enabled) { mIsInitialAutojoinEnabled = enabled; return this; } /** * Specifies whether the system will bring up the network (if selected) as untrusted. An * untrusted network has its {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} * capability removed. The Wi-Fi network selection process may use this information to * influence priority of the suggested network for Wi-Fi network selection (most likely to * reduce it). The connectivity service may use this information to influence the overall * network configuration of the device. *

    *

  • These suggestions are only considered for network selection if a * {@link NetworkRequest} without {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} * capability is filed. *
  • An untrusted network's credentials may not be shared with the user using * {@link #setCredentialSharedWithUser(boolean)}.
  • *
  • If not set, defaults to false (i.e. network is trusted).
  • * * @param isUntrusted Boolean indicating whether the network should be brought up untrusted * (if true) or trusted (if false). * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setUntrusted(boolean isUntrusted) { mIsNetworkUntrusted = isUntrusted; return this; } /** * Specifies whether the system will bring up the network (if selected) as restricted. A * restricted network has its {@link NetworkCapabilities#NET_CAPABILITY_NOT_RESTRICTED} * capability removed. The Wi-Fi network selection process may use this information to * influence priority of the suggested network for Wi-Fi network selection (most likely to * reduce it). The connectivity service may use this information to influence the overall * network configuration of the device. *

    *

  • These suggestions are only considered for network selection if a * {@link NetworkRequest} without {@link NetworkCapabilities#NET_CAPABILITY_NOT_RESTRICTED} * capability is filed. *
  • A restricted network's credentials may not be shared with the user using * {@link #setCredentialSharedWithUser(boolean)}.
  • *
  • If not set, defaults to false (i.e. network is unrestricted).
  • * * @param isRestricted Boolean indicating whether the network should be brought up * restricted (if true) or unrestricted (if false). * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setRestricted(boolean isRestricted) { mIsNetworkRestricted = isRestricted; return this; } /** * Sets whether Wi-Fi 7 is enabled for this network. * * @param enabled Enable Wi-Fi 7 if true, otherwise disable Wi-Fi 7 * @return Instance of {@link Builder} to enable chaining of the builder method. */ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API) public @NonNull Builder setWifi7Enabled(boolean enabled) { mIsWifi7Enabled = enabled; return this; } /** * Specifies whether the system will bring up the network (if selected) as OEM paid. An * OEM paid network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID} capability * added. * Note: *
  • The connectivity service may use this information to influence the overall * network configuration of the device. This network is typically only available to system * apps. *
  • On devices which do not support concurrent connection (indicated via * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), Wi-Fi * network selection process may use this information to influence priority of the * suggested network for Wi-Fi network selection (most likely to reduce it). *
  • On devices which support concurrent connections (indicated via * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), these * OEM paid networks may be brought up as a secondary concurrent connection (primary * connection will be used for networks available to the user and all apps. *

    *

  • An OEM paid network's credentials may not be shared with the user using * {@link #setCredentialSharedWithUser(boolean)}.
  • *
  • These suggestions are only considered for network selection if a * {@link NetworkRequest} with {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID} * capability is filed. *
  • Each suggestion can have both {@link #setOemPaid(boolean)} and * {@link #setOemPrivate(boolean)} set if the app wants these suggestions considered * for creating either an OEM paid network or OEM private network determined based on * the {@link NetworkRequest} that is active. *
  • If not set, defaults to false (i.e. network is not OEM paid).
  • * * @param isOemPaid Boolean indicating whether the network should be brought up as OEM paid * (if true) or not OEM paid (if false). * @return Instance of {@link Builder} to enable chaining of the builder method. * @hide */ @SystemApi @RequiresApi(Build.VERSION_CODES.S) public @NonNull Builder setOemPaid(boolean isOemPaid) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } mIsNetworkOemPaid = isOemPaid; return this; } /** * Specifies whether the system will bring up the network (if selected) as OEM private. An * OEM private network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE} capability * added. * Note: *
  • The connectivity service may use this information to influence the overall * network configuration of the device. This network is typically only available to system * apps. *
  • On devices which do not support concurrent connection (indicated via * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), Wi-Fi * network selection process may use this information to influence priority of the suggested * network for Wi-Fi network selection (most likely to reduce it). *
  • On devices which support concurrent connections (indicated via * {@link WifiManager#isStaConcurrencyForRestrictedConnectionsSupported()}), these OEM * private networks may be brought up as a secondary concurrent connection (primary * connection will be used for networks available to the user and all apps. *

    *

  • An OEM private network's credentials may not be shared with the user using * {@link #setCredentialSharedWithUser(boolean)}.
  • *
  • These suggestions are only considered for network selection if a * {@link NetworkRequest} with {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE} * capability is filed. *
  • Each suggestion can have both {@link #setOemPaid(boolean)} and * {@link #setOemPrivate(boolean)} set if the app wants these suggestions considered * for creating either an OEM paid network or OEM private network determined based on * the {@link NetworkRequest} that is active. *
  • If not set, defaults to false (i.e. network is not OEM private).
  • * * @param isOemPrivate Boolean indicating whether the network should be brought up as OEM * private (if true) or not OEM private (if false). * @return Instance of {@link Builder} to enable chaining of the builder method. * @hide */ @SystemApi @RequiresApi(Build.VERSION_CODES.S) public @NonNull Builder setOemPrivate(boolean isOemPrivate) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } mIsNetworkOemPrivate = isOemPrivate; return this; } /** * Specifies whether the suggestion represents a carrier merged network. A carrier merged * Wi-Fi network is treated as part of the mobile carrier network. Such configuration may * impact the user interface and data usage accounting. *

    * Only carriers with the * {@link android.telephony.CarrierConfigManager#KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL} * flag set to {@code true} may use this API. *

    *

  • A suggestion marked as carrier merged must be metered enterprise network with a valid * subscription Id set. * @see #setIsMetered(boolean) * @see #setSubscriptionId(int) * @see #setWpa2EnterpriseConfig(WifiEnterpriseConfig) * @see #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig) * @see #setWpa3EnterpriseStandardModeConfig(WifiEnterpriseConfig) * @see #setPasspointConfig(PasspointConfiguration) *
  • *
  • If not set, defaults to false (i.e. not a carrier merged network.)
  • *

    * @param isCarrierMerged Boolean indicating whether the network is treated a carrier * merged network (if true) or non-merged network (if false); * @return Instance of {@link Builder} to enable chaining of the builder method. */ @RequiresApi(Build.VERSION_CODES.S) public @NonNull Builder setCarrierMerged(boolean isCarrierMerged) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } mIsCarrierMerged = isCarrierMerged; return this; } /** * Specifies whether the suggestion represents an SAE network which only * accepts Hash-to-Element mode. * If this is enabled, Hunting & Pecking mode is disabled and only Hash-to-Element * mode is used for this network. * This is only valid for an SAE network which is configured using the * {@link #setWpa3Passphrase}. * Before calling this API, the application should check Hash-to-Element support using * {@link WifiManager#isWpa3SaeH2eSupported()}. * * @param enable Boolean indicating whether the network only accepts Hash-to-Element mode, * default is false. * @return Instance of {@link Builder} to enable chaining of the builder method. */ @RequiresApi(Build.VERSION_CODES.S) public @NonNull Builder setIsWpa3SaeH2eOnlyModeEnabled(boolean enable) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } mSaeH2eOnlyMode = enable; return this; } private void setSecurityParamsInWifiConfiguration( @NonNull WifiConfiguration configuration) { if (!TextUtils.isEmpty(mWpa2PskPassphrase)) { // WPA-PSK network. configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK); // WifiConfiguration.preSharedKey needs quotes around ASCII password. configuration.preSharedKey = "\"" + mWpa2PskPassphrase + "\""; } else if (!TextUtils.isEmpty(mWpa3SaePassphrase)) { // WPA3-SAE network. configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE); // WifiConfiguration.preSharedKey needs quotes around ASCII password. configuration.preSharedKey = "\"" + mWpa3SaePassphrase + "\""; if (mSaeH2eOnlyMode) configuration.enableSaeH2eOnlyMode(mSaeH2eOnlyMode); } else if (mWpa2EnterpriseConfig != null) { // WPA-EAP network configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP); configuration.enterpriseConfig = mWpa2EnterpriseConfig; } else if (mWpa3EnterpriseConfig != null) { // WPA3-Enterprise if (mWpa3EnterpriseType == WPA3_ENTERPRISE_AUTO && mWpa3EnterpriseConfig.getEapMethod() == WifiEnterpriseConfig.Eap.TLS && WifiEnterpriseConfig.isSuiteBCipherCert( mWpa3EnterpriseConfig.getClientCertificate()) && WifiEnterpriseConfig.isSuiteBCipherCert( mWpa3EnterpriseConfig.getCaCertificate())) { // WPA3-Enterprise in 192-bit security mode configuration.setSecurityParams( WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT); } else if (mWpa3EnterpriseType == WPA3_ENTERPRISE_192_BIT) { // WPA3-Enterprise in 192-bit security mode configuration.setSecurityParams( WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT); } else { // WPA3-Enterprise configuration.setSecurityParams( WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE); } configuration.enterpriseConfig = mWpa3EnterpriseConfig; } else if (mIsEnhancedOpen) { // OWE network configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE); } else if (!TextUtils.isEmpty(mWapiPskPassphrase)) { // WAPI-PSK network. configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_PSK); // WifiConfiguration.preSharedKey needs quotes around ASCII password. configuration.preSharedKey = "\"" + mWapiPskPassphrase + "\""; } else if (mWapiEnterpriseConfig != null) { // WAPI-CERT network configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WAPI_CERT); configuration.enterpriseConfig = mWapiEnterpriseConfig; } else { // Open network configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); } } /** * Helper method to build WifiConfiguration object from the builder. * @return Instance of {@link WifiConfiguration}. */ private WifiConfiguration buildWifiConfiguration() { final WifiConfiguration wifiConfiguration = new WifiConfiguration(); // WifiConfiguration.SSID needs quotes around unicode SSID. wifiConfiguration.SSID = mWifiSsid.toString(); if (mBssid != null) { wifiConfiguration.BSSID = mBssid.toString(); } setSecurityParamsInWifiConfiguration(wifiConfiguration); wifiConfiguration.hiddenSSID = mIsHiddenSSID; wifiConfiguration.priority = mPriority; wifiConfiguration.meteredOverride = mMeteredOverride; wifiConfiguration.carrierId = mCarrierId; wifiConfiguration.trusted = !mIsNetworkUntrusted; wifiConfiguration.oemPaid = mIsNetworkOemPaid; wifiConfiguration.oemPrivate = mIsNetworkOemPrivate; wifiConfiguration.carrierMerged = mIsCarrierMerged; wifiConfiguration.macRandomizationSetting = mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT ? WifiConfiguration.RANDOMIZATION_NON_PERSISTENT : WifiConfiguration.RANDOMIZATION_PERSISTENT; wifiConfiguration.subscriptionId = mSubscriptionId; wifiConfiguration.restricted = mIsNetworkRestricted; wifiConfiguration.setSubscriptionGroup(mSubscriptionGroup); wifiConfiguration.setWifi7Enabled(mIsWifi7Enabled); return wifiConfiguration; } private void validateSecurityParams() { int numSecurityTypes = 0; numSecurityTypes += mIsEnhancedOpen ? 1 : 0; numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0; numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0; numSecurityTypes += !TextUtils.isEmpty(mWapiPskPassphrase) ? 1 : 0; numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0; numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0; numSecurityTypes += mWapiEnterpriseConfig != null ? 1 : 0; numSecurityTypes += mPasspointConfiguration != null ? 1 : 0; if (numSecurityTypes > 1) { throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase," + " setWpa3Passphrase, setWpa2EnterpriseConfig, setWpa3EnterpriseConfig" + " setWapiPassphrase, setWapiCertSuite, setIsWapiCertSuiteAuto" + " or setPasspointConfig can be invoked for network suggestion"); } } private WifiConfiguration buildWifiConfigurationForPasspoint() { WifiConfiguration wifiConfiguration = new WifiConfiguration(); wifiConfiguration.FQDN = mPasspointConfiguration.getHomeSp().getFqdn(); wifiConfiguration.setPasspointUniqueId(mPasspointConfiguration.getUniqueId()); wifiConfiguration.priority = mPriority; wifiConfiguration.meteredOverride = mMeteredOverride; wifiConfiguration.trusted = !mIsNetworkUntrusted; wifiConfiguration.oemPaid = mIsNetworkOemPaid; wifiConfiguration.oemPrivate = mIsNetworkOemPrivate; wifiConfiguration.carrierMerged = mIsCarrierMerged; wifiConfiguration.carrierId = mCarrierId; wifiConfiguration.subscriptionId = mSubscriptionId; wifiConfiguration.macRandomizationSetting = mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT ? WifiConfiguration.RANDOMIZATION_NON_PERSISTENT : WifiConfiguration.RANDOMIZATION_PERSISTENT; wifiConfiguration.restricted = mIsNetworkRestricted; wifiConfiguration.setSubscriptionGroup(mSubscriptionGroup); wifiConfiguration.setWifi7Enabled(mIsWifi7Enabled); mPasspointConfiguration.setCarrierId(mCarrierId); mPasspointConfiguration.setSubscriptionId(mSubscriptionId); mPasspointConfiguration.setSubscriptionGroup(mSubscriptionGroup); mPasspointConfiguration.setMeteredOverride(wifiConfiguration.meteredOverride); mPasspointConfiguration.setOemPrivate(mIsNetworkOemPrivate); mPasspointConfiguration.setOemPaid(mIsNetworkOemPaid); mPasspointConfiguration.setCarrierMerged(mIsCarrierMerged); // MAC randomization should always be enabled for passpoint suggestions regardless of // the PasspointConfiguration's original setting. mPasspointConfiguration.setMacRandomizationEnabled(true); mPasspointConfiguration.setNonPersistentMacRandomizationEnabled( mMacRandomizationSetting == RANDOMIZATION_NON_PERSISTENT); return wifiConfiguration; } /** * Create a network suggestion object for use in * {@link WifiManager#addNetworkSuggestions(List)}. * *

    * Note: Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to * the platform. *

    * * For example: * To provide credentials for one open, one WPA2, one WPA3 network with their * corresponding SSID's and one with Passpoint config: * *
    {@code
             * final WifiNetworkSuggestion suggestion1 =
             *      new Builder()
             *      .setSsid("test111111")
             *      .build();
             * final WifiNetworkSuggestion suggestion2 =
             *      new Builder()
             *      .setSsid("test222222")
             *      .setWpa2Passphrase("test123456")
             *      .build();
             * final WifiNetworkSuggestion suggestion3 =
             *      new Builder()
             *      .setSsid("test333333")
             *      .setWpa3Passphrase("test6789")
             *      .build();
             * final PasspointConfiguration passpointConfig= new PasspointConfiguration();
             * // configure passpointConfig to include a valid Passpoint configuration
             * final WifiNetworkSuggestion suggestion4 =
             *      new Builder()
             *      .setPasspointConfig(passpointConfig)
             *      .build();
             * final List suggestionsList =
             *      new ArrayList { {
             *          add(suggestion1);
             *          add(suggestion2);
             *          add(suggestion3);
             *          add(suggestion4);
             *      } };
             * final WifiManager wifiManager =
             *      context.getSystemService(Context.WIFI_SERVICE);
             * wifiManager.addNetworkSuggestions(suggestionsList);
             * // ...
             * }
    * * @return Instance of {@link WifiNetworkSuggestion} * @throws IllegalStateException on invalid params set * @see WifiNetworkSuggestion */ public @NonNull WifiNetworkSuggestion build() { validateSecurityParams(); WifiConfiguration wifiConfiguration; if (mPasspointConfiguration != null) { if (mWifiSsid != null) { throw new IllegalStateException("setSsid should not be invoked for suggestion " + "with Passpoint configuration"); } if (mIsHiddenSSID) { throw new IllegalStateException("setIsHiddenSsid should not be invoked for " + "suggestion with Passpoint configuration"); } wifiConfiguration = buildWifiConfigurationForPasspoint(); } else { if (mWifiSsid == null) { throw new IllegalStateException("setSsid should be invoked for suggestion"); } if (mWifiSsid.getBytes().length == 0) { throw new IllegalStateException("invalid ssid for suggestion"); } if (mBssid != null && (mBssid.equals(MacAddress.BROADCAST_ADDRESS) || mBssid.equals(WifiManager.ALL_ZEROS_MAC_ADDRESS))) { throw new IllegalStateException("invalid bssid for suggestion"); } if (TextUtils.isEmpty(mWpa3SaePassphrase) && mSaeH2eOnlyMode) { throw new IllegalStateException( "Hash-to-Element only mode is only allowed for the SAE network"); } wifiConfiguration = buildWifiConfiguration(); if (wifiConfiguration.isOpenNetwork()) { if (mIsSharedWithUserSet && mIsSharedWithUser) { throw new IllegalStateException("Open network should not be " + "setCredentialSharedWithUser to true"); } mIsSharedWithUser = false; } } if (!mIsSharedWithUser && !mIsInitialAutojoinEnabled) { throw new IllegalStateException("Should have not a network with both " + "setCredentialSharedWithUser and " + "setIsAutojoinEnabled set to false"); } if (mIsNetworkUntrusted || mIsNetworkRestricted) { if (mIsSharedWithUserSet && mIsSharedWithUser) { throw new IllegalStateException("Should not be both" + "setCredentialSharedWithUser and +" + "setUntrusted or setRestricted to true"); } mIsSharedWithUser = false; } if (mIsNetworkOemPaid) { if (mIsSharedWithUserSet && mIsSharedWithUser) { throw new IllegalStateException("Should not be both" + "setCredentialSharedWithUser and +" + "setOemPaid to true"); } mIsSharedWithUser = false; } if (mIsNetworkOemPrivate) { if (mIsSharedWithUserSet && mIsSharedWithUser) { throw new IllegalStateException("Should not be both" + "setCredentialSharedWithUser and +" + "setOemPrivate to true"); } mIsSharedWithUser = false; } if (mIsCarrierMerged) { if ((mSubscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID && mSubscriptionGroup == null) || mMeteredOverride != WifiConfiguration.METERED_OVERRIDE_METERED || !isEnterpriseSuggestion()) { throw new IllegalStateException("A carrier merged network must be a metered, " + "enterprise network with valid subscription Id"); } } if (mSubscriptionGroup != null && mSubscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { throw new IllegalStateException("Should not be set both SubscriptionGroup and " + "SubscriptionId"); } return new WifiNetworkSuggestion( wifiConfiguration, mPasspointConfiguration, mIsAppInteractionRequired, mIsUserInteractionRequired, mIsSharedWithUser, mIsInitialAutojoinEnabled, mPriorityGroup); } private boolean isEnterpriseSuggestion() { return !(mWpa2EnterpriseConfig == null && mWpa3EnterpriseConfig == null && mWapiEnterpriseConfig == null && mPasspointConfiguration == null); } } /** * Network configuration for the provided network. * @hide */ @NonNull public final WifiConfiguration wifiConfiguration; /** * Passpoint configuration for the provided network. * @hide */ @Nullable public final PasspointConfiguration passpointConfiguration; /** * Whether app needs to log in to captive portal to obtain Internet access. * @hide */ public final boolean isAppInteractionRequired; /** * Whether user needs to log in to captive portal to obtain Internet access. * @hide */ public final boolean isUserInteractionRequired; /** * Whether app share credential with the user, allow user use provided credential to * connect network manually. * @hide */ public final boolean isUserAllowedToManuallyConnect; /** * Whether the suggestion will be initialized as auto-joined or not. * @hide */ public final boolean isInitialAutoJoinEnabled; /** * Priority group ID. * @hide */ public final int priorityGroup; /** @hide */ public WifiNetworkSuggestion() { this.wifiConfiguration = new WifiConfiguration(); this.passpointConfiguration = null; this.isAppInteractionRequired = false; this.isUserInteractionRequired = false; this.isUserAllowedToManuallyConnect = true; this.isInitialAutoJoinEnabled = true; this.priorityGroup = 0; } /** @hide */ public WifiNetworkSuggestion(@NonNull WifiConfiguration networkConfiguration, @Nullable PasspointConfiguration passpointConfiguration, boolean isAppInteractionRequired, boolean isUserInteractionRequired, boolean isUserAllowedToManuallyConnect, boolean isInitialAutoJoinEnabled, int priorityGroup) { checkNotNull(networkConfiguration); this.wifiConfiguration = networkConfiguration; this.passpointConfiguration = passpointConfiguration; this.isAppInteractionRequired = isAppInteractionRequired; this.isUserInteractionRequired = isUserInteractionRequired; this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect; this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled; this.priorityGroup = priorityGroup; } public static final @NonNull Creator CREATOR = new Creator() { @Override public WifiNetworkSuggestion createFromParcel(Parcel in) { return new WifiNetworkSuggestion( in.readParcelable(null), // wifiConfiguration in.readParcelable(null), // PasspointConfiguration in.readBoolean(), // isAppInteractionRequired in.readBoolean(), // isUserInteractionRequired in.readBoolean(), // isSharedCredentialWithUser in.readBoolean(), // isAutojoinEnabled in.readInt() // priorityGroup ); } @Override public WifiNetworkSuggestion[] newArray(int size) { return new WifiNetworkSuggestion[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(wifiConfiguration, flags); dest.writeParcelable(passpointConfiguration, flags); dest.writeBoolean(isAppInteractionRequired); dest.writeBoolean(isUserInteractionRequired); dest.writeBoolean(isUserAllowedToManuallyConnect); dest.writeBoolean(isInitialAutoJoinEnabled); dest.writeInt(priorityGroup); } @Override public int hashCode() { return Objects.hash(wifiConfiguration.SSID, wifiConfiguration.BSSID, wifiConfiguration.getDefaultSecurityType(), wifiConfiguration.getPasspointUniqueId(), wifiConfiguration.subscriptionId, wifiConfiguration.carrierId, wifiConfiguration.getSubscriptionGroup()); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof WifiNetworkSuggestion)) { return false; } WifiNetworkSuggestion lhs = (WifiNetworkSuggestion) obj; if (this.passpointConfiguration == null ^ lhs.passpointConfiguration == null) { return false; } return TextUtils.equals(this.wifiConfiguration.SSID, lhs.wifiConfiguration.SSID) && TextUtils.equals(this.wifiConfiguration.BSSID, lhs.wifiConfiguration.BSSID) && TextUtils.equals(this.wifiConfiguration.getDefaultSecurityType(), lhs.wifiConfiguration.getDefaultSecurityType()) && TextUtils.equals(this.wifiConfiguration.getPasspointUniqueId(), lhs.wifiConfiguration.getPasspointUniqueId()) && this.wifiConfiguration.carrierId == lhs.wifiConfiguration.carrierId && this.wifiConfiguration.subscriptionId == lhs.wifiConfiguration.subscriptionId && Objects.equals(this.wifiConfiguration.getSubscriptionGroup(), lhs.wifiConfiguration.getSubscriptionGroup()); } @Override public String toString() { StringBuilder sb = new StringBuilder("WifiNetworkSuggestion[ ") .append("SSID=").append(wifiConfiguration.SSID) .append(", BSSID=").append(wifiConfiguration.BSSID) .append(", FQDN=").append(wifiConfiguration.FQDN) .append(", SecurityParams="); wifiConfiguration.getSecurityParamsList().stream() .forEach(param -> { sb.append(" "); sb.append(WifiConfiguration.getSecurityTypeName(param.getSecurityType())); if (param.isAddedByAutoUpgrade()) sb.append("^"); }); sb.append(", isAppInteractionRequired=").append(isAppInteractionRequired) .append(", isUserInteractionRequired=").append(isUserInteractionRequired) .append(", isCredentialSharedWithUser=").append(isUserAllowedToManuallyConnect) .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled) .append(", isUnTrusted=").append(!wifiConfiguration.trusted) .append(", isOemPaid=").append(wifiConfiguration.oemPaid) .append(", isOemPrivate=").append(wifiConfiguration.oemPrivate) .append(", isCarrierMerged=").append(wifiConfiguration.carrierMerged) .append(", isHiddenSsid=").append(wifiConfiguration.hiddenSSID) .append(", priorityGroup=").append(priorityGroup) .append(", subscriptionId=").append(wifiConfiguration.subscriptionId) .append(", subscriptionGroup=").append(wifiConfiguration.getSubscriptionGroup()) .append(", carrierId=").append(wifiConfiguration.carrierId) .append(", priority=").append(wifiConfiguration.priority) .append(", meteredness=").append(wifiConfiguration.meteredOverride) .append(", restricted=").append(wifiConfiguration.restricted) .append(" ]"); return sb.toString(); } /** * Get the {@link WifiConfiguration} associated with this Suggestion. * @hide */ @SystemApi @NonNull public WifiConfiguration getWifiConfiguration() { return wifiConfiguration; } /** * Get the BSSID, or null if unset. * @see Builder#setBssid(MacAddress) */ @Nullable public MacAddress getBssid() { if (wifiConfiguration.BSSID == null) { return null; } return MacAddress.fromString(wifiConfiguration.BSSID); } /** @see Builder#setCredentialSharedWithUser(boolean) */ public boolean isCredentialSharedWithUser() { return isUserAllowedToManuallyConnect; } /** @see Builder#setIsAppInteractionRequired(boolean) */ public boolean isAppInteractionRequired() { return isAppInteractionRequired; } /** @see Builder#setIsEnhancedOpen(boolean) */ public boolean isEnhancedOpen() { return wifiConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE); } /** @see Builder#setIsHiddenSsid(boolean) */ public boolean isHiddenSsid() { return wifiConfiguration.hiddenSSID; } /** @see Builder#setIsInitialAutojoinEnabled(boolean) */ public boolean isInitialAutojoinEnabled() { return isInitialAutoJoinEnabled; } /** @see Builder#setIsMetered(boolean) */ public boolean isMetered() { return wifiConfiguration.meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED; } /** @see Builder#setIsUserInteractionRequired(boolean) */ public boolean isUserInteractionRequired() { return isUserInteractionRequired; } /** * Get the {@link PasspointConfiguration} associated with this Suggestion, or null if this * Suggestion is not for a Passpoint network. */ @Nullable public PasspointConfiguration getPasspointConfig() { return passpointConfiguration; } /** @see Builder#setPriority(int) */ @IntRange(from = 0) public int getPriority() { return wifiConfiguration.priority; } /** * Return the unicode SSID of the network, or null if this is a Passpoint network or the SSID is * non-unicode. *

    * Note: use {@link #getWifiSsid()} which supports both unicode and non-unicode SSID. * @see Builder#setSsid(String) */ @Nullable public String getSsid() { if (wifiConfiguration.SSID == null) { return null; } WifiSsid wifiSsid; try { wifiSsid = WifiSsid.fromString(wifiConfiguration.SSID); } catch (IllegalArgumentException e) { return null; } if (wifiSsid.getUtf8Text() == null) { return null; } return wifiSsid.getUtf8Text().toString(); } /** * Return the {@link WifiSsid} of the network, or null if this is a Passpoint network. * @see Builder#setWifiSsid(WifiSsid) * @return An object representing the SSID the network. {@code null} for passpoint network. */ @Nullable public WifiSsid getWifiSsid() { if (wifiConfiguration.SSID == null) { return null; } WifiSsid wifiSsid; try { wifiSsid = WifiSsid.fromString(wifiConfiguration.SSID); } catch (IllegalArgumentException e) { throw new IllegalStateException("Invalid SSID in the network suggestion"); } return wifiSsid; } /** @see Builder#setUntrusted(boolean) */ public boolean isUntrusted() { return !wifiConfiguration.trusted; } /** * Return if a suggestion is for a restricted network * @see Builder#setRestricted(boolean) * @return true if the suggestion is restricted, false otherwise */ public boolean isRestricted() { return wifiConfiguration.restricted; } /** * @see Builder#setOemPaid(boolean) * @hide */ @SystemApi @RequiresApi(Build.VERSION_CODES.S) public boolean isOemPaid() { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } return wifiConfiguration.oemPaid; } /** * @see Builder#setOemPrivate(boolean) * @hide */ @SystemApi @RequiresApi(Build.VERSION_CODES.S) public boolean isOemPrivate() { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } return wifiConfiguration.oemPrivate; } /** * @see Builder#setCarrierMerged(boolean) */ @RequiresApi(Build.VERSION_CODES.S) public boolean isCarrierMerged() { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } return wifiConfiguration.carrierMerged; } /** * Get the WifiEnterpriseConfig, or null if unset. * @see Builder#setWapiEnterpriseConfig(WifiEnterpriseConfig) * @see Builder#setWpa2EnterpriseConfig(WifiEnterpriseConfig) * @see Builder#setWpa3EnterpriseConfig(WifiEnterpriseConfig) */ @Nullable public WifiEnterpriseConfig getEnterpriseConfig() { if (!wifiConfiguration.isEnterprise()) { return null; } return wifiConfiguration.enterpriseConfig; } /** * Get the passphrase, or null if unset. * @see Builder#setWapiPassphrase(String) * @see Builder#setWpa2Passphrase(String) * @see Builder#setWpa3Passphrase(String) */ @Nullable public String getPassphrase() { if (wifiConfiguration.preSharedKey == null) { return null; } return WifiInfo.removeDoubleQuotes(wifiConfiguration.preSharedKey); } /** * @see Builder#setPriorityGroup(int) */ @IntRange(from = 0) public int getPriorityGroup() { return priorityGroup; } /** * @see Builder#setSubscriptionId(int) */ @RequiresApi(Build.VERSION_CODES.S) public int getSubscriptionId() { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } return wifiConfiguration.subscriptionId; } /** * @see Builder#setCarrierId(int) * @hide */ @SystemApi public int getCarrierId() { return wifiConfiguration.carrierId; } /** * Get the MAC randomization method. * @return one of {@code RANDOMIZATION_*} values * @see Builder#setMacRandomizationSetting(int) */ public @MacRandomizationSetting int getMacRandomizationSetting() { return wifiConfiguration.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_NON_PERSISTENT ? RANDOMIZATION_NON_PERSISTENT : RANDOMIZATION_PERSISTENT; } /** * Get the subscription Group UUID of the suggestion * @see Builder#setSubscriptionGroup(ParcelUuid) * @return Uuid represent a Subscription Group */ @RequiresApi(Build.VERSION_CODES.TIRAMISU) public @Nullable ParcelUuid getSubscriptionGroup() { if (!SdkLevel.isAtLeastT()) { throw new UnsupportedOperationException(); } return wifiConfiguration.getSubscriptionGroup(); } /** * See {@link Builder#setWifi7Enabled(boolean)} */ @FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API) public boolean isWifi7Enabled() { return wifiConfiguration.isWifi7Enabled(); } }