/* * Copyright (C) 2016 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 com.android.server.wifi.util; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_BAND_24G_SUPPORTED; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_BAND_5G_SUPPORTED; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_BAND_60G_SUPPORTED; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_BAND_6G_SUPPORTED; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_IEEE80211_AX; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_IEEE80211_BE; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_WPA3_OWE; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_WPA3_OWE_TRANSITION; import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_WPA3_SAE; import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_AP_BRIDGE; import static com.android.server.wifi.HalDeviceManager.HDM_CREATE_IFACE_STA; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.net.wifi.CoexUnsafeChannel; import android.net.wifi.ScanResult; import android.net.wifi.SoftApCapability; import android.net.wifi.SoftApConfiguration; import android.net.wifi.SoftApConfiguration.BandType; import android.net.wifi.SoftApInfo; import android.net.wifi.WifiAvailableChannel; import android.net.wifi.WifiClient; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.net.wifi.WifiScanner; import android.net.wifi.nl80211.DeviceWiphyCapabilities; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.android.server.wifi.SoftApManager; import com.android.server.wifi.WifiNative; import com.android.server.wifi.WifiSettingsConfigStore; import com.android.server.wifi.coex.CoexManager; import com.android.wifi.resources.R; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.StringJoiner; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * Provide utility functions for updating soft AP related configuration. */ public class ApConfigUtil { private static final String TAG = "ApConfigUtil"; public static final int INVALID_VALUE_FOR_BAND_OR_CHANNEL = -1; public static final int DEFAULT_AP_BAND = SoftApConfiguration.BAND_2GHZ; public static final int DEFAULT_AP_CHANNEL = 6; public static final int HIGHEST_2G_AP_CHANNEL = 14; /* Random number generator used for AP channel selection. */ private static final Random sRandom = new Random(); private static boolean sVerboseLoggingEnabled = false; /** * Enable or disable verbose logging * @param verboseEnabled true if verbose logging is enabled */ public static void enableVerboseLogging(boolean verboseEnabled) { sVerboseLoggingEnabled = verboseEnabled; } /** * Valid Global Operating classes in each wifi band * Reference: Table E-4 in IEEE Std 802.11-2016. */ private static final SparseArray sBandToOperatingClass = new SparseArray<>(); static { sBandToOperatingClass.append(SoftApConfiguration.BAND_2GHZ, new int[]{81, 82, 83, 84}); sBandToOperatingClass.append(SoftApConfiguration.BAND_5GHZ, new int[]{115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130}); sBandToOperatingClass.append(SoftApConfiguration.BAND_6GHZ, new int[]{131, 132, 133, 134, 135, 136}); } /** * Converts a SoftApConfiguration.BAND_* constant to a meaningful String */ public static String bandToString(int band) { StringJoiner sj = new StringJoiner(" & "); sj.setEmptyValue("unspecified"); if ((band & SoftApConfiguration.BAND_2GHZ) != 0) { sj.add("2Ghz"); } band &= ~SoftApConfiguration.BAND_2GHZ; if ((band & SoftApConfiguration.BAND_5GHZ) != 0) { sj.add("5Ghz"); } band &= ~SoftApConfiguration.BAND_5GHZ; if ((band & SoftApConfiguration.BAND_6GHZ) != 0) { sj.add("6Ghz"); } band &= ~SoftApConfiguration.BAND_6GHZ; if ((band & SoftApConfiguration.BAND_60GHZ) != 0) { sj.add("60Ghz"); } band &= ~SoftApConfiguration.BAND_60GHZ; if (band != 0) { return "Invalid band"; } return sj.toString(); } /** * Helper function to get the band corresponding to the operating class. * * @param operatingClass Global operating class. * @return band, -1 if no match. * */ public static int getBandFromOperatingClass(int operatingClass) { for (int i = 0; i < sBandToOperatingClass.size(); i++) { int band = sBandToOperatingClass.keyAt(i); int[] operatingClasses = sBandToOperatingClass.get(band); for (int j = 0; j < operatingClasses.length; j++) { if (operatingClasses[j] == operatingClass) { return band; } } } return -1; } /** * Convert band from SoftApConfiguration.BandType to WifiScanner.WifiBand * @param band in SoftApConfiguration.BandType * @return band in WifiScanner.WifiBand */ public static @WifiScanner.WifiBand int apConfig2wifiScannerBand(@BandType int band) { switch(band) { case SoftApConfiguration.BAND_2GHZ: return WifiScanner.WIFI_BAND_24_GHZ; case SoftApConfiguration.BAND_5GHZ: return WifiScanner.WIFI_BAND_5_GHZ; case SoftApConfiguration.BAND_6GHZ: return WifiScanner.WIFI_BAND_6_GHZ; case SoftApConfiguration.BAND_60GHZ: return WifiScanner.WIFI_BAND_60_GHZ; default: return WifiScanner.WIFI_BAND_UNSPECIFIED; } } /** * Convert channel/band to frequency. * Note: the utility does not perform any regulatory domain compliance. * @param channel number to convert * @param band of channel to convert * @return center frequency in Mhz of the channel, -1 if no match */ public static int convertChannelToFrequency(int channel, @BandType int band) { return ScanResult.convertChannelToFrequencyMhzIfSupported(channel, apConfig2wifiScannerBand(band)); } /** * Convert frequency to band. * Note: the utility does not perform any regulatory domain compliance. * @param frequency frequency to convert * @return band, -1 if no match */ public static int convertFrequencyToBand(int frequency) { if (ScanResult.is24GHz(frequency)) { return SoftApConfiguration.BAND_2GHZ; } else if (ScanResult.is5GHz(frequency)) { return SoftApConfiguration.BAND_5GHZ; } else if (ScanResult.is6GHz(frequency)) { return SoftApConfiguration.BAND_6GHZ; } else if (ScanResult.is60GHz(frequency)) { return SoftApConfiguration.BAND_60GHZ; } return -1; } /** * Convert band from WifiConfiguration into SoftApConfiguration * * @param wifiConfigBand band encoded as WifiConfiguration.AP_BAND_xxxx * @return band as encoded as SoftApConfiguration.BAND_xxx */ public static int convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand) { switch (wifiConfigBand) { case WifiConfiguration.AP_BAND_2GHZ: return SoftApConfiguration.BAND_2GHZ; case WifiConfiguration.AP_BAND_5GHZ: return SoftApConfiguration.BAND_5GHZ; case WifiConfiguration.AP_BAND_ANY: return SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ; default: return SoftApConfiguration.BAND_2GHZ; } } /** * Add 2.4Ghz to target band when 2.4Ghz SoftAp supported. * * @param targetBand The band is needed to add 2.4G. * @return The band includes 2.4Ghz when 2.4G SoftAp supported. */ public static @BandType int append24GToBandIf24GSupported(@BandType int targetBand, Context context) { if (isBandSupported(SoftApConfiguration.BAND_2GHZ, context)) { return targetBand | SoftApConfiguration.BAND_2GHZ; } return targetBand; } /** * Add 5Ghz to target band when 5Ghz SoftAp supported. * * @param targetBand The band is needed to add 5GHz band. * @return The band includes 5Ghz when 5G SoftAp supported. */ public static @BandType int append5GToBandIf5GSupported(@BandType int targetBand, Context context) { if (isBandSupported(SoftApConfiguration.BAND_5GHZ, context)) { return targetBand | SoftApConfiguration.BAND_5GHZ; } return targetBand; } /** * Checks if band is a valid combination of {link SoftApConfiguration#BandType} values */ public static boolean isBandValid(@BandType int band) { int bandAny = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ | SoftApConfiguration.BAND_6GHZ | SoftApConfiguration.BAND_60GHZ; return ((band != 0) && ((band & ~bandAny) == 0)); } /** * Check if the band contains a certain sub-band * * @param band The combination of bands to validate * @param testBand the test band to validate on * @return true if band contains testBand, false otherwise */ public static boolean containsBand(@BandType int band, @BandType int testBand) { return ((band & testBand) != 0); } /** * Checks if band contains multiple sub-bands * @param band a combination of sub-bands * @return true if band has multiple sub-bands, false otherwise */ public static boolean isMultiband(@BandType int band) { return ((band & (band - 1)) != 0); } /** * Checks whether or not band configuration is supported. * @param apBand a combination of the bands * @param context the caller context used to get value from resource file. * @return true if band is supported, false otherwise */ public static boolean isBandSupported(@BandType int apBand, Context context) { if (!isBandValid(apBand)) { Log.e(TAG, "Invalid SoftAp band " + apBand); return false; } for (int b : SoftApConfiguration.BAND_TYPES) { if (containsBand(apBand, b) && !isSoftApBandSupported(context, b)) { Log.e(TAG, "Can not start softAp with band " + bandToString(b) + " not supported."); return false; } } return true; } /** * Convert string to channel list * Format of the list is a comma separated channel numbers, or range of channel numbers * Example, "34-48, 149". * @param channelString for a comma separated channel numbers, or range of channel numbers * such as "34-48, 149" * @return list of channel numbers */ public static List convertStringToChannelList(String channelString) { if (channelString == null) { return null; } List channelList = new ArrayList(); for (String channelRange : channelString.split(",")) { try { if (channelRange.contains("-")) { String[] channels = channelRange.split("-"); if (channels.length != 2) { Log.e(TAG, "Unrecognized channel range, Length is " + channels.length); continue; } int start = Integer.parseInt(channels[0].trim()); int end = Integer.parseInt(channels[1].trim()); if (start > end) { Log.e(TAG, "Invalid channel range, from " + start + " to " + end); continue; } for (int channel = start; channel <= end; channel++) { channelList.add(channel); } } else { channelList.add(Integer.parseInt(channelRange.trim())); } } catch (NumberFormatException e) { // Ignore malformed string Log.e(TAG, "Malformed channel value detected: " + e); continue; } } return channelList; } /** * Returns the unsafe channels frequency from coex module. * * @param coexManager reference used to get unsafe channels to avoid for coex. */ @NonNull public static Set getUnsafeChannelFreqsFromCoex(@NonNull CoexManager coexManager) { Set unsafeFreqs = new HashSet<>(); if (SdkLevel.isAtLeastS()) { for (CoexUnsafeChannel unsafeChannel : coexManager.getCoexUnsafeChannels()) { unsafeFreqs.add(ScanResult.convertChannelToFrequencyMhzIfSupported( unsafeChannel.getChannel(), unsafeChannel.getBand())); } } return unsafeFreqs; } private static List getConfiguredChannelList(Resources resources, @BandType int band) { switch (band) { case SoftApConfiguration.BAND_2GHZ: return convertStringToChannelList(resources.getString( R.string.config_wifiSoftap2gChannelList)); case SoftApConfiguration.BAND_5GHZ: return convertStringToChannelList(resources.getString( R.string.config_wifiSoftap5gChannelList)); case SoftApConfiguration.BAND_6GHZ: return convertStringToChannelList(resources.getString( R.string.config_wifiSoftap6gChannelList)); case SoftApConfiguration.BAND_60GHZ: return convertStringToChannelList(resources.getString( R.string.config_wifiSoftap60gChannelList)); default: return null; } } private static List addDfsChannelsIfNeeded(List regulatoryList, @WifiScanner.WifiBand int scannerBand, WifiNative wifiNative, Resources resources, boolean inFrequencyMHz) { // Add DFS channels to the supported channel list if the device supports SoftAp // operation in the DFS channel. if (resources.getBoolean(R.bool.config_wifiSoftapAcsIncludeDfs) && scannerBand == WifiScanner.WIFI_BAND_5_GHZ) { int[] dfs5gBand = wifiNative.getChannelsForBand( WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY); for (int freq : dfs5gBand) { final int freqOrChan = inFrequencyMHz ? freq : ScanResult.convertFrequencyMhzToChannelIfSupported(freq); if (!regulatoryList.contains(freqOrChan)) { regulatoryList.add(freqOrChan); } } } return regulatoryList; } private static List getWifiCondAvailableChannelsForBand( @WifiScanner.WifiBand int scannerBand, WifiNative wifiNative, Resources resources, boolean inFrequencyMHz) { List regulatoryList = new ArrayList(); // Get the allowed list of channel frequencies in MHz from wificond int[] regulatoryArray = wifiNative.getChannelsForBand(scannerBand); for (int freq : regulatoryArray) { regulatoryList.add(inFrequencyMHz ? freq : ScanResult.convertFrequencyMhzToChannelIfSupported(freq)); } return addDfsChannelsIfNeeded(regulatoryList, scannerBand, wifiNative, resources, inFrequencyMHz); } private static List getHalAvailableChannelsForBand( @WifiScanner.WifiBand int scannerBand, WifiNative wifiNative, Resources resources, boolean inFrequencyMHz) { // Try vendor HAL API to get the usable channel list. List usableChannelList = wifiNative.getUsableChannels( scannerBand, WifiAvailableChannel.OP_MODE_SAP, WifiAvailableChannel.FILTER_REGULATORY); if (usableChannelList == null) { // If HAL doesn't support getUsableChannels then return null return null; } List regulatoryList = new ArrayList<>(); if (inFrequencyMHz) { usableChannelList.forEach(a -> regulatoryList.add(a.getFrequencyMhz())); } else { usableChannelList.forEach(a -> regulatoryList.add(ScanResult .convertFrequencyMhzToChannelIfSupported(a.getFrequencyMhz()))); } return addDfsChannelsIfNeeded(regulatoryList, scannerBand, wifiNative, resources, inFrequencyMHz); } /** * Get channels or frequencies for band that are allowed by both regulatory * and OEM configuration. * * @param band to get channels for * @param wifiNative reference used to get regulatory restrictions. * @param resources used to get OEM restrictions * @param inFrequencyMHz true to convert channel to frequency. * @return A list of frequencies that are allowed, null on error. */ public static List getAvailableChannelFreqsForBand( @BandType int band, WifiNative wifiNative, Resources resources, boolean inFrequencyMHz) { if (!isBandValid(band) || isMultiband(band)) { return null; } int scannerBand = apConfig2wifiScannerBand(band); List regulatoryList = null; boolean useWifiCond = false; // Check if vendor HAL API for getting usable channels is available. If HAL doesn't support // the API it returns null list, in that case we retrieve the list from wificond. if (!wifiNative.isHalSupported()) { // HAL is not supported, fallback to wificond useWifiCond = true; } else { if (!wifiNative.isHalStarted()) { // HAL is not started, return null return null; } regulatoryList = getHalAvailableChannelsForBand(scannerBand, wifiNative, resources, inFrequencyMHz); if (regulatoryList == null) { // HAL API not supported by HAL, fallback to wificond useWifiCond = true; } } if (useWifiCond) { regulatoryList = getWifiCondAvailableChannelsForBand(scannerBand, wifiNative, resources, inFrequencyMHz); } List configuredList = getConfiguredChannelList(resources, band); if (configuredList == null || configuredList.isEmpty() || regulatoryList == null) { return regulatoryList; } List filteredList = new ArrayList(); // Otherwise, filter the configured list for (int channel : configuredList) { if (inFrequencyMHz) { int channelFreq = convertChannelToFrequency(channel, band); if (regulatoryList.contains(channelFreq)) { filteredList.add(channelFreq); } } else if (regulatoryList.contains(channel)) { filteredList.add(channel); } } if (sVerboseLoggingEnabled) { Log.d(TAG, "Filtered channel list for band " + bandToString(band) + " : " + filteredList.stream().map(Object::toString).collect(Collectors.joining(","))); } return filteredList; } /** * Return a channel frequency for AP setup based on the frequency band. * @param apBand one or combination of the values of SoftApConfiguration.BAND_*. * @param coexManager reference used to get unsafe channels to avoid for coex. * @param resources the resources to use to get configured allowed channels. * @param capability soft AP capability * @return a valid channel frequency on success, -1 on failure. */ public static int chooseApChannel(int apBand, @NonNull CoexManager coexManager, @NonNull Resources resources, SoftApCapability capability) { if (!isBandValid(apBand)) { Log.e(TAG, "Invalid band: " + apBand); return -1; } Set unsafeFreqs = new HashSet<>(); if (SdkLevel.isAtLeastS()) { unsafeFreqs = getUnsafeChannelFreqsFromCoex(coexManager); } final int[] bandPreferences = new int[]{ SoftApConfiguration.BAND_60GHZ, SoftApConfiguration.BAND_6GHZ, SoftApConfiguration.BAND_5GHZ, SoftApConfiguration.BAND_2GHZ}; int selectedUnsafeFreq = 0; for (int band : bandPreferences) { if ((apBand & band) == 0) { continue; } int[] availableChannels = capability.getSupportedChannelList(band); if (availableChannels == null || availableChannels.length == 0) { continue; } final List availableFreqs = Arrays.stream(availableChannels).boxed() .map(ch -> convertChannelToFrequency(ch, band)) .collect(Collectors.toList()); // Separate the available freqs by safe and unsafe. List availableSafeFreqs = new ArrayList<>(); List availableUnsafeFreqs = new ArrayList<>(); for (int freq : availableFreqs) { if (unsafeFreqs.contains(freq)) { availableUnsafeFreqs.add(freq); } else { availableSafeFreqs.add(freq); } } // If there are safe freqs available for this band, randomly select one. if (!availableSafeFreqs.isEmpty()) { return availableSafeFreqs.get(sRandom.nextInt(availableSafeFreqs.size())); } else if (!availableUnsafeFreqs.isEmpty() && selectedUnsafeFreq == 0) { // Save an unsafe freq from the first preferred band to fall back on later. selectedUnsafeFreq = availableUnsafeFreqs.get( sRandom.nextInt(availableUnsafeFreqs.size())); } } // If all available channels are soft unsafe, select a random one of the highest band. boolean isHardUnsafe = false; if (SdkLevel.isAtLeastS()) { isHardUnsafe = (coexManager.getCoexRestrictions() & WifiManager.COEX_RESTRICTION_SOFTAP) != 0; } if (!isHardUnsafe && selectedUnsafeFreq != 0) { return selectedUnsafeFreq; } // If all available channels are hard unsafe, select the default AP channel. if (containsBand(apBand, DEFAULT_AP_BAND)) { final int defaultChannelFreq = convertChannelToFrequency(DEFAULT_AP_CHANNEL, DEFAULT_AP_BAND); Log.e(TAG, "Allowed channel list not specified, selecting default channel"); if (isHardUnsafe && unsafeFreqs.contains(defaultChannelFreq)) { Log.e(TAG, "Default channel is hard restricted due to coex"); } return defaultChannelFreq; } Log.e(TAG, "No available channels"); return -1; } /** * Remove unavailable bands from the input band and return the resulting * (remaining) available bands. Unavailable bands are those which don't have channels available. * * @param capability SoftApCapability which indicates supported channel list. * @param targetBand The target band which plan to enable * @param coexManager reference to CoexManager * * @return the available band which removed the unsupported band. * 0 when all of the band is not supported. */ public static @BandType int removeUnavailableBands(SoftApCapability capability, @NonNull int targetBand, CoexManager coexManager) { int availableBand = targetBand; for (int band : SoftApConfiguration.BAND_TYPES) { Set availableChannelFreqsList = new HashSet<>(); if ((targetBand & band) != 0) { for (int channel : capability.getSupportedChannelList(band)) { availableChannelFreqsList.add(convertChannelToFrequency(channel, band)); } // Only remove hard unsafe channels if (SdkLevel.isAtLeastS() && (coexManager.getCoexRestrictions() & WifiManager.COEX_RESTRICTION_SOFTAP) != 0) { availableChannelFreqsList.removeAll(getUnsafeChannelFreqsFromCoex(coexManager)); } if (availableChannelFreqsList.size() == 0) { availableBand &= ~band; } } } return availableBand; } /** * Remove unavailable bands from the softAp configuration and return the updated configuration. * Unavailable bands are those which don't have channels available. * * @param config The current {@link SoftApConfiguration}. * @param capability SoftApCapability which indicates supported channel list. * @param coexManager reference to CoexManager * @param context the caller context used to get value from resource file. * * @return the updated SoftApConfiguration. */ public static SoftApConfiguration removeUnavailableBandsFromConfig( SoftApConfiguration config, SoftApCapability capability, CoexManager coexManager, @NonNull Context context) { SoftApConfiguration.Builder builder = new SoftApConfiguration.Builder(config); try { if (config.getBands().length == 1) { int configuredBand = config.getBand(); int availableBand = ApConfigUtil.removeUnavailableBands( capability, configuredBand, coexManager); if (availableBand != configuredBand) { availableBand = ApConfigUtil.append24GToBandIf24GSupported(availableBand, context); Log.i(TAG, "Reset band from " + configuredBand + " to " + availableBand + " in single AP configuration"); builder.setBand(availableBand); } } else if (SdkLevel.isAtLeastS()) { SparseIntArray channels = config.getChannels(); SparseIntArray newChannels = new SparseIntArray(channels.size()); for (int i = 0; i < channels.size(); i++) { int configuredBand = channels.keyAt(i); int availableBand = ApConfigUtil.removeUnavailableBands( capability, configuredBand, coexManager); if (availableBand != configuredBand) { Log.i(TAG, "Reset band in index " + i + " from " + configuredBand + " to " + availableBand + " in dual AP configuration"); } if (isBandValid(availableBand)) { newChannels.put(availableBand, channels.valueAt(i)); } } if (newChannels.size() != 0) { builder.setChannels(newChannels); } else { builder.setBand( ApConfigUtil.append24GToBandIf24GSupported(0, context)); } } } catch (Exception e) { Log.e(TAG, "Failed to update config by removing unavailable bands" + e); return null; } return builder.build(); } /** * Remove all unsupported bands from the input band and return the resulting * (remaining) support bands. Unsupported bands are those which don't have channels available. * * @param context The caller context used to get value from resource file. * @param band The target band which plan to enable * * @return the available band which removed the unsupported band. * 0 when all of the band is not supported. */ public static @BandType int removeUnsupportedBands(Context context, @NonNull int band) { int availableBand = band; for (int b : SoftApConfiguration.BAND_TYPES) { if (((band & b) != 0) && !isSoftApBandSupported(context, b)) { availableBand &= ~b; } } return availableBand; } /** * Check if security type is restricted for operation in 6GHz band * As per WFA specification for 6GHz operation, the following security types are not allowed to * be used in 6GHz band: * - OPEN * - WPA2-Personal * - WPA3-SAE-Transition * - WPA3-OWE-Transition * * @param type security type to check on * * @return true if security type is restricted for operation in 6GHz band, false otherwise */ public static boolean isSecurityTypeRestrictedFor6gBand( @SoftApConfiguration.SecurityType int type) { switch(type) { case SoftApConfiguration.SECURITY_TYPE_OPEN: case SoftApConfiguration.SECURITY_TYPE_WPA2_PSK: case SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION: case SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION: return true; } return false; } /** * Checks whether HAL support converting the restricted security type to an allowed one in 6GHz * band configuration. * @param resources the resources to get the OEM configuration for HAL support. * @param type security type. * @return true if HAL support to map WPA3 transition mode to WPA3 in 6GHz band, * false otherwise. */ public static boolean canHALConvertRestrictedSecurityTypeFor6GHz(@NonNull Resources resources, @SoftApConfiguration.SecurityType int type) { return type == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION && resources.getBoolean(R.bool .config_wifiSofapHalMapWpa3TransitionModeToWpa3OnlyIn6GHzBand); } /** * Remove {@link SoftApConfiguration#BAND_6GHZ} if multiple bands are configured * as a mask when security type is restricted to operate in this band. * * @param resources the resources to get the OEM configuration for HAL support. * @param config The current {@link SoftApConfiguration}. * @param isBridgedMode true if bridged mode is enabled, false otherwise. * * @return the updated SoftApConfiguration. */ public static SoftApConfiguration remove6gBandForUnsupportedSecurity( @NonNull Resources resources, SoftApConfiguration config, boolean isBridgedMode) { SoftApConfiguration.Builder builder = new SoftApConfiguration.Builder(config); try { int securityType = config.getSecurityType(); if (config.getBands().length == 1) { int configuredBand = config.getBand(); if ((configuredBand & SoftApConfiguration.BAND_6GHZ) != 0 && isSecurityTypeRestrictedFor6gBand(config.getSecurityType())) { Log.i(TAG, "remove BAND_6G if multiple bands are configured " + "as a mask when security type is restricted"); builder.setBand(configuredBand & ~SoftApConfiguration.BAND_6GHZ); } } else if (SdkLevel.isAtLeastS()) { SparseIntArray channels = config.getChannels(); SparseIntArray newChannels = new SparseIntArray(channels.size()); if (isSecurityTypeRestrictedFor6gBand(securityType)) { for (int i = 0; i < channels.size(); i++) { int band = channels.keyAt(i); if ((band & SoftApConfiguration.BAND_6GHZ) != 0 && canHALConvertRestrictedSecurityTypeFor6GHz(resources, securityType) && isBridgedMode) { Log.i(TAG, "Do not remove BAND_6G in bridged mode for" + " security type: " + securityType + " as HAL can convert the security type"); } else { Log.i(TAG, "remove BAND_6G if multiple bands are configured " + "as a mask when security type is restricted"); band &= ~SoftApConfiguration.BAND_6GHZ; } newChannels.put(band, channels.valueAt(i)); } builder.setChannels(newChannels); } } } catch (Exception e) { Log.e(TAG, "Failed to update config by removing 6G band for unsupported security type:" + e); return null; } return builder.build(); } /** * As per IEEE specification, 11BE mode should be disabled for the following * security types. * - OPEN * - WPA2-Personal * Also, disable 11BE in OWE-Transition as SoftAp run in bridged mode with one instance in open * mode. */ @VisibleForTesting static boolean is11beDisabledForSecurityType( @SoftApConfiguration.SecurityType int type) { switch(type) { case SoftApConfiguration.SECURITY_TYPE_OPEN: case SoftApConfiguration.SECURITY_TYPE_WPA2_PSK: case SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION: return true; } return false; } /** * Check if IEEE80211BE is allowed for the given softAp configuration. * * @param capabilities capabilities of the device to check support for IEEE80211BE support. * @param context The caller context used to get the OEM configuration for support for * IEEE80211BE & single link MLO in bridged mode from the resource file. * @param config The current {@link SoftApConfiguration}. * @param isBridgedMode true if bridged mode is enabled, false otherwise. * * @return true if IEEE80211BE is allowed for the given configuration, false otherwise. */ public static boolean is11beAllowedForThisConfiguration(DeviceWiphyCapabilities capabilities, @NonNull Context context, SoftApConfiguration config, boolean isBridgedMode) { if (!ApConfigUtil.isIeee80211beSupported(context)) { return false; } if (capabilities == null || !capabilities.isWifiStandardSupported( ScanResult.WIFI_STANDARD_11BE)) { return false; } if (isBridgedMode && !context.getResources().getBoolean( R.bool.config_wifiSoftApSingleLinkMloInBridgedModeSupported)) { return false; } if (is11beDisabledForSecurityType(config.getSecurityType())) { return false; } return true; } /** * Update AP band and channel based on the provided country code and band. * This will also set * @param wifiNative reference to WifiNative * @param coexManager reference to CoexManager * @param resources the resources to use to get configured allowed channels. * @param countryCode country code * @param config configuration to update * @param capability soft ap capability * @return the corresponding {@link SoftApManager.StartResult} result code. */ public static @SoftApManager.StartResult int updateApChannelConfig(WifiNative wifiNative, @NonNull CoexManager coexManager, Resources resources, String countryCode, SoftApConfiguration.Builder configBuilder, SoftApConfiguration config, SoftApCapability capability) { /* Use default band and channel for device without HAL. */ if (!wifiNative.isHalStarted()) { configBuilder.setChannel(DEFAULT_AP_CHANNEL, DEFAULT_AP_BAND); return SoftApManager.START_RESULT_SUCCESS; } /* Country code is mandatory for 5GHz band. */ if (config.getBand() == SoftApConfiguration.BAND_5GHZ && countryCode == null) { Log.e(TAG, "5GHz band is not allowed without country code"); return SoftApManager.START_RESULT_FAILURE_GENERAL; } if (!capability.areFeaturesSupported(SOFTAP_FEATURE_ACS_OFFLOAD)) { /* Select a channel if it is not specified and ACS is not enabled */ if (config.getChannel() == 0) { int freq = chooseApChannel(config.getBand(), coexManager, resources, capability); if (freq == -1) { /* We're not able to get channel from wificond. */ Log.e(TAG, "Failed to get available channel."); return SoftApManager.START_RESULT_FAILURE_NO_CHANNEL; } configBuilder.setChannel( ScanResult.convertFrequencyMhzToChannelIfSupported(freq), convertFrequencyToBand(freq)); } if (SdkLevel.isAtLeastT()) { /* remove list of allowed channels since they only apply to ACS */ if (sVerboseLoggingEnabled) { Log.i(TAG, "Ignoring Allowed ACS channels since ACS is not supported."); } configBuilder.setAllowedAcsChannels(SoftApConfiguration.BAND_2GHZ, new int[] {}); configBuilder.setAllowedAcsChannels(SoftApConfiguration.BAND_5GHZ, new int[] {}); configBuilder.setAllowedAcsChannels(SoftApConfiguration.BAND_6GHZ, new int[] {}); } } return SoftApManager.START_RESULT_SUCCESS; } /** * Helper function for converting WifiConfiguration to SoftApConfiguration. * * Only Support None and WPA2 configuration conversion. * Note that WifiConfiguration only Supports 2GHz, 5GHz, 2GHz+5GHz bands, * so conversion is limited to these bands. * * @param wifiConfig the WifiConfiguration which need to convert. * @return the SoftApConfiguration if wifiConfig is valid, null otherwise. */ @Nullable public static SoftApConfiguration fromWifiConfiguration( @NonNull WifiConfiguration wifiConfig) { SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); try { // WifiConfiguration#SSID uses a formatted string with double quotes for UTF-8 and no // quotes for hexadecimal. But to support legacy behavior, we need to continue // setting the entire string with quotes as the UTF-8 SSID. configBuilder.setSsid(wifiConfig.SSID); if (wifiConfig.getAuthType() == WifiConfiguration.KeyMgmt.WPA2_PSK) { configBuilder.setPassphrase(wifiConfig.preSharedKey, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); } configBuilder.setHiddenSsid(wifiConfig.hiddenSSID); int band; switch (wifiConfig.apBand) { case WifiConfiguration.AP_BAND_2GHZ: band = SoftApConfiguration.BAND_2GHZ; break; case WifiConfiguration.AP_BAND_5GHZ: band = SoftApConfiguration.BAND_5GHZ; break; case WifiConfiguration.AP_BAND_60GHZ: band = SoftApConfiguration.BAND_60GHZ; break; default: // WifiConfiguration.AP_BAND_ANY means only 2GHz and 5GHz bands band = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ; break; } if (wifiConfig.apChannel == 0) { configBuilder.setBand(band); } else { configBuilder.setChannel(wifiConfig.apChannel, band); } } catch (IllegalArgumentException iae) { Log.e(TAG, "Invalid WifiConfiguration" + iae); return null; } catch (IllegalStateException ise) { Log.e(TAG, "Invalid WifiConfiguration" + ise); return null; } return configBuilder.build(); } /** * Helper function to creating SoftApCapability instance with initial field from resource file. * * @param context the caller context used to get value from resource file. * @return SoftApCapability which updated the feature support or not from resource. */ @NonNull public static SoftApCapability updateCapabilityFromResource(@NonNull Context context) { long features = 0; if (isAcsSupported(context)) { Log.d(TAG, "Update Softap capability, add acs feature support"); features |= SOFTAP_FEATURE_ACS_OFFLOAD; } if (isClientForceDisconnectSupported(context)) { Log.d(TAG, "Update Softap capability, add client control feature support"); features |= SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT; } if (isWpa3SaeSupported(context)) { Log.d(TAG, "Update Softap capability, add SAE feature support"); features |= SOFTAP_FEATURE_WPA3_SAE; } if (isMacCustomizationSupported(context)) { Log.d(TAG, "Update Softap capability, add MAC customization support"); features |= SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION; } if (isSoftApBandSupported(context, SoftApConfiguration.BAND_2GHZ)) { Log.d(TAG, "Update Softap capability, add 2.4G support"); features |= SOFTAP_FEATURE_BAND_24G_SUPPORTED; } if (isSoftApBandSupported(context, SoftApConfiguration.BAND_5GHZ)) { Log.d(TAG, "Update Softap capability, add 5G support"); features |= SOFTAP_FEATURE_BAND_5G_SUPPORTED; } if (isSoftApBandSupported(context, SoftApConfiguration.BAND_6GHZ)) { Log.d(TAG, "Update Softap capability, add 6G support"); features |= SOFTAP_FEATURE_BAND_6G_SUPPORTED; } if (isSoftApBandSupported(context, SoftApConfiguration.BAND_60GHZ)) { Log.d(TAG, "Update Softap capability, add 60G support"); features |= SOFTAP_FEATURE_BAND_60G_SUPPORTED; } if (isIeee80211axSupported(context)) { Log.d(TAG, "Update Softap capability, add ax support"); features |= SOFTAP_FEATURE_IEEE80211_AX; } if (isIeee80211beSupported(context)) { Log.d(TAG, "Update Softap capability, add be support"); features |= SOFTAP_FEATURE_IEEE80211_BE; } if (isOweTransitionSupported(context)) { Log.d(TAG, "Update Softap capability, add OWE Transition feature support"); features |= SOFTAP_FEATURE_WPA3_OWE_TRANSITION; } if (isOweSupported(context)) { Log.d(TAG, "Update Softap capability, add OWE feature support"); features |= SOFTAP_FEATURE_WPA3_OWE; } SoftApCapability capability = new SoftApCapability(features); int hardwareSupportedMaxClient = context.getResources().getInteger( R.integer.config_wifiHardwareSoftapMaxClientCount); if (hardwareSupportedMaxClient > 0) { Log.d(TAG, "Update Softap capability, max client = " + hardwareSupportedMaxClient); capability.setMaxSupportedClients(hardwareSupportedMaxClient); } return capability; } /** * Helper function to update SoftApCapability instance based on config store. * * @param capability the original softApCapability * @param configStore where we stored the Capability after first time fetch from driver. * @return SoftApCapability which updated from the config store. */ @NonNull public static SoftApCapability updateCapabilityFromConfigStore( SoftApCapability capability, WifiSettingsConfigStore configStore) { if (capability == null) { return null; } if (capability.areFeaturesSupported(SOFTAP_FEATURE_IEEE80211_BE)) { capability.setSupportedFeatures(isIeee80211beEnabledInConfig(configStore), SOFTAP_FEATURE_IEEE80211_BE); } return capability; } /** * Helper function to get device support 802.11 AX on Soft AP or not * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isIeee80211axSupported(@NonNull Context context) { return context.getResources().getBoolean( R.bool.config_wifiSoftapIeee80211axSupported); } /** * Helper function to get device support 802.11 BE on Soft AP or not * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isIeee80211beSupported(@NonNull Context context) { return context.getResources().getBoolean( R.bool.config_wifiSoftapIeee80211beSupported); } /** * Helper function to check Config supports 802.11 BE on Soft AP or not * * @param configStore to check the support from WifiSettingsConfigStore * @return true if supported, false otherwise. */ public static boolean isIeee80211beEnabledInConfig( WifiSettingsConfigStore configStore) { return configStore.get( WifiSettingsConfigStore.WIFI_WIPHY_11BE_SUPPORTED); } /** * Helper function to get device support AP MAC randomization or not. * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isApMacRandomizationSupported(@NonNull Context context) { return context.getResources().getBoolean( R.bool.config_wifi_ap_mac_randomization_supported); } /** * Helper function to get HAL support bridged AP or not. * * @param context the caller context used to get value from resource file. * @param wifiNative to get the Iface combination from device. * @return true if supported, false otherwise. */ public static boolean isBridgedModeSupported( @NonNull Context context, @NonNull WifiNative wifiNative) { return SdkLevel.isAtLeastS() && context.getResources().getBoolean( R.bool.config_wifiBridgedSoftApSupported) && wifiNative.canDeviceSupportCreateTypeCombo(new SparseArray() {{ put(HDM_CREATE_IFACE_AP_BRIDGE, 1); }}); } /** * Helper function to get whether or not device claim support bridged AP. * (i.e. In resource file) * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isBridgedModeSupportedInConfig(@NonNull Context context) { return SdkLevel.isAtLeastS() && context.getResources().getBoolean( R.bool.config_wifiBridgedSoftApSupported); } /** * Helper function to get HAL support STA + bridged AP or not. * * @param context the caller context used to get value from resource file. * @param wifiNative to get the Iface combination from device. * @return true if supported, false otherwise. */ public static boolean isStaWithBridgedModeSupported( @NonNull Context context, @NonNull WifiNative wifiNative) { return SdkLevel.isAtLeastS() && context.getResources().getBoolean( R.bool.config_wifiStaWithBridgedSoftApConcurrencySupported) && wifiNative.canDeviceSupportCreateTypeCombo(new SparseArray() {{ put(HDM_CREATE_IFACE_AP_BRIDGE, 1); put(HDM_CREATE_IFACE_STA, 1); }}); } /** * Helper function to get HAL support client force disconnect or not. * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isClientForceDisconnectSupported(@NonNull Context context) { return context.getResources().getBoolean( R.bool.config_wifiSofapClientForceDisconnectSupported); } /** * Helper function to get SAE support or not. * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isWpa3SaeSupported(@NonNull Context context) { return context.getResources().getBoolean( R.bool.config_wifi_softap_sae_supported); } /** * Helper function to get ACS support or not. * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isAcsSupported(@NonNull Context context) { return context.getResources().getBoolean( R.bool.config_wifi_softap_acs_supported); } /** * Helper function to get MAC Address customization or not. * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isMacCustomizationSupported(@NonNull Context context) { return context.getResources().getBoolean( R.bool.config_wifiSoftapMacAddressCustomizationSupported); } /** * Helper function to get whether or not Soft AP support on particular band. * * @param context the caller context used to get value from resource file. * @param band the band soft AP to operate on. * @return true if supported, false otherwise. */ public static boolean isSoftApBandSupported(@NonNull Context context, @BandType int band) { switch (band) { case SoftApConfiguration.BAND_2GHZ: return context.getResources().getBoolean(R.bool.config_wifi24ghzSupport) && context.getResources().getBoolean( R.bool.config_wifiSoftap24ghzSupported); case SoftApConfiguration.BAND_5GHZ: return context.getResources().getBoolean(R.bool.config_wifi5ghzSupport) && context.getResources().getBoolean( R.bool.config_wifiSoftap5ghzSupported); case SoftApConfiguration.BAND_6GHZ: return context.getResources().getBoolean(R.bool.config_wifi6ghzSupport) && context.getResources().getBoolean( R.bool.config_wifiSoftap6ghzSupported); case SoftApConfiguration.BAND_60GHZ: return context.getResources().getBoolean(R.bool.config_wifi60ghzSupport) && context.getResources().getBoolean( R.bool.config_wifiSoftap60ghzSupported); default: return false; } } /** * Helper function to get whether or not dynamic country code update is supported when Soft AP * enabled. * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isSoftApDynamicCountryCodeSupported(@NonNull Context context) { return context.getResources().getBoolean( R.bool.config_wifiSoftApDynamicCountryCodeUpdateSupported); } /** * Helper function to get whether or not restart Soft AP required when country code changed. * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isSoftApRestartRequiredWhenCountryCodeChanged(@NonNull Context context) { return context.getResources().getBoolean( R.bool.config_wifiForcedSoftApRestartWhenCountryCodeChanged); } /** * Helper function to get OWE-Transition is support or not. * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isOweTransitionSupported(@NonNull Context context) { return context.getResources().getBoolean( R.bool.config_wifiSoftapOweTransitionSupported); } /** * Helper function to get OWE is support or not. * * @param context the caller context used to get value from resource file. * @return true if supported, false otherwise. */ public static boolean isOweSupported(@NonNull Context context) { return context.getResources().getBoolean( R.bool.config_wifiSoftapOweSupported); } /** * Helper function for comparing two SoftApConfiguration. * * @param currentConfig the original configuration. * @param newConfig the new configuration which plan to apply. * @return true if the difference between the two configurations requires a restart to apply, * false otherwise. */ public static boolean checkConfigurationChangeNeedToRestart( SoftApConfiguration currentConfig, SoftApConfiguration newConfig) { return !Objects.equals(currentConfig.getWifiSsid(), newConfig.getWifiSsid()) || !Objects.equals(currentConfig.getBssid(), newConfig.getBssid()) || currentConfig.getSecurityType() != newConfig.getSecurityType() || !Objects.equals(currentConfig.getPassphrase(), newConfig.getPassphrase()) || currentConfig.isHiddenSsid() != newConfig.isHiddenSsid() || currentConfig.getBand() != newConfig.getBand() || currentConfig.getChannel() != newConfig.getChannel() || (SdkLevel.isAtLeastS() && !currentConfig.getChannels().toString() .equals(newConfig.getChannels().toString())); } /** * Helper function for checking all of the configuration are supported or not. * * @param config target configuration want to check. * @param capability the capability which indicate feature support or not. * @return true if supported, false otherwise. */ public static boolean checkSupportAllConfiguration(SoftApConfiguration config, SoftApCapability capability) { if (!capability.areFeaturesSupported(SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT) && (config.getMaxNumberOfClients() != 0 || config.isClientControlByUserEnabled() || config.getBlockedClientList().size() != 0)) { Log.d(TAG, "Error, Client control requires HAL support"); return false; } if (!capability.areFeaturesSupported(SOFTAP_FEATURE_WPA3_SAE)) { if (config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION || config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE) { Log.d(TAG, "Error, SAE requires HAL support"); return false; } } if (!capability.areFeaturesSupported(SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)) { if (config.getBssid() != null) { Log.d(TAG, "Error, MAC address customization requires HAL support"); return false; } if (SdkLevel.isAtLeastS() && (config.getMacRandomizationSetting() == SoftApConfiguration.RANDOMIZATION_PERSISTENT || config.getMacRandomizationSetting() == SoftApConfiguration.RANDOMIZATION_NON_PERSISTENT)) { Log.d(TAG, "Error, MAC randomization requires HAL support"); return false; } } int requestedBands = 0; for (int band : config.getBands()) { requestedBands |= band; } if (!capability.areFeaturesSupported(SOFTAP_FEATURE_BAND_24G_SUPPORTED)) { if ((requestedBands & SoftApConfiguration.BAND_2GHZ) != 0) { Log.d(TAG, "Error, 2.4Ghz band requires HAL support"); return false; } } if (!capability.areFeaturesSupported(SOFTAP_FEATURE_BAND_5G_SUPPORTED)) { if ((requestedBands & SoftApConfiguration.BAND_5GHZ) != 0) { Log.d(TAG, "Error, 5Ghz band requires HAL support"); return false; } } if (!capability.areFeaturesSupported(SOFTAP_FEATURE_BAND_6G_SUPPORTED)) { if ((requestedBands & SoftApConfiguration.BAND_6GHZ) != 0) { Log.d(TAG, "Error, 6Ghz band requires HAL support"); return false; } } if (!capability.areFeaturesSupported(SOFTAP_FEATURE_BAND_60G_SUPPORTED)) { if ((requestedBands & SoftApConfiguration.BAND_60GHZ) != 0) { Log.d(TAG, "Error, 60Ghz band requires HAL support"); return false; } } if (!capability.areFeaturesSupported(SOFTAP_FEATURE_WPA3_OWE_TRANSITION)) { if (config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION) { Log.d(TAG, "Error, OWE transition requires HAL support"); return false; } } if (!capability.areFeaturesSupported(SOFTAP_FEATURE_WPA3_OWE)) { if (config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_WPA3_OWE) { Log.d(TAG, "Error, OWE requires HAL support"); return false; } } // Checks for Dual AP if (SdkLevel.isAtLeastS() && config.getBands().length > 1) { int[] bands = config.getBands(); if ((bands[0] & SoftApConfiguration.BAND_60GHZ) != 0 || (bands[1] & SoftApConfiguration.BAND_60GHZ) != 0) { Log.d(TAG, "Error, dual APs doesn't support on 60GHz"); return false; } if (!capability.areFeaturesSupported(SOFTAP_FEATURE_ACS_OFFLOAD) && (config.getChannels().valueAt(0) == 0 || config.getChannels().valueAt(1) == 0)) { Log.d(TAG, "Error, dual APs requires HAL ACS support when channel isn't specified"); return false; } } return true; } /** * Check if need to provide freq range for ACS. * * @param band in SoftApConfiguration.BandType * @param context the caller context used to get values from resource file * @param config the used SoftApConfiguration * * @return true when freq ranges is needed, otherwise false. */ public static boolean isSendFreqRangesNeeded(@BandType int band, Context context, SoftApConfiguration config) { // Fist we check if one of the selected bands has restrictions in the overlay file or in the // provided SoftApConfiguration. // Note, // - We store the config string here for future use, hence we need to check all bands. // - If there is no restrictions on channels, we store the full band for (int b : SoftApConfiguration.BAND_TYPES) { if ((band & b) != 0) { List configuredList = getConfiguredChannelList(context.getResources(), b); if (configuredList != null && !configuredList.isEmpty()) { // If any of the selected band has restriction in the overlay file return true. return true; } if (SdkLevel.isAtLeastT() && config.getAllowedAcsChannels(b).length != 0) { return true; } } } // Next, if only one of 5G or 6G is selected, then we need freqList to separate them // Since there is no other way. if (((band & SoftApConfiguration.BAND_5GHZ) != 0) && ((band & SoftApConfiguration.BAND_6GHZ) == 0)) { return true; } if (((band & SoftApConfiguration.BAND_5GHZ) == 0) && ((band & SoftApConfiguration.BAND_6GHZ) != 0)) { return true; } // In all other cases, we don't need to set the freqList return false; } /** * Collect a List of allowed channels for ACS operations on a selected band * * @param band on which channel list are required * @param oemConfigString Configuration string from OEM resource file. * An empty string means all channels on this band are allowed * @param callerConfig allowed chnannels as required by the caller * * @return List of channel numbers that meet both criteria */ public static List collectAllowedAcsChannels(@BandType int band, String oemConfigString, int[] callerConfig) { // Convert the OEM config string into a set of channel numbers Set allowedChannelSet = getOemAllowedChannels(band, oemConfigString); // Update the allowed channels with user configuration allowedChannelSet.retainAll(getCallerAllowedChannels(band, callerConfig)); return new ArrayList(allowedChannelSet); } private static Set getSetForAllChannelsInBand(@BandType int band) { switch(band) { case SoftApConfiguration.BAND_2GHZ: return IntStream.rangeClosed( ScanResult.BAND_24_GHZ_FIRST_CH_NUM, ScanResult.BAND_24_GHZ_LAST_CH_NUM) .boxed() .collect(Collectors.toSet()); case SoftApConfiguration.BAND_5GHZ: return IntStream.rangeClosed( ScanResult.BAND_5_GHZ_FIRST_CH_NUM, ScanResult.BAND_5_GHZ_LAST_CH_NUM) .boxed() .collect(Collectors.toSet()); case SoftApConfiguration.BAND_6GHZ: return IntStream.rangeClosed( ScanResult.BAND_6_GHZ_FIRST_CH_NUM, ScanResult.BAND_6_GHZ_LAST_CH_NUM) .boxed() .collect(Collectors.toSet()); default: Log.e(TAG, "Invalid band: " + bandToString(band)); return Collections.emptySet(); } } private static Set getOemAllowedChannels(@BandType int band, String oemConfigString) { if (TextUtils.isEmpty(oemConfigString)) { // Empty string means all channels are allowed in this band return getSetForAllChannelsInBand(band); } // String is not empty, parsing it Set allowedChannelsOem = new HashSet<>(); for (String channelRange : oemConfigString.split(",")) { try { if (channelRange.contains("-")) { String[] channels = channelRange.split("-"); if (channels.length != 2) { Log.e(TAG, "Unrecognized channel range, length is " + channels.length); continue; } int start = Integer.parseInt(channels[0].trim()); int end = Integer.parseInt(channels[1].trim()); if (start > end) { Log.e(TAG, "Invalid channel range, from " + start + " to " + end); continue; } allowedChannelsOem.addAll(IntStream.rangeClosed(start, end) .boxed().collect(Collectors.toSet())); } else if (!TextUtils.isEmpty(channelRange)) { int channel = Integer.parseInt(channelRange.trim()); allowedChannelsOem.add(channel); } } catch (NumberFormatException e) { // Ignore malformed value Log.e(TAG, "Malformed channel value detected: " + e); continue; } } return allowedChannelsOem; } private static Set getCallerAllowedChannels(@BandType int band, int[] callerConfig) { if (callerConfig.length == 0) { // Empty set means all channels are allowed in this band return getSetForAllChannelsInBand(band); } // Otherwise return the caller set as is return IntStream.of(callerConfig).boxed() .collect(Collectors.toCollection(HashSet::new)); } /** * Deep copy for object Map */ public static Map deepCopyForSoftApInfoMap( Map originalMap) { if (originalMap == null) { return null; } Map deepCopyMap = new HashMap(); for (Map.Entry entry: originalMap.entrySet()) { deepCopyMap.put(entry.getKey(), new SoftApInfo(entry.getValue())); } return deepCopyMap; } /** * Deep copy for object Map> */ public static Map> deepCopyForWifiClientListMap( Map> originalMap) { if (originalMap == null) { return null; } Map> deepCopyMap = new HashMap>(); for (Map.Entry> entry: originalMap.entrySet()) { List clients = new ArrayList<>(); for (WifiClient client : entry.getValue()) { clients.add(new WifiClient(client.getMacAddress(), client.getApInstanceIdentifier())); } deepCopyMap.put(entry.getKey(), clients); } return deepCopyMap; } /** * Observer the available channel from native layer (vendor HAL if getUsableChannels is * supported, or wificond if not supported) and update the SoftApCapability * * @param softApCapability the current softap capability * @param context the caller context used to get value from resource file * @param wifiNative reference used to collect regulatory restrictions. * @param channelMap the channel for each band * @return updated soft AP capability */ public static SoftApCapability updateSoftApCapabilityWithAvailableChannelList( @NonNull SoftApCapability softApCapability, @NonNull Context context, @NonNull WifiNative wifiNative, @NonNull SparseArray channelMap) { SoftApCapability newSoftApCapability = new SoftApCapability(softApCapability); if (channelMap != null) { for (int band : SoftApConfiguration.BAND_TYPES) { if (isSoftApBandSupported(context, band)) { int[] supportedChannelList = channelMap.get(band); if (supportedChannelList != null) { newSoftApCapability.setSupportedChannelList(band, supportedChannelList); } } } return newSoftApCapability; } List supportedChannelList = null; for (int band : SoftApConfiguration.BAND_TYPES) { if (isSoftApBandSupported(context, band)) { supportedChannelList = getAvailableChannelFreqsForBand( band, wifiNative, context.getResources(), false); if (supportedChannelList != null) { newSoftApCapability.setSupportedChannelList( band, supportedChannelList.stream().mapToInt(Integer::intValue).toArray()); } } } return newSoftApCapability; } /** * Helper function to check if security type can ignore password. * * @param security type for SoftApConfiguration. * @return true for Open/Owe-Transition SoftAp AKM. */ public static boolean isNonPasswordAP(int security) { return (security == SoftApConfiguration.SECURITY_TYPE_OPEN || security == SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION || security == SoftApConfiguration.SECURITY_TYPE_WPA3_OWE); } }