/* * 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 android.net.wifi.ScanResult.UNSPECIFIED; import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.MacAddress; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.wifi.ScanResult.WifiBand; import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; import android.text.TextUtils; import android.util.Pair; import com.android.modules.utils.build.SdkLevel; import java.nio.charset.CharsetEncoder; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Objects; /** * Network specifier object used to request a Wi-Fi network. Apps should use the * {@link WifiNetworkSpecifier.Builder} class to create an instance. *

* This specifier can be used to request a local-only connection on devices that support concurrent * connections (indicated via * {@link WifiManager#isStaConcurrencyForLocalOnlyConnectionsSupported()} and if the initiating app * targets SDK ≥ {@link android.os.Build.VERSION_CODES#S} or is a system app. These local-only * connections may be brought up as a secondary concurrent connection (primary connection will be * used for networks with internet connectivity available to the user and all apps). *

*

* This specifier can also be used to listen for connected Wi-Fi networks on a particular band. * Additionally, some devices may support requesting a connection to a particular band. If the * device does not support such a request, it will send {@link NetworkCallback#onUnavailable()} * upon request to the callback passed to * {@link ConnectivityManager#requestNetwork(NetworkRequest, NetworkCallback)} or equivalent. * See {@link Builder#build()} for details. *

*/ public final class WifiNetworkSpecifier extends NetworkSpecifier implements Parcelable { private static final String TAG = "WifiNetworkSpecifier"; /** * Returns the band for a given frequency in MHz. * @hide */ @WifiBand public static int getBand(final int freqMHz) { if (ScanResult.is24GHz(freqMHz)) { return ScanResult.WIFI_BAND_24_GHZ; } else if (ScanResult.is5GHz(freqMHz)) { return ScanResult.WIFI_BAND_5_GHZ; } else if (ScanResult.is6GHz(freqMHz)) { return ScanResult.WIFI_BAND_6_GHZ; } else if (ScanResult.is60GHz(freqMHz)) { return ScanResult.WIFI_BAND_60_GHZ; } return UNSPECIFIED; } /** * Check the channel in the array is valid. * @hide */ public static boolean validateChannelFrequencyInMhz(@NonNull int[] channels) { if (channels == null) { return false; } for (int channel : channels) { if (ScanResult.convertFrequencyMhzToChannelIfSupported(channel) == UNSPECIFIED) { return false; } } return true; } /** * Validates that the passed band is a valid band * @param band the band to check * @return true if the band is valid, false otherwise * @hide */ public static boolean validateBand(@WifiBand int band) { switch (band) { case UNSPECIFIED: case ScanResult.WIFI_BAND_24_GHZ: case ScanResult.WIFI_BAND_5_GHZ: case ScanResult.WIFI_BAND_5_GHZ_LOW: case ScanResult.WIFI_BAND_5_GHZ_HIGH: case ScanResult.WIFI_BAND_6_GHZ: case ScanResult.WIFI_BAND_60_GHZ: return true; default: return false; } } /** * Builder used to create {@link WifiNetworkSpecifier} objects. */ public static final class Builder { private static final String MATCH_ALL_SSID_PATTERN_PATH = ".*"; private static final String MATCH_EMPTY_SSID_PATTERN_PATH = ""; private static final Pair MATCH_NO_BSSID_PATTERN1 = new Pair<>(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS); private static final Pair MATCH_NO_BSSID_PATTERN2 = new Pair<>(WifiManager.ALL_ZEROS_MAC_ADDRESS, MacAddress.BROADCAST_ADDRESS); private static final Pair MATCH_ALL_BSSID_PATTERN = new Pair<>(WifiManager.ALL_ZEROS_MAC_ADDRESS, WifiManager.ALL_ZEROS_MAC_ADDRESS); private static final MacAddress MATCH_EXACT_BSSID_PATTERN_MASK = MacAddress.BROADCAST_ADDRESS; /** * 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 pattern match specified by the app. */ private @Nullable PatternMatcher mSsidPatternMatcher; /** * BSSID pattern match specified by the app. * Pair of . */ private @Nullable Pair mBssidPatternMatcher; /** * 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; /** * This is a network that does not broadcast its SSID, so an * SSID-specific probe request must be used for scans. */ private boolean mIsHiddenSSID; /** * The requested band for this connection, or BAND_UNSPECIFIED. */ @WifiBand private int mBand; private int[] mChannels; public Builder() { mSsidPatternMatcher = null; mBssidPatternMatcher = null; mIsEnhancedOpen = false; mWpa2PskPassphrase = null; mWpa3SaePassphrase = null; mWpa2EnterpriseConfig = null; mWpa3EnterpriseConfig = null; mIsHiddenSSID = false; mBand = UNSPECIFIED; mChannels = new int[0]; } /** * Set the unicode SSID match pattern to use for filtering networks from scan results. *

*

  • Overrides any previous value set using {@link #setSsid(String)} or * {@link #setSsidPattern(PatternMatcher)}.
  • * * @param ssidPattern Instance of {@link PatternMatcher} containing the UTF-8 encoded * string pattern to use for matching the network's SSID. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setSsidPattern(@NonNull PatternMatcher ssidPattern) { checkNotNull(ssidPattern); mSsidPatternMatcher = ssidPattern; return this; } /** * Set the unicode SSID for the network. *

    *

  • Sets the SSID to use for filtering networks from scan results. Will only match * networks whose SSID is identical to the UTF-8 encoding of the specified value.
  • *
  • Overrides any previous value set using {@link #setSsid(String)} or * {@link #setSsidPattern(PatternMatcher)}.
  • * * @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"); } mSsidPatternMatcher = new PatternMatcher(ssid, PatternMatcher.PATTERN_LITERAL); return this; } /** * Set the BSSID match pattern to use for filtering networks from scan results. * Will match all networks with BSSID which satisfies the following: * {@code BSSID & mask == baseAddress}. *

    *

  • Overrides any previous value set using {@link #setBssid(MacAddress)} or * {@link #setBssidPattern(MacAddress, MacAddress)}.
  • * * @param baseAddress Base address for BSSID pattern. * @param mask Mask for BSSID pattern. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setBssidPattern( @NonNull MacAddress baseAddress, @NonNull MacAddress mask) { checkNotNull(baseAddress); checkNotNull(mask); mBssidPatternMatcher = Pair.create(baseAddress, mask); 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. *

    *

  • Sets the BSSID to use for filtering networks from scan results. Will only match * networks whose BSSID is identical to specified value.
  • *
  • Overrides any previous value set using {@link #setBssid(MacAddress)} or * {@link #setBssidPattern(MacAddress, 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); mBssidPatternMatcher = Pair.create(bssid, MATCH_EXACT_BSSID_PATTERN_MASK); return this; } /** * Specifies whether this represents an Enhanced Open (OWE) network. * * @param isEnhancedOpen {@code true} to indicate that the network uses 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-EAP networks. See {@link WifiEnterpriseConfig} for description. Local-only * connection will not support Trust On First Use (TOFU). If TOFU is enabled on this * Enterprise Config, framework will reject the connection. See {@link * WifiEnterpriseConfig#enableTrustOnFirstUse} * * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setWpa2EnterpriseConfig( @NonNull WifiEnterpriseConfig enterpriseConfig) { checkNotNull(enterpriseConfig); 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. */ @Deprecated public @NonNull Builder setWpa3EnterpriseConfig( @NonNull WifiEnterpriseConfig enterpriseConfig) { checkNotNull(enterpriseConfig); mWpa3EnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig); return this; } /** * Set the associated enterprise configuration for this network. Needed for authenticating * to standard WPA3-Enterprise networks. See {@link WifiEnterpriseConfig} for description. * For WPA3-Enterprise in 192-bit security mode networks, see {@link * #setWpa3Enterprise192BitModeConfig(WifiEnterpriseConfig)} for description. Local-only * connection will not support Trust On First Use (TOFU). If TOFU is enabled on this * Enterprise Config, framework will reject the connection. See {@link * WifiEnterpriseConfig#enableTrustOnFirstUse} * * @param enterpriseConfig Instance of {@link WifiEnterpriseConfig}. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setWpa3EnterpriseStandardModeConfig( @NonNull WifiEnterpriseConfig enterpriseConfig) { checkNotNull(enterpriseConfig); 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). Local-only connection will not support Trust On First Use (TOFU). * If TOFU is enabled on this Enterprise Config, framework will reject the connection. See * {@link WifiEnterpriseConfig#enableTrustOnFirstUse} * * @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; } /** * Specifies whether this represents a hidden network. *

    *

  • Setting this disallows the usage of {@link #setSsidPattern(PatternMatcher)} since * hidden networks need to be explicitly probed for.
  • *
  • 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 band requested for this network. * * Only a single band can be requested. An app can file multiple callbacks concurrently * if they need to know about multiple bands. * * @param band The requested band. * @return Instance of {@link Builder} to enable chaining of the builder method. */ public @NonNull Builder setBand(@WifiBand int band) { if (!validateBand(band)) { throw new IllegalArgumentException("Unexpected band in setBand : " + band); } mBand = band; return this; } /** * Specifies the preferred channels for this network. The channels set in the request will * be used to optimize the scan and connection. *

    *

  • Should only be set to request local-only network
  • *
  • If not set, defaults to an empty array and device will do a full band scan.
  • * * @param channelFreqs an Array of the channels in MHz. The length of the array must not * exceed {@link WifiManager#getMaxNumberOfChannelsPerNetworkSpecifierRequest()} * * @return Instance of {@link Builder} to enable chaining of the builder method. */ @NonNull public Builder setPreferredChannelsFrequenciesMhz(@NonNull int[] channelFreqs) { Objects.requireNonNull(channelFreqs); if (!validateChannelFrequencyInMhz(channelFreqs)) { throw new IllegalArgumentException("Invalid channel frequency in the input array"); } mChannels = channelFreqs.clone(); 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 + "\""; } 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 { // 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. if (mSsidPatternMatcher.getType() == PatternMatcher.PATTERN_LITERAL) { wifiConfiguration.SSID = "\"" + mSsidPatternMatcher.getPath() + "\""; } if (mBssidPatternMatcher.second == MATCH_EXACT_BSSID_PATTERN_MASK) { wifiConfiguration.BSSID = mBssidPatternMatcher.first.toString(); } setSecurityParamsInWifiConfiguration(wifiConfiguration); wifiConfiguration.hiddenSSID = mIsHiddenSSID; return wifiConfiguration; } private boolean hasSetAnyPattern() { return mSsidPatternMatcher != null || mBssidPatternMatcher != null; } private void setMatchAnyPatternIfUnset() { if (mSsidPatternMatcher == null) { mSsidPatternMatcher = new PatternMatcher(MATCH_ALL_SSID_PATTERN_PATH, PatternMatcher.PATTERN_SIMPLE_GLOB); } if (mBssidPatternMatcher == null) { mBssidPatternMatcher = MATCH_ALL_BSSID_PATTERN; } } private boolean hasSetMatchNonePattern() { if (mSsidPatternMatcher.getType() != PatternMatcher.PATTERN_PREFIX && mSsidPatternMatcher.getPath().equals(MATCH_EMPTY_SSID_PATTERN_PATH)) { return true; } if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN1)) { return true; } if (mBssidPatternMatcher.equals(MATCH_NO_BSSID_PATTERN2)) { return true; } return false; } private boolean hasSetMatchAllPattern() { if ((mSsidPatternMatcher.match(MATCH_EMPTY_SSID_PATTERN_PATH)) && mBssidPatternMatcher.equals(MATCH_ALL_BSSID_PATTERN)) { return true; } return false; } private void validateSecurityParams() { int numSecurityTypes = 0; numSecurityTypes += mIsEnhancedOpen ? 1 : 0; numSecurityTypes += !TextUtils.isEmpty(mWpa2PskPassphrase) ? 1 : 0; numSecurityTypes += !TextUtils.isEmpty(mWpa3SaePassphrase) ? 1 : 0; numSecurityTypes += mWpa2EnterpriseConfig != null ? 1 : 0; numSecurityTypes += mWpa3EnterpriseConfig != null ? 1 : 0; if (numSecurityTypes > 1) { throw new IllegalStateException("only one of setIsEnhancedOpen, setWpa2Passphrase," + "setWpa3Passphrase, setWpa2EnterpriseConfig or setWpa3EnterpriseConfig" + " can be invoked for network specifier"); } } /** * Create a specifier object used to request a Wi-Fi network. The generated * {@link NetworkSpecifier} should be used in * {@link NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} when building * the {@link NetworkRequest}. * *

    * When using with {@link ConnectivityManager#requestNetwork(NetworkRequest, * NetworkCallback)} or variants, note that some devices may not support requesting a * network with all combinations of specifier members. For example, some devices may only * support requesting local-only networks (networks without the * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability), or not support * requesting a particular band. However, there are no restrictions when using * {@link ConnectivityManager#registerNetworkCallback(NetworkRequest, NetworkCallback)} * or other similar methods which monitor but do not request networks. * * If the device can't support a request, the app will receive a call to * {@link NetworkCallback#onUnavailable()}. *

    * *

    * When requesting a local-only network, apps can set a combination of network match params: *

  • SSID Pattern using {@link #setSsidPattern(PatternMatcher)} OR Specific SSID using * {@link #setSsid(String)}.
  • * AND/OR *
  • BSSID Pattern using {@link #setBssidPattern(MacAddress, MacAddress)} OR Specific * BSSID using {@link #setBssid(MacAddress)}
  • * to trigger connection to a network that matches the set params. * The system will find the set of networks matching the request and present the user * with a system dialog which will allow the user to select a specific Wi-Fi network to * connect to or to deny the request. * * To protect user privacy, some limitations to the ability of matching patterns apply. * In particular, when the system brings up a network to satisfy a {@link NetworkRequest} * from some app, the system reserves the right to decline matching the SSID pattern to * the real SSID of the network for other apps than the app that requested the network, and * not send those callbacks even if the SSID matches the requested pattern. *

    * * For example: * To connect to an open network with a SSID prefix of "test" and a BSSID OUI of "10:03:23": * *
    {@code
             * final NetworkSpecifier specifier =
             *      new Builder()
             *      .setSsidPattern(new PatternMatcher("test", PatternMatcher.PATTERN_PREFIX))
             *      .setBssidPattern(MacAddress.fromString("10:03:23:00:00:00"),
             *                       MacAddress.fromString("ff:ff:ff:00:00:00"))
             *      .build()
             * final NetworkRequest request =
             *      new NetworkRequest.Builder()
             *      .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
             *      .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
             *      .setNetworkSpecifier(specifier)
             *      .build();
             * final ConnectivityManager connectivityManager =
             *      context.getSystemService(Context.CONNECTIVITY_SERVICE);
             * final NetworkCallback networkCallback = new NetworkCallback() {
             *      ...
             *      {@literal @}Override
             *      void onAvailable(...) {}
             *      // etc.
             * };
             * connectivityManager.requestNetwork(request, networkCallback);
             * }
    * * @return Instance of {@link NetworkSpecifier}. * @throws IllegalStateException on invalid params set. */ public @NonNull WifiNetworkSpecifier build() { if (!hasSetAnyPattern() && mBand == UNSPECIFIED) { throw new IllegalStateException("one of setSsidPattern/setSsid/setBssidPattern/" + "setBssid/setBand should be invoked for specifier"); } setMatchAnyPatternIfUnset(); if (hasSetMatchNonePattern()) { throw new IllegalStateException("cannot set match-none pattern for specifier"); } if (hasSetMatchAllPattern() && mBand == UNSPECIFIED) { throw new IllegalStateException("cannot set match-all pattern for specifier"); } if (mIsHiddenSSID && mSsidPatternMatcher.getType() != PatternMatcher.PATTERN_LITERAL) { throw new IllegalStateException("setSsid should also be invoked when " + "setIsHiddenSsid is invoked for network specifier"); } if (mChannels.length != 0 && mBand != UNSPECIFIED) { throw new IllegalStateException("cannot setPreferredChannelsFrequencyInMhz with " + "setBand together"); } validateSecurityParams(); return new WifiNetworkSpecifier( mSsidPatternMatcher, mBssidPatternMatcher, mBand, buildWifiConfiguration(), mChannels); } } /** * SSID pattern match specified by the app. * @hide */ public final PatternMatcher ssidPatternMatcher; /** * BSSID pattern match specified by the app. * Pair of . * @hide */ public final Pair bssidPatternMatcher; /** * The band for this Wi-Fi network. */ @WifiBand private final int mBand; private final int[] mChannelFreqs; /** * Security credentials for the network. *

    * Note: {@link WifiConfiguration#SSID} & {@link WifiConfiguration#BSSID} fields from * WifiConfiguration are not used. Instead we use the {@link #ssidPatternMatcher} & * {@link #bssidPatternMatcher} fields embedded directly * within {@link WifiNetworkSpecifier}. * @hide */ public final WifiConfiguration wifiConfiguration; /** @hide */ public WifiNetworkSpecifier() throws IllegalAccessException { throw new IllegalAccessException("Use the builder to create an instance"); } /** @hide */ public WifiNetworkSpecifier(@NonNull PatternMatcher ssidPatternMatcher, @NonNull Pair bssidPatternMatcher, @WifiBand int band, @NonNull WifiConfiguration wifiConfiguration, @NonNull int[] channelFreqs) { checkNotNull(ssidPatternMatcher); checkNotNull(bssidPatternMatcher); checkNotNull(wifiConfiguration); this.ssidPatternMatcher = ssidPatternMatcher; this.bssidPatternMatcher = bssidPatternMatcher; this.mBand = band; this.wifiConfiguration = wifiConfiguration; this.mChannelFreqs = channelFreqs; } /** * The band for this Wi-Fi network specifier. */ @WifiBand public int getBand() { return mBand; } /** * The preferred channels fot this network specifier. * @see Builder#setPreferredChannelsFrequenciesMhz(int[]) */ @NonNull public int[] getPreferredChannelFrequenciesMhz() { return mChannelFreqs.clone(); } public static final @NonNull Creator CREATOR = new Creator() { @Override public WifiNetworkSpecifier createFromParcel(Parcel in) { PatternMatcher ssidPatternMatcher = in.readParcelable(/* classLoader */null); MacAddress baseAddress = in.readParcelable(null); MacAddress mask = in.readParcelable(null); Pair bssidPatternMatcher = Pair.create(baseAddress, mask); int band = in.readInt(); WifiConfiguration wifiConfiguration = in.readParcelable(null); int[] mChannels = in.createIntArray(); return new WifiNetworkSpecifier(ssidPatternMatcher, bssidPatternMatcher, band, wifiConfiguration, mChannels); } @Override public WifiNetworkSpecifier[] newArray(int size) { return new WifiNetworkSpecifier[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(ssidPatternMatcher, flags); dest.writeParcelable(bssidPatternMatcher.first, flags); dest.writeParcelable(bssidPatternMatcher.second, flags); dest.writeInt(mBand); dest.writeParcelable(wifiConfiguration, flags); dest.writeIntArray(mChannelFreqs); } @Override public int hashCode() { return Objects.hash( ssidPatternMatcher.getPath(), ssidPatternMatcher.getType(), bssidPatternMatcher, mBand, wifiConfiguration.allowedKeyManagement, Arrays.hashCode(mChannelFreqs)); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof WifiNetworkSpecifier)) { return false; } WifiNetworkSpecifier lhs = (WifiNetworkSpecifier) obj; return Objects.equals(this.ssidPatternMatcher.getPath(), lhs.ssidPatternMatcher.getPath()) && Objects.equals(this.ssidPatternMatcher.getType(), lhs.ssidPatternMatcher.getType()) && Objects.equals(this.bssidPatternMatcher, lhs.bssidPatternMatcher) && this.mBand == lhs.mBand && Objects.equals(this.wifiConfiguration.allowedKeyManagement, lhs.wifiConfiguration.allowedKeyManagement) && Arrays.equals(mChannelFreqs, lhs.mChannelFreqs); } @Override public String toString() { return new StringBuilder() .append("WifiNetworkSpecifier [") .append(", SSID Match pattern=").append(ssidPatternMatcher) .append(", BSSID Match pattern=").append(bssidPatternMatcher) .append(", SSID=").append(wifiConfiguration.SSID) .append(", BSSID=").append(wifiConfiguration.BSSID) .append(", band=").append(mBand) .append("]") .toString(); } /** @hide */ @Override public boolean canBeSatisfiedBy(NetworkSpecifier other) { if (other instanceof WifiNetworkAgentSpecifier) { return ((WifiNetworkAgentSpecifier) other).satisfiesNetworkSpecifier(this); } // Specific requests are checked for equality although testing for equality of 2 patterns do // not make much sense! return equals(other); } /** @hide */ @Override @Nullable public NetworkSpecifier redact() { if (!SdkLevel.isAtLeastS()) return this; return new Builder().setBand(mBand).build(); } }