1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi.util; 18 19 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD; 20 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_BAND_24G_SUPPORTED; 21 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_BAND_5G_SUPPORTED; 22 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_BAND_60G_SUPPORTED; 23 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_BAND_6G_SUPPORTED; 24 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT; 25 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_IEEE80211_AX; 26 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_IEEE80211_BE; 27 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION; 28 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_WPA3_OWE; 29 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_WPA3_OWE_TRANSITION; 30 import static android.net.wifi.SoftApCapability.SOFTAP_FEATURE_WPA3_SAE; 31 32 import android.annotation.NonNull; 33 import android.annotation.Nullable; 34 import android.content.Context; 35 import android.content.res.Resources; 36 import android.net.wifi.CoexUnsafeChannel; 37 import android.net.wifi.ScanResult; 38 import android.net.wifi.SoftApCapability; 39 import android.net.wifi.SoftApConfiguration; 40 import android.net.wifi.SoftApConfiguration.BandType; 41 import android.net.wifi.SoftApInfo; 42 import android.net.wifi.WifiAvailableChannel; 43 import android.net.wifi.WifiClient; 44 import android.net.wifi.WifiConfiguration; 45 import android.net.wifi.WifiManager; 46 import android.net.wifi.WifiScanner; 47 import android.text.TextUtils; 48 import android.util.Log; 49 import android.util.SparseArray; 50 import android.util.SparseIntArray; 51 52 import com.android.modules.utils.build.SdkLevel; 53 import com.android.server.wifi.WifiNative; 54 import com.android.server.wifi.coex.CoexManager; 55 import com.android.wifi.resources.R; 56 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.Collections; 60 import java.util.HashMap; 61 import java.util.HashSet; 62 import java.util.List; 63 import java.util.Map; 64 import java.util.Objects; 65 import java.util.Random; 66 import java.util.Set; 67 import java.util.StringJoiner; 68 import java.util.stream.Collectors; 69 import java.util.stream.IntStream; 70 71 /** 72 * Provide utility functions for updating soft AP related configuration. 73 */ 74 public class ApConfigUtil { 75 private static final String TAG = "ApConfigUtil"; 76 77 public static final int INVALID_VALUE_FOR_BAND_OR_CHANNEL = -1; 78 public static final int DEFAULT_AP_BAND = SoftApConfiguration.BAND_2GHZ; 79 public static final int DEFAULT_AP_CHANNEL = 6; 80 public static final int HIGHEST_2G_AP_CHANNEL = 14; 81 82 /* Return code for updateConfiguration. */ 83 public static final int SUCCESS = 0; 84 public static final int ERROR_NO_CHANNEL = 1; 85 public static final int ERROR_GENERIC = 2; 86 public static final int ERROR_UNSUPPORTED_CONFIGURATION = 3; 87 88 /* Random number generator used for AP channel selection. */ 89 private static final Random sRandom = new Random(); 90 private static boolean sVerboseLoggingEnabled = false; 91 92 /** 93 * Enable or disable verbose logging 94 * @param verboseEnabled true if verbose logging is enabled 95 */ enableVerboseLogging(boolean verboseEnabled)96 public static void enableVerboseLogging(boolean verboseEnabled) { 97 sVerboseLoggingEnabled = verboseEnabled; 98 } 99 100 /** 101 * Valid Global Operating classes in each wifi band 102 * Reference: Table E-4 in IEEE Std 802.11-2016. 103 */ 104 private static final SparseArray<int[]> sBandToOperatingClass = new SparseArray<>(); 105 static { sBandToOperatingClass.append(SoftApConfiguration.BAND_2GHZ, new int[]{81, 82, 83, 84})106 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})107 sBandToOperatingClass.append(SoftApConfiguration.BAND_5GHZ, new int[]{115, 116, 117, 118, 108 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})109 sBandToOperatingClass.append(SoftApConfiguration.BAND_6GHZ, new int[]{131, 132, 133, 134, 110 135, 136}); 111 } 112 113 /** 114 * Converts a SoftApConfiguration.BAND_* constant to a meaningful String 115 */ bandToString(int band)116 public static String bandToString(int band) { 117 StringJoiner sj = new StringJoiner(" & "); 118 sj.setEmptyValue("unspecified"); 119 if ((band & SoftApConfiguration.BAND_2GHZ) != 0) { 120 sj.add("2Ghz"); 121 } 122 band &= ~SoftApConfiguration.BAND_2GHZ; 123 124 if ((band & SoftApConfiguration.BAND_5GHZ) != 0) { 125 sj.add("5Ghz"); 126 } 127 band &= ~SoftApConfiguration.BAND_5GHZ; 128 129 if ((band & SoftApConfiguration.BAND_6GHZ) != 0) { 130 sj.add("6Ghz"); 131 } 132 band &= ~SoftApConfiguration.BAND_6GHZ; 133 134 if ((band & SoftApConfiguration.BAND_60GHZ) != 0) { 135 sj.add("60Ghz"); 136 } 137 band &= ~SoftApConfiguration.BAND_60GHZ; 138 if (band != 0) { 139 return "Invalid band"; 140 } 141 return sj.toString(); 142 } 143 144 /** 145 * Helper function to get the band corresponding to the operating class. 146 * 147 * @param operatingClass Global operating class. 148 * @return band, -1 if no match. 149 * 150 */ getBandFromOperatingClass(int operatingClass)151 public static int getBandFromOperatingClass(int operatingClass) { 152 for (int i = 0; i < sBandToOperatingClass.size(); i++) { 153 int band = sBandToOperatingClass.keyAt(i); 154 int[] operatingClasses = sBandToOperatingClass.get(band); 155 156 for (int j = 0; j < operatingClasses.length; j++) { 157 if (operatingClasses[j] == operatingClass) { 158 return band; 159 } 160 } 161 } 162 return -1; 163 } 164 165 /** 166 * Convert band from SoftApConfiguration.BandType to WifiScanner.WifiBand 167 * @param band in SoftApConfiguration.BandType 168 * @return band in WifiScanner.WifiBand 169 */ apConfig2wifiScannerBand(@andType int band)170 public static @WifiScanner.WifiBand int apConfig2wifiScannerBand(@BandType int band) { 171 switch(band) { 172 case SoftApConfiguration.BAND_2GHZ: 173 return WifiScanner.WIFI_BAND_24_GHZ; 174 case SoftApConfiguration.BAND_5GHZ: 175 return WifiScanner.WIFI_BAND_5_GHZ; 176 case SoftApConfiguration.BAND_6GHZ: 177 return WifiScanner.WIFI_BAND_6_GHZ; 178 case SoftApConfiguration.BAND_60GHZ: 179 return WifiScanner.WIFI_BAND_60_GHZ; 180 default: 181 return WifiScanner.WIFI_BAND_UNSPECIFIED; 182 } 183 } 184 185 /** 186 * Convert channel/band to frequency. 187 * Note: the utility does not perform any regulatory domain compliance. 188 * @param channel number to convert 189 * @param band of channel to convert 190 * @return center frequency in Mhz of the channel, -1 if no match 191 */ convertChannelToFrequency(int channel, @BandType int band)192 public static int convertChannelToFrequency(int channel, @BandType int band) { 193 return ScanResult.convertChannelToFrequencyMhzIfSupported(channel, 194 apConfig2wifiScannerBand(band)); 195 } 196 197 /** 198 * Convert frequency to band. 199 * Note: the utility does not perform any regulatory domain compliance. 200 * @param frequency frequency to convert 201 * @return band, -1 if no match 202 */ convertFrequencyToBand(int frequency)203 public static int convertFrequencyToBand(int frequency) { 204 if (ScanResult.is24GHz(frequency)) { 205 return SoftApConfiguration.BAND_2GHZ; 206 } else if (ScanResult.is5GHz(frequency)) { 207 return SoftApConfiguration.BAND_5GHZ; 208 } else if (ScanResult.is6GHz(frequency)) { 209 return SoftApConfiguration.BAND_6GHZ; 210 } else if (ScanResult.is60GHz(frequency)) { 211 return SoftApConfiguration.BAND_60GHZ; 212 } 213 214 return -1; 215 } 216 217 /** 218 * Convert band from WifiConfiguration into SoftApConfiguration 219 * 220 * @param wifiConfigBand band encoded as WifiConfiguration.AP_BAND_xxxx 221 * @return band as encoded as SoftApConfiguration.BAND_xxx 222 */ convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand)223 public static int convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand) { 224 switch (wifiConfigBand) { 225 case WifiConfiguration.AP_BAND_2GHZ: 226 return SoftApConfiguration.BAND_2GHZ; 227 case WifiConfiguration.AP_BAND_5GHZ: 228 return SoftApConfiguration.BAND_5GHZ; 229 case WifiConfiguration.AP_BAND_ANY: 230 return SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ; 231 default: 232 return SoftApConfiguration.BAND_2GHZ; 233 } 234 } 235 236 /** 237 * Add 2.4Ghz to target band when 2.4Ghz SoftAp supported. 238 * 239 * @param targetBand The band is needed to add 2.4G. 240 * @return The band includes 2.4Ghz when 2.4G SoftAp supported. 241 */ append24GToBandIf24GSupported(@andType int targetBand, Context context)242 public static @BandType int append24GToBandIf24GSupported(@BandType int targetBand, 243 Context context) { 244 if (isBandSupported(SoftApConfiguration.BAND_2GHZ, context)) { 245 return targetBand | SoftApConfiguration.BAND_2GHZ; 246 } 247 return targetBand; 248 } 249 250 /** 251 * Add 5Ghz to target band when 5Ghz SoftAp supported. 252 * 253 * @param targetBand The band is needed to add 5GHz band. 254 * @return The band includes 5Ghz when 5G SoftAp supported. 255 */ append5GToBandIf5GSupported(@andType int targetBand, Context context)256 public static @BandType int append5GToBandIf5GSupported(@BandType int targetBand, 257 Context context) { 258 if (isBandSupported(SoftApConfiguration.BAND_5GHZ, context)) { 259 return targetBand | SoftApConfiguration.BAND_5GHZ; 260 } 261 return targetBand; 262 } 263 264 /** 265 * Checks if band is a valid combination of {link SoftApConfiguration#BandType} values 266 */ isBandValid(@andType int band)267 public static boolean isBandValid(@BandType int band) { 268 int bandAny = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ 269 | SoftApConfiguration.BAND_6GHZ | SoftApConfiguration.BAND_60GHZ; 270 return ((band != 0) && ((band & ~bandAny) == 0)); 271 } 272 273 /** 274 * Check if the band contains a certain sub-band 275 * 276 * @param band The combination of bands to validate 277 * @param testBand the test band to validate on 278 * @return true if band contains testBand, false otherwise 279 */ containsBand(@andType int band, @BandType int testBand)280 public static boolean containsBand(@BandType int band, @BandType int testBand) { 281 return ((band & testBand) != 0); 282 } 283 284 /** 285 * Checks if band contains multiple sub-bands 286 * @param band a combination of sub-bands 287 * @return true if band has multiple sub-bands, false otherwise 288 */ isMultiband(@andType int band)289 public static boolean isMultiband(@BandType int band) { 290 return ((band & (band - 1)) != 0); 291 } 292 293 294 /** 295 * Checks whether or not band configuration is supported. 296 * @param apBand a combination of the bands 297 * @param context the caller context used to get value from resource file. 298 * @return true if band is supported, false otherwise 299 */ isBandSupported(@andType int apBand, Context context)300 public static boolean isBandSupported(@BandType int apBand, Context context) { 301 if (!isBandValid(apBand)) { 302 Log.e(TAG, "Invalid SoftAp band " + apBand); 303 return false; 304 } 305 306 for (int b : SoftApConfiguration.BAND_TYPES) { 307 if (containsBand(apBand, b) && !isSoftApBandSupported(context, b)) { 308 Log.e(TAG, "Can not start softAp with band " + bandToString(b) 309 + " not supported."); 310 return false; 311 } 312 } 313 314 return true; 315 } 316 317 /** 318 * Convert string to channel list 319 * Format of the list is a comma separated channel numbers, or range of channel numbers 320 * Example, "34-48, 149". 321 * @param channelString for a comma separated channel numbers, or range of channel numbers 322 * such as "34-48, 149" 323 * @return list of channel numbers 324 */ convertStringToChannelList(String channelString)325 public static List<Integer> convertStringToChannelList(String channelString) { 326 if (channelString == null) { 327 return null; 328 } 329 330 List<Integer> channelList = new ArrayList<Integer>(); 331 332 for (String channelRange : channelString.split(",")) { 333 try { 334 if (channelRange.contains("-")) { 335 String[] channels = channelRange.split("-"); 336 if (channels.length != 2) { 337 Log.e(TAG, "Unrecognized channel range, Length is " + channels.length); 338 continue; 339 } 340 int start = Integer.parseInt(channels[0].trim()); 341 int end = Integer.parseInt(channels[1].trim()); 342 if (start > end) { 343 Log.e(TAG, "Invalid channel range, from " + start + " to " + end); 344 continue; 345 } 346 347 for (int channel = start; channel <= end; channel++) { 348 channelList.add(channel); 349 } 350 } else { 351 channelList.add(Integer.parseInt(channelRange.trim())); 352 } 353 } catch (NumberFormatException e) { 354 // Ignore malformed string 355 Log.e(TAG, "Malformed channel value detected: " + e); 356 continue; 357 } 358 } 359 return channelList; 360 } 361 362 /** 363 * Returns the unsafe channels frequency from coex module. 364 * 365 * @param coexManager reference used to get unsafe channels to avoid for coex. 366 */ 367 @NonNull getUnsafeChannelFreqsFromCoex(@onNull CoexManager coexManager)368 public static Set<Integer> getUnsafeChannelFreqsFromCoex(@NonNull CoexManager coexManager) { 369 Set<Integer> unsafeFreqs = new HashSet<>(); 370 if (SdkLevel.isAtLeastS()) { 371 for (CoexUnsafeChannel unsafeChannel : coexManager.getCoexUnsafeChannels()) { 372 unsafeFreqs.add(ScanResult.convertChannelToFrequencyMhzIfSupported( 373 unsafeChannel.getChannel(), unsafeChannel.getBand())); 374 } 375 } 376 return unsafeFreqs; 377 } 378 getConfiguredChannelList(Resources resources, @BandType int band)379 private static List<Integer> getConfiguredChannelList(Resources resources, @BandType int band) { 380 switch (band) { 381 case SoftApConfiguration.BAND_2GHZ: 382 return convertStringToChannelList(resources.getString( 383 R.string.config_wifiSoftap2gChannelList)); 384 case SoftApConfiguration.BAND_5GHZ: 385 return convertStringToChannelList(resources.getString( 386 R.string.config_wifiSoftap5gChannelList)); 387 case SoftApConfiguration.BAND_6GHZ: 388 return convertStringToChannelList(resources.getString( 389 R.string.config_wifiSoftap6gChannelList)); 390 case SoftApConfiguration.BAND_60GHZ: 391 return convertStringToChannelList(resources.getString( 392 R.string.config_wifiSoftap60gChannelList)); 393 default: 394 return null; 395 } 396 } 397 addDfsChannelsIfNeeded(List<Integer> regulatoryList, @WifiScanner.WifiBand int scannerBand, WifiNative wifiNative, Resources resources, boolean inFrequencyMHz)398 private static List<Integer> addDfsChannelsIfNeeded(List<Integer> regulatoryList, 399 @WifiScanner.WifiBand int scannerBand, WifiNative wifiNative, Resources resources, 400 boolean inFrequencyMHz) { 401 // Add DFS channels to the supported channel list if the device supports SoftAp 402 // operation in the DFS channel. 403 if (resources.getBoolean(R.bool.config_wifiSoftapAcsIncludeDfs) 404 && scannerBand == WifiScanner.WIFI_BAND_5_GHZ) { 405 int[] dfs5gBand = wifiNative.getChannelsForBand( 406 WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY); 407 for (int freq : dfs5gBand) { 408 final int freqOrChan = inFrequencyMHz 409 ? freq : ScanResult.convertFrequencyMhzToChannelIfSupported(freq); 410 if (!regulatoryList.contains(freqOrChan)) { 411 regulatoryList.add(freqOrChan); 412 } 413 } 414 } 415 return regulatoryList; 416 } 417 getWifiCondAvailableChannelsForBand( @ifiScanner.WifiBand int scannerBand, WifiNative wifiNative, Resources resources, boolean inFrequencyMHz)418 private static List<Integer> getWifiCondAvailableChannelsForBand( 419 @WifiScanner.WifiBand int scannerBand, WifiNative wifiNative, Resources resources, 420 boolean inFrequencyMHz) { 421 List<Integer> regulatoryList = new ArrayList<Integer>(); 422 // Get the allowed list of channel frequencies in MHz from wificond 423 int[] regulatoryArray = wifiNative.getChannelsForBand(scannerBand); 424 for (int freq : regulatoryArray) { 425 regulatoryList.add(inFrequencyMHz 426 ? freq : ScanResult.convertFrequencyMhzToChannelIfSupported(freq)); 427 } 428 return addDfsChannelsIfNeeded(regulatoryList, scannerBand, wifiNative, resources, 429 inFrequencyMHz); 430 } 431 getHalAvailableChannelsForBand( @ifiScanner.WifiBand int scannerBand, WifiNative wifiNative, Resources resources, boolean inFrequencyMHz)432 private static List<Integer> getHalAvailableChannelsForBand( 433 @WifiScanner.WifiBand int scannerBand, WifiNative wifiNative, Resources resources, 434 boolean inFrequencyMHz) { 435 // Try vendor HAL API to get the usable channel list. 436 List<WifiAvailableChannel> usableChannelList = wifiNative.getUsableChannels( 437 scannerBand, 438 WifiAvailableChannel.OP_MODE_SAP, 439 WifiAvailableChannel.FILTER_REGULATORY); 440 if (usableChannelList == null) { 441 // If HAL doesn't support getUsableChannels then return null 442 return null; 443 } 444 List<Integer> regulatoryList = usableChannelList.stream() 445 .map(ch -> inFrequencyMHz 446 ? ch.getFrequencyMhz() 447 : ScanResult.convertFrequencyMhzToChannelIfSupported( 448 ch.getFrequencyMhz())) 449 .collect(Collectors.toList()); 450 return addDfsChannelsIfNeeded(regulatoryList, scannerBand, wifiNative, resources, 451 inFrequencyMHz); 452 } 453 454 /** 455 * Get channels or frequencies for band that are allowed by both regulatory 456 * and OEM configuration. 457 * 458 * @param band to get channels for 459 * @param wifiNative reference used to get regulatory restrictions. 460 * @param resources used to get OEM restrictions 461 * @param inFrequencyMHz true to convert channel to frequency. 462 * @return A list of frequencies that are allowed, null on error. 463 */ getAvailableChannelFreqsForBand( @andType int band, WifiNative wifiNative, Resources resources, boolean inFrequencyMHz)464 public static List<Integer> getAvailableChannelFreqsForBand( 465 @BandType int band, WifiNative wifiNative, Resources resources, 466 boolean inFrequencyMHz) { 467 if (!isBandValid(band) || isMultiband(band)) { 468 return null; 469 } 470 471 int scannerBand = apConfig2wifiScannerBand(band); 472 List<Integer> regulatoryList = null; 473 boolean useWifiCond = false; 474 // Check if vendor HAL API for getting usable channels is available. If HAL doesn't support 475 // the API it returns null list, in that case we retrieve the list from wificond. 476 if (!wifiNative.isHalSupported()) { 477 // HAL is not supported, fallback to wificond 478 useWifiCond = true; 479 } else { 480 if (!wifiNative.isHalStarted()) { 481 // HAL is not started, return null 482 return null; 483 } 484 regulatoryList = getHalAvailableChannelsForBand(scannerBand, wifiNative, resources, 485 inFrequencyMHz); 486 if (regulatoryList == null) { 487 // HAL API not supported by HAL, fallback to wificond 488 useWifiCond = true; 489 } 490 } 491 if (useWifiCond) { 492 regulatoryList = getWifiCondAvailableChannelsForBand(scannerBand, wifiNative, resources, 493 inFrequencyMHz); 494 } 495 List<Integer> configuredList = getConfiguredChannelList(resources, band); 496 if (configuredList == null || configuredList.isEmpty() || regulatoryList == null) { 497 return regulatoryList; 498 } 499 List<Integer> filteredList = new ArrayList<Integer>(); 500 // Otherwise, filter the configured list 501 for (int channel : configuredList) { 502 if (inFrequencyMHz) { 503 int channelFreq = convertChannelToFrequency(channel, band); 504 if (regulatoryList.contains(channelFreq)) { 505 filteredList.add(channelFreq); 506 } 507 } else if (regulatoryList.contains(channel)) { 508 filteredList.add(channel); 509 } 510 } 511 if (sVerboseLoggingEnabled) { 512 Log.d(TAG, "Filtered channel list for band " + bandToString(band) + " : " 513 + filteredList.stream().map(Object::toString).collect(Collectors.joining(","))); 514 } 515 return filteredList; 516 } 517 518 /** 519 * Return a channel frequency for AP setup based on the frequency band. 520 * @param apBand one or combination of the values of SoftApConfiguration.BAND_*. 521 * @param coexManager reference used to get unsafe channels to avoid for coex. 522 * @param resources the resources to use to get configured allowed channels. 523 * @param capability soft AP capability 524 * @return a valid channel frequency on success, -1 on failure. 525 */ chooseApChannel(int apBand, @NonNull CoexManager coexManager, @NonNull Resources resources, SoftApCapability capability)526 public static int chooseApChannel(int apBand, @NonNull CoexManager coexManager, 527 @NonNull Resources resources, SoftApCapability capability) { 528 if (!isBandValid(apBand)) { 529 Log.e(TAG, "Invalid band: " + apBand); 530 return -1; 531 } 532 533 Set<Integer> unsafeFreqs = new HashSet<>(); 534 if (SdkLevel.isAtLeastS()) { 535 unsafeFreqs = getUnsafeChannelFreqsFromCoex(coexManager); 536 } 537 final int[] bandPreferences = new int[]{ 538 SoftApConfiguration.BAND_60GHZ, 539 SoftApConfiguration.BAND_6GHZ, 540 SoftApConfiguration.BAND_5GHZ, 541 SoftApConfiguration.BAND_2GHZ}; 542 int selectedUnsafeFreq = 0; 543 for (int band : bandPreferences) { 544 if ((apBand & band) == 0) { 545 continue; 546 } 547 int[] availableChannels = capability.getSupportedChannelList(band); 548 if (availableChannels == null || availableChannels.length == 0) { 549 continue; 550 } 551 final List<Integer> availableFreqs = 552 Arrays.stream(availableChannels).boxed() 553 .map(ch -> convertChannelToFrequency(ch, band)) 554 .collect(Collectors.toList()); 555 // Separate the available freqs by safe and unsafe. 556 List<Integer> availableSafeFreqs = new ArrayList<>(); 557 List<Integer> availableUnsafeFreqs = new ArrayList<>(); 558 for (int freq : availableFreqs) { 559 if (unsafeFreqs.contains(freq)) { 560 availableUnsafeFreqs.add(freq); 561 } else { 562 availableSafeFreqs.add(freq); 563 } 564 } 565 // If there are safe freqs available for this band, randomly select one. 566 if (!availableSafeFreqs.isEmpty()) { 567 return availableSafeFreqs.get(sRandom.nextInt(availableSafeFreqs.size())); 568 } else if (!availableUnsafeFreqs.isEmpty() && selectedUnsafeFreq == 0) { 569 // Save an unsafe freq from the first preferred band to fall back on later. 570 selectedUnsafeFreq = availableUnsafeFreqs.get( 571 sRandom.nextInt(availableUnsafeFreqs.size())); 572 } 573 } 574 // If all available channels are soft unsafe, select a random one of the highest band. 575 boolean isHardUnsafe = false; 576 if (SdkLevel.isAtLeastS()) { 577 isHardUnsafe = 578 (coexManager.getCoexRestrictions() & WifiManager.COEX_RESTRICTION_SOFTAP) != 0; 579 } 580 if (!isHardUnsafe && selectedUnsafeFreq != 0) { 581 return selectedUnsafeFreq; 582 } 583 584 // If all available channels are hard unsafe, select the default AP channel. 585 if (containsBand(apBand, DEFAULT_AP_BAND)) { 586 final int defaultChannelFreq = convertChannelToFrequency(DEFAULT_AP_CHANNEL, 587 DEFAULT_AP_BAND); 588 Log.e(TAG, "Allowed channel list not specified, selecting default channel"); 589 if (isHardUnsafe && unsafeFreqs.contains(defaultChannelFreq)) { 590 Log.e(TAG, "Default channel is hard restricted due to coex"); 591 } 592 return defaultChannelFreq; 593 } 594 Log.e(TAG, "No available channels"); 595 return -1; 596 } 597 598 /** 599 * Remove unavailable bands from the input band and return the resulting 600 * (remaining) available bands. Unavailable bands are those which don't have channels available. 601 * 602 * @param capability SoftApCapability which indicates supported channel list. 603 * @param targetBand The target band which plan to enable 604 * @param coexManager reference to CoexManager 605 * 606 * @return the available band which removed the unsupported band. 607 * 0 when all of the band is not supported. 608 */ removeUnavailableBands(SoftApCapability capability, @NonNull int targetBand, CoexManager coexManager)609 public static @BandType int removeUnavailableBands(SoftApCapability capability, 610 @NonNull int targetBand, CoexManager coexManager) { 611 int availableBand = targetBand; 612 for (int band : SoftApConfiguration.BAND_TYPES) { 613 Set<Integer> availableChannelFreqsList = new HashSet<>(); 614 if ((targetBand & band) != 0) { 615 for (int channel : capability.getSupportedChannelList(band)) { 616 availableChannelFreqsList.add(convertChannelToFrequency(channel, band)); 617 } 618 // Only remove hard unsafe channels 619 if (SdkLevel.isAtLeastS() 620 && (coexManager.getCoexRestrictions() & WifiManager.COEX_RESTRICTION_SOFTAP) 621 != 0) { 622 availableChannelFreqsList.removeAll(getUnsafeChannelFreqsFromCoex(coexManager)); 623 } 624 if (availableChannelFreqsList.size() == 0) { 625 availableBand &= ~band; 626 } 627 } 628 } 629 return availableBand; 630 } 631 632 /** 633 * Remove unavailable bands from the softAp configuration and return the updated configuration. 634 * Unavailable bands are those which don't have channels available. 635 * 636 * @param config The current {@link SoftApConfiguration}. 637 * @param capability SoftApCapability which indicates supported channel list. 638 * @param coexManager reference to CoexManager 639 * @param context the caller context used to get value from resource file. 640 * 641 * @return the updated SoftApConfiguration. 642 */ removeUnavailableBandsFromConfig( SoftApConfiguration config, SoftApCapability capability, CoexManager coexManager, @NonNull Context context)643 public static SoftApConfiguration removeUnavailableBandsFromConfig( 644 SoftApConfiguration config, SoftApCapability capability, CoexManager coexManager, 645 @NonNull Context context) { 646 SoftApConfiguration.Builder builder = new SoftApConfiguration.Builder(config); 647 648 try { 649 if (config.getBands().length == 1) { 650 int configuredBand = config.getBand(); 651 int availableBand = ApConfigUtil.removeUnavailableBands( 652 capability, 653 configuredBand, coexManager); 654 if (availableBand != configuredBand) { 655 availableBand = ApConfigUtil.append24GToBandIf24GSupported(availableBand, 656 context); 657 Log.i(TAG, "Reset band from " + configuredBand + " to " 658 + availableBand + " in single AP configuration"); 659 builder.setBand(availableBand); 660 } 661 } else if (SdkLevel.isAtLeastS()) { 662 SparseIntArray channels = config.getChannels(); 663 SparseIntArray newChannels = new SparseIntArray(channels.size()); 664 for (int i = 0; i < channels.size(); i++) { 665 int configuredBand = channels.keyAt(i); 666 int availableBand = ApConfigUtil.removeUnavailableBands( 667 capability, 668 configuredBand, coexManager); 669 if (availableBand != configuredBand) { 670 Log.i(TAG, "Reset band in index " + i + " from " + configuredBand 671 + " to " + availableBand + " in dual AP configuration"); 672 } 673 if (isBandValid(availableBand)) { 674 newChannels.put(availableBand, channels.valueAt(i)); 675 } 676 } 677 if (newChannels.size() != 0) { 678 builder.setChannels(newChannels); 679 } else { 680 builder.setBand( 681 ApConfigUtil.append24GToBandIf24GSupported(0, context)); 682 } 683 } 684 } catch (Exception e) { 685 Log.e(TAG, "Failed to update config by removing unavailable bands" 686 + e); 687 return null; 688 } 689 690 return builder.build(); 691 } 692 693 /** 694 * Remove all unsupported bands from the input band and return the resulting 695 * (remaining) support bands. Unsupported bands are those which don't have channels available. 696 * 697 * @param context The caller context used to get value from resource file. 698 * @param band The target band which plan to enable 699 * 700 * @return the available band which removed the unsupported band. 701 * 0 when all of the band is not supported. 702 */ removeUnsupportedBands(Context context, @NonNull int band)703 public static @BandType int removeUnsupportedBands(Context context, 704 @NonNull int band) { 705 int availableBand = band; 706 for (int b : SoftApConfiguration.BAND_TYPES) { 707 if (((band & b) != 0) && !isSoftApBandSupported(context, b)) { 708 availableBand &= ~b; 709 } 710 } 711 return availableBand; 712 } 713 714 /** 715 * Check if security type is restricted for operation in 6GHz band 716 * As per WFA specification for 6GHz operation, the following security types are not allowed to 717 * be used in 6GHz band: 718 * - OPEN 719 * - WPA2-Personal 720 * - WPA3-SAE-Transition 721 * - WPA3-OWE-Transition 722 * 723 * @param type security type to check on 724 * 725 * @return true if security type is restricted for operation in 6GHz band, false otherwise 726 */ isSecurityTypeRestrictedFor6gBand( @oftApConfiguration.SecurityType int type)727 public static boolean isSecurityTypeRestrictedFor6gBand( 728 @SoftApConfiguration.SecurityType int type) { 729 switch(type) { 730 case SoftApConfiguration.SECURITY_TYPE_OPEN: 731 case SoftApConfiguration.SECURITY_TYPE_WPA2_PSK: 732 case SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION: 733 case SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION: 734 return true; 735 } 736 return false; 737 } 738 739 /** 740 * Remove {@link SoftApConfiguration#BAND_6GHZ} if multiple bands are configured 741 * as a mask when security type is restricted to operate in this band. 742 * 743 * @param config The current {@link SoftApConfiguration}. 744 * 745 * @return the updated SoftApConfiguration. 746 */ remove6gBandForUnsupportedSecurity( SoftApConfiguration config)747 public static SoftApConfiguration remove6gBandForUnsupportedSecurity( 748 SoftApConfiguration config) { 749 SoftApConfiguration.Builder builder = new SoftApConfiguration.Builder(config); 750 751 try { 752 if (config.getBands().length == 1) { 753 int configuredBand = config.getBand(); 754 if ((configuredBand & SoftApConfiguration.BAND_6GHZ) != 0 755 && isSecurityTypeRestrictedFor6gBand(config.getSecurityType())) { 756 Log.i(TAG, "remove BAND_6G if multiple bands are configured " 757 + "as a mask since security type is restricted"); 758 builder.setBand(configuredBand & ~SoftApConfiguration.BAND_6GHZ); 759 } 760 } else if (SdkLevel.isAtLeastS()) { 761 SparseIntArray channels = config.getChannels(); 762 SparseIntArray newChannels = new SparseIntArray(channels.size()); 763 if (isSecurityTypeRestrictedFor6gBand(config.getSecurityType())) { 764 for (int i = 0; i < channels.size(); i++) { 765 int band = channels.keyAt(i); 766 if ((band & SoftApConfiguration.BAND_6GHZ) != 0) { 767 Log.i(TAG, "remove BAND_6G if multiple bands are configured " 768 + "as a mask when security type is restricted"); 769 band &= ~SoftApConfiguration.BAND_6GHZ; 770 } 771 newChannels.put(band, channels.valueAt(i)); 772 } 773 builder.setChannels(newChannels); 774 } 775 } 776 } catch (Exception e) { 777 Log.e(TAG, "Failed to update config by removing 6G band for unsupported security type:" 778 + e); 779 return null; 780 } 781 782 return builder.build(); 783 } 784 785 /** 786 * Update AP band and channel based on the provided country code and band. 787 * This will also set 788 * @param wifiNative reference to WifiNative 789 * @param coexManager reference to CoexManager 790 * @param resources the resources to use to get configured allowed channels. 791 * @param countryCode country code 792 * @param config configuration to update 793 * @param capability soft ap capability 794 * @return an integer result code 795 */ updateApChannelConfig(WifiNative wifiNative, @NonNull CoexManager coexManager, Resources resources, String countryCode, SoftApConfiguration.Builder configBuilder, SoftApConfiguration config, SoftApCapability capability)796 public static int updateApChannelConfig(WifiNative wifiNative, 797 @NonNull CoexManager coexManager, 798 Resources resources, 799 String countryCode, 800 SoftApConfiguration.Builder configBuilder, 801 SoftApConfiguration config, 802 SoftApCapability capability) { 803 /* Use default band and channel for device without HAL. */ 804 if (!wifiNative.isHalStarted()) { 805 configBuilder.setChannel(DEFAULT_AP_CHANNEL, DEFAULT_AP_BAND); 806 return SUCCESS; 807 } 808 809 /* Country code is mandatory for 5GHz band. */ 810 if (config.getBand() == SoftApConfiguration.BAND_5GHZ 811 && countryCode == null) { 812 Log.e(TAG, "5GHz band is not allowed without country code"); 813 return ERROR_GENERIC; 814 } 815 if (!capability.areFeaturesSupported(SOFTAP_FEATURE_ACS_OFFLOAD)) { 816 /* Select a channel if it is not specified and ACS is not enabled */ 817 if (config.getChannel() == 0) { 818 int freq = chooseApChannel(config.getBand(), coexManager, resources, 819 capability); 820 if (freq == -1) { 821 /* We're not able to get channel from wificond. */ 822 Log.e(TAG, "Failed to get available channel."); 823 return ERROR_NO_CHANNEL; 824 } 825 configBuilder.setChannel( 826 ScanResult.convertFrequencyMhzToChannelIfSupported(freq), 827 convertFrequencyToBand(freq)); 828 } 829 830 if (SdkLevel.isAtLeastT()) { 831 /* remove list of allowed channels since they only apply to ACS */ 832 if (sVerboseLoggingEnabled) { 833 Log.i(TAG, "Ignoring Allowed ACS channels since ACS is not supported."); 834 } 835 configBuilder.setAllowedAcsChannels(SoftApConfiguration.BAND_2GHZ, 836 new int[] {}); 837 configBuilder.setAllowedAcsChannels(SoftApConfiguration.BAND_5GHZ, 838 new int[] {}); 839 configBuilder.setAllowedAcsChannels(SoftApConfiguration.BAND_6GHZ, 840 new int[] {}); 841 } 842 } 843 844 return SUCCESS; 845 } 846 847 /** 848 * Helper function for converting WifiConfiguration to SoftApConfiguration. 849 * 850 * Only Support None and WPA2 configuration conversion. 851 * Note that WifiConfiguration only Supports 2GHz, 5GHz, 2GHz+5GHz bands, 852 * so conversion is limited to these bands. 853 * 854 * @param wifiConfig the WifiConfiguration which need to convert. 855 * @return the SoftApConfiguration if wifiConfig is valid, null otherwise. 856 */ 857 @Nullable fromWifiConfiguration( @onNull WifiConfiguration wifiConfig)858 public static SoftApConfiguration fromWifiConfiguration( 859 @NonNull WifiConfiguration wifiConfig) { 860 SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); 861 try { 862 // WifiConfiguration#SSID uses a formatted string with double quotes for UTF-8 and no 863 // quotes for hexadecimal. But to support legacy behavior, we need to continue 864 // setting the entire string with quotes as the UTF-8 SSID. 865 configBuilder.setSsid(wifiConfig.SSID); 866 if (wifiConfig.getAuthType() == WifiConfiguration.KeyMgmt.WPA2_PSK) { 867 configBuilder.setPassphrase(wifiConfig.preSharedKey, 868 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); 869 } 870 configBuilder.setHiddenSsid(wifiConfig.hiddenSSID); 871 872 int band; 873 switch (wifiConfig.apBand) { 874 case WifiConfiguration.AP_BAND_2GHZ: 875 band = SoftApConfiguration.BAND_2GHZ; 876 break; 877 case WifiConfiguration.AP_BAND_5GHZ: 878 band = SoftApConfiguration.BAND_5GHZ; 879 break; 880 case WifiConfiguration.AP_BAND_60GHZ: 881 band = SoftApConfiguration.BAND_60GHZ; 882 break; 883 default: 884 // WifiConfiguration.AP_BAND_ANY means only 2GHz and 5GHz bands 885 band = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ; 886 break; 887 } 888 if (wifiConfig.apChannel == 0) { 889 configBuilder.setBand(band); 890 } else { 891 configBuilder.setChannel(wifiConfig.apChannel, band); 892 } 893 } catch (IllegalArgumentException iae) { 894 Log.e(TAG, "Invalid WifiConfiguration" + iae); 895 return null; 896 } catch (IllegalStateException ise) { 897 Log.e(TAG, "Invalid WifiConfiguration" + ise); 898 return null; 899 } 900 return configBuilder.build(); 901 } 902 903 /** 904 * Helper function to creating SoftApCapability instance with initial field from resource file. 905 * 906 * @param context the caller context used to get value from resource file. 907 * @return SoftApCapability which updated the feature support or not from resource. 908 */ 909 @NonNull updateCapabilityFromResource(@onNull Context context)910 public static SoftApCapability updateCapabilityFromResource(@NonNull Context context) { 911 long features = 0; 912 if (isAcsSupported(context)) { 913 Log.d(TAG, "Update Softap capability, add acs feature support"); 914 features |= SOFTAP_FEATURE_ACS_OFFLOAD; 915 } 916 917 if (isClientForceDisconnectSupported(context)) { 918 Log.d(TAG, "Update Softap capability, add client control feature support"); 919 features |= SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT; 920 } 921 922 if (isWpa3SaeSupported(context)) { 923 Log.d(TAG, "Update Softap capability, add SAE feature support"); 924 features |= SOFTAP_FEATURE_WPA3_SAE; 925 } 926 927 if (isMacCustomizationSupported(context)) { 928 Log.d(TAG, "Update Softap capability, add MAC customization support"); 929 features |= SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION; 930 } 931 932 if (isSoftApBandSupported(context, SoftApConfiguration.BAND_2GHZ)) { 933 Log.d(TAG, "Update Softap capability, add 2.4G support"); 934 features |= SOFTAP_FEATURE_BAND_24G_SUPPORTED; 935 } 936 937 if (isSoftApBandSupported(context, SoftApConfiguration.BAND_5GHZ)) { 938 Log.d(TAG, "Update Softap capability, add 5G support"); 939 features |= SOFTAP_FEATURE_BAND_5G_SUPPORTED; 940 } 941 942 if (isSoftApBandSupported(context, SoftApConfiguration.BAND_6GHZ)) { 943 Log.d(TAG, "Update Softap capability, add 6G support"); 944 features |= SOFTAP_FEATURE_BAND_6G_SUPPORTED; 945 } 946 947 if (isSoftApBandSupported(context, SoftApConfiguration.BAND_60GHZ)) { 948 Log.d(TAG, "Update Softap capability, add 60G support"); 949 features |= SOFTAP_FEATURE_BAND_60G_SUPPORTED; 950 } 951 952 if (isIeee80211axSupported(context)) { 953 Log.d(TAG, "Update Softap capability, add ax support"); 954 features |= SOFTAP_FEATURE_IEEE80211_AX; 955 } 956 957 if (isIeee80211beSupported(context)) { 958 Log.d(TAG, "Update Softap capability, add be support"); 959 features |= SOFTAP_FEATURE_IEEE80211_BE; 960 } 961 962 if (isOweTransitionSupported(context)) { 963 Log.d(TAG, "Update Softap capability, add OWE Transition feature support"); 964 features |= SOFTAP_FEATURE_WPA3_OWE_TRANSITION; 965 } 966 967 if (isOweSupported(context)) { 968 Log.d(TAG, "Update Softap capability, add OWE feature support"); 969 features |= SOFTAP_FEATURE_WPA3_OWE; 970 } 971 972 SoftApCapability capability = new SoftApCapability(features); 973 int hardwareSupportedMaxClient = context.getResources().getInteger( 974 R.integer.config_wifiHardwareSoftapMaxClientCount); 975 if (hardwareSupportedMaxClient > 0) { 976 Log.d(TAG, "Update Softap capability, max client = " + hardwareSupportedMaxClient); 977 capability.setMaxSupportedClients(hardwareSupportedMaxClient); 978 } 979 980 return capability; 981 } 982 983 /** 984 * Helper function to get device support 802.11 AX on Soft AP or not 985 * 986 * @param context the caller context used to get value from resource file. 987 * @return true if supported, false otherwise. 988 */ isIeee80211axSupported(@onNull Context context)989 public static boolean isIeee80211axSupported(@NonNull Context context) { 990 return context.getResources().getBoolean( 991 R.bool.config_wifiSoftapIeee80211axSupported); 992 } 993 994 /** 995 * Helper function to get device support 802.11 BE on Soft AP or not 996 * 997 * @param context the caller context used to get value from resource file. 998 * @return true if supported, false otherwise. 999 */ isIeee80211beSupported(@onNull Context context)1000 public static boolean isIeee80211beSupported(@NonNull Context context) { 1001 return context.getResources().getBoolean( 1002 R.bool.config_wifiSoftapIeee80211beSupported); 1003 } 1004 1005 /** 1006 * Helper function to get device support AP MAC randomization or not. 1007 * 1008 * @param context the caller context used to get value from resource file. 1009 * @return true if supported, false otherwise. 1010 */ isApMacRandomizationSupported(@onNull Context context)1011 public static boolean isApMacRandomizationSupported(@NonNull Context context) { 1012 return context.getResources().getBoolean( 1013 R.bool.config_wifi_ap_mac_randomization_supported); 1014 } 1015 1016 /** 1017 * Helper function to get HAL support bridged AP or not. 1018 * 1019 * @param context the caller context used to get value from resource file. 1020 * @return true if supported, false otherwise. 1021 */ isBridgedModeSupported(@onNull Context context)1022 public static boolean isBridgedModeSupported(@NonNull Context context) { 1023 return SdkLevel.isAtLeastS() && context.getResources().getBoolean( 1024 R.bool.config_wifiBridgedSoftApSupported); 1025 } 1026 1027 /** 1028 * Helper function to get HAL support STA + bridged AP or not. 1029 * 1030 * @param context the caller context used to get value from resource file. 1031 * @return true if supported, false otherwise. 1032 */ isStaWithBridgedModeSupported(@onNull Context context)1033 public static boolean isStaWithBridgedModeSupported(@NonNull Context context) { 1034 return SdkLevel.isAtLeastS() && context.getResources().getBoolean( 1035 R.bool.config_wifiStaWithBridgedSoftApConcurrencySupported); 1036 } 1037 1038 /** 1039 * Helper function to get HAL support client force disconnect or not. 1040 * 1041 * @param context the caller context used to get value from resource file. 1042 * @return true if supported, false otherwise. 1043 */ isClientForceDisconnectSupported(@onNull Context context)1044 public static boolean isClientForceDisconnectSupported(@NonNull Context context) { 1045 return context.getResources().getBoolean( 1046 R.bool.config_wifiSofapClientForceDisconnectSupported); 1047 } 1048 1049 /** 1050 * Helper function to get SAE support or not. 1051 * 1052 * @param context the caller context used to get value from resource file. 1053 * @return true if supported, false otherwise. 1054 */ isWpa3SaeSupported(@onNull Context context)1055 public static boolean isWpa3SaeSupported(@NonNull Context context) { 1056 return context.getResources().getBoolean( 1057 R.bool.config_wifi_softap_sae_supported); 1058 } 1059 1060 /** 1061 * Helper function to get ACS support or not. 1062 * 1063 * @param context the caller context used to get value from resource file. 1064 * @return true if supported, false otherwise. 1065 */ isAcsSupported(@onNull Context context)1066 public static boolean isAcsSupported(@NonNull Context context) { 1067 return context.getResources().getBoolean( 1068 R.bool.config_wifi_softap_acs_supported); 1069 } 1070 1071 /** 1072 * Helper function to get MAC Address customization or not. 1073 * 1074 * @param context the caller context used to get value from resource file. 1075 * @return true if supported, false otherwise. 1076 */ isMacCustomizationSupported(@onNull Context context)1077 public static boolean isMacCustomizationSupported(@NonNull Context context) { 1078 return context.getResources().getBoolean( 1079 R.bool.config_wifiSoftapMacAddressCustomizationSupported); 1080 } 1081 1082 /** 1083 * Helper function to get whether or not Soft AP support on particular band. 1084 * 1085 * @param context the caller context used to get value from resource file. 1086 * @param band the band soft AP to operate on. 1087 * @return true if supported, false otherwise. 1088 */ isSoftApBandSupported(@onNull Context context, @BandType int band)1089 public static boolean isSoftApBandSupported(@NonNull Context context, @BandType int band) { 1090 switch (band) { 1091 case SoftApConfiguration.BAND_2GHZ: 1092 return context.getResources().getBoolean(R.bool.config_wifi24ghzSupport) 1093 && context.getResources().getBoolean( 1094 R.bool.config_wifiSoftap24ghzSupported); 1095 case SoftApConfiguration.BAND_5GHZ: 1096 return context.getResources().getBoolean(R.bool.config_wifi5ghzSupport) 1097 && context.getResources().getBoolean( 1098 R.bool.config_wifiSoftap5ghzSupported); 1099 case SoftApConfiguration.BAND_6GHZ: 1100 return context.getResources().getBoolean(R.bool.config_wifi6ghzSupport) 1101 && context.getResources().getBoolean( 1102 R.bool.config_wifiSoftap6ghzSupported); 1103 case SoftApConfiguration.BAND_60GHZ: 1104 return context.getResources().getBoolean(R.bool.config_wifi60ghzSupport) 1105 && context.getResources().getBoolean( 1106 R.bool.config_wifiSoftap60ghzSupported); 1107 default: 1108 return false; 1109 } 1110 } 1111 1112 /** 1113 * Helper function to get whether or not dynamic country code update is supported when Soft AP 1114 * enabled. 1115 * 1116 * @param context the caller context used to get value from resource file. 1117 * @return true if supported, false otherwise. 1118 */ isSoftApDynamicCountryCodeSupported(@onNull Context context)1119 public static boolean isSoftApDynamicCountryCodeSupported(@NonNull Context context) { 1120 return context.getResources().getBoolean( 1121 R.bool.config_wifiSoftApDynamicCountryCodeUpdateSupported); 1122 } 1123 1124 1125 /** 1126 * Helper function to get whether or not restart Soft AP required when country code changed. 1127 * 1128 * @param context the caller context used to get value from resource file. 1129 * @return true if supported, false otherwise. 1130 */ isSoftApRestartRequiredWhenCountryCodeChanged(@onNull Context context)1131 public static boolean isSoftApRestartRequiredWhenCountryCodeChanged(@NonNull Context context) { 1132 return context.getResources().getBoolean( 1133 R.bool.config_wifiForcedSoftApRestartWhenCountryCodeChanged); 1134 } 1135 1136 /** 1137 * Helper function to get OWE-Transition is support or not. 1138 * 1139 * @param context the caller context used to get value from resource file. 1140 * @return true if supported, false otherwise. 1141 */ isOweTransitionSupported(@onNull Context context)1142 public static boolean isOweTransitionSupported(@NonNull Context context) { 1143 return context.getResources().getBoolean( 1144 R.bool.config_wifiSoftapOweTransitionSupported); 1145 } 1146 1147 /** 1148 * Helper function to get OWE is support or not. 1149 * 1150 * @param context the caller context used to get value from resource file. 1151 * @return true if supported, false otherwise. 1152 */ isOweSupported(@onNull Context context)1153 public static boolean isOweSupported(@NonNull Context context) { 1154 return context.getResources().getBoolean( 1155 R.bool.config_wifiSoftapOweSupported); 1156 } 1157 1158 /** 1159 * Helper function for comparing two SoftApConfiguration. 1160 * 1161 * @param currentConfig the original configuration. 1162 * @param newConfig the new configuration which plan to apply. 1163 * @return true if the difference between the two configurations requires a restart to apply, 1164 * false otherwise. 1165 */ checkConfigurationChangeNeedToRestart( SoftApConfiguration currentConfig, SoftApConfiguration newConfig)1166 public static boolean checkConfigurationChangeNeedToRestart( 1167 SoftApConfiguration currentConfig, SoftApConfiguration newConfig) { 1168 return !Objects.equals(currentConfig.getWifiSsid(), newConfig.getWifiSsid()) 1169 || !Objects.equals(currentConfig.getBssid(), newConfig.getBssid()) 1170 || currentConfig.getSecurityType() != newConfig.getSecurityType() 1171 || !Objects.equals(currentConfig.getPassphrase(), newConfig.getPassphrase()) 1172 || currentConfig.isHiddenSsid() != newConfig.isHiddenSsid() 1173 || currentConfig.getBand() != newConfig.getBand() 1174 || currentConfig.getChannel() != newConfig.getChannel() 1175 || (SdkLevel.isAtLeastS() && !currentConfig.getChannels().toString() 1176 .equals(newConfig.getChannels().toString())); 1177 } 1178 1179 1180 /** 1181 * Helper function for checking all of the configuration are supported or not. 1182 * 1183 * @param config target configuration want to check. 1184 * @param capability the capability which indicate feature support or not. 1185 * @return true if supported, false otherwise. 1186 */ checkSupportAllConfiguration(SoftApConfiguration config, SoftApCapability capability)1187 public static boolean checkSupportAllConfiguration(SoftApConfiguration config, 1188 SoftApCapability capability) { 1189 if (!capability.areFeaturesSupported(SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT) 1190 && (config.getMaxNumberOfClients() != 0 || config.isClientControlByUserEnabled() 1191 || config.getBlockedClientList().size() != 0)) { 1192 Log.d(TAG, "Error, Client control requires HAL support"); 1193 return false; 1194 } 1195 if (!capability.areFeaturesSupported(SOFTAP_FEATURE_WPA3_SAE)) { 1196 if (config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION 1197 || config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE) { 1198 Log.d(TAG, "Error, SAE requires HAL support"); 1199 return false; 1200 } 1201 } 1202 if (!capability.areFeaturesSupported(SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)) { 1203 if (config.getBssid() != null) { 1204 Log.d(TAG, "Error, MAC address customization requires HAL support"); 1205 return false; 1206 } 1207 if (SdkLevel.isAtLeastS() 1208 && (config.getMacRandomizationSetting() 1209 == SoftApConfiguration.RANDOMIZATION_PERSISTENT 1210 || config.getMacRandomizationSetting() 1211 == SoftApConfiguration.RANDOMIZATION_NON_PERSISTENT)) { 1212 Log.d(TAG, "Error, MAC randomization requires HAL support"); 1213 return false; 1214 } 1215 } 1216 int requestedBands = 0; 1217 for (int band : config.getBands()) { 1218 requestedBands |= band; 1219 } 1220 if (!capability.areFeaturesSupported(SOFTAP_FEATURE_BAND_24G_SUPPORTED)) { 1221 if ((requestedBands & SoftApConfiguration.BAND_2GHZ) != 0) { 1222 Log.d(TAG, "Error, 2.4Ghz band requires HAL support"); 1223 return false; 1224 } 1225 } 1226 if (!capability.areFeaturesSupported(SOFTAP_FEATURE_BAND_5G_SUPPORTED)) { 1227 if ((requestedBands & SoftApConfiguration.BAND_5GHZ) != 0) { 1228 Log.d(TAG, "Error, 5Ghz band requires HAL support"); 1229 return false; 1230 } 1231 } 1232 if (!capability.areFeaturesSupported(SOFTAP_FEATURE_BAND_6G_SUPPORTED)) { 1233 if ((requestedBands & SoftApConfiguration.BAND_6GHZ) != 0) { 1234 Log.d(TAG, "Error, 6Ghz band requires HAL support"); 1235 return false; 1236 } 1237 } 1238 if (!capability.areFeaturesSupported(SOFTAP_FEATURE_BAND_60G_SUPPORTED)) { 1239 if ((requestedBands & SoftApConfiguration.BAND_60GHZ) != 0) { 1240 Log.d(TAG, "Error, 60Ghz band requires HAL support"); 1241 return false; 1242 } 1243 } 1244 if (!capability.areFeaturesSupported(SOFTAP_FEATURE_WPA3_OWE_TRANSITION)) { 1245 if (config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION) { 1246 Log.d(TAG, "Error, OWE transition requires HAL support"); 1247 return false; 1248 } 1249 } 1250 if (!capability.areFeaturesSupported(SOFTAP_FEATURE_WPA3_OWE)) { 1251 if (config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_WPA3_OWE) { 1252 Log.d(TAG, "Error, OWE requires HAL support"); 1253 return false; 1254 } 1255 } 1256 1257 // Checks for Dual AP 1258 if (SdkLevel.isAtLeastS() && config.getBands().length > 1) { 1259 int[] bands = config.getBands(); 1260 if ((bands[0] & SoftApConfiguration.BAND_60GHZ) != 0 1261 || (bands[1] & SoftApConfiguration.BAND_60GHZ) != 0) { 1262 Log.d(TAG, "Error, dual APs doesn't support on 60GHz"); 1263 return false; 1264 } 1265 if (!capability.areFeaturesSupported(SOFTAP_FEATURE_ACS_OFFLOAD) 1266 && (config.getChannels().valueAt(0) == 0 1267 || config.getChannels().valueAt(1) == 0)) { 1268 Log.d(TAG, "Error, dual APs requires HAL ACS support when channel isn't specified"); 1269 return false; 1270 } 1271 } 1272 return true; 1273 } 1274 1275 1276 /** 1277 * Check if need to provide freq range for ACS. 1278 * 1279 * @param band in SoftApConfiguration.BandType 1280 * @param context the caller context used to get values from resource file 1281 * @param config the used SoftApConfiguration 1282 * 1283 * @return true when freq ranges is needed, otherwise false. 1284 */ isSendFreqRangesNeeded(@andType int band, Context context, SoftApConfiguration config)1285 public static boolean isSendFreqRangesNeeded(@BandType int band, Context context, 1286 SoftApConfiguration config) { 1287 // Fist we check if one of the selected bands has restrictions in the overlay file or in the 1288 // provided SoftApConfiguration. 1289 // Note, 1290 // - We store the config string here for future use, hence we need to check all bands. 1291 // - If there is no restrictions on channels, we store the full band 1292 for (int b : SoftApConfiguration.BAND_TYPES) { 1293 if ((band & b) != 0) { 1294 List<Integer> configuredList = getConfiguredChannelList(context.getResources(), b); 1295 if (configuredList != null && !configuredList.isEmpty()) { 1296 // If any of the selected band has restriction in the overlay file return true. 1297 return true; 1298 } 1299 if (SdkLevel.isAtLeastT() && config.getAllowedAcsChannels(b).length != 0) { 1300 return true; 1301 } 1302 } 1303 } 1304 1305 // Next, if only one of 5G or 6G is selected, then we need freqList to separate them 1306 // Since there is no other way. 1307 if (((band & SoftApConfiguration.BAND_5GHZ) != 0) 1308 && ((band & SoftApConfiguration.BAND_6GHZ) == 0)) { 1309 return true; 1310 } 1311 if (((band & SoftApConfiguration.BAND_5GHZ) == 0) 1312 && ((band & SoftApConfiguration.BAND_6GHZ) != 0)) { 1313 return true; 1314 } 1315 1316 // In all other cases, we don't need to set the freqList 1317 return false; 1318 } 1319 1320 /** 1321 * Collect a List of allowed channels for ACS operations on a selected band 1322 * 1323 * @param band on which channel list are required 1324 * @param oemConfigString Configuration string from OEM resource file. 1325 * An empty string means all channels on this band are allowed 1326 * @param callerConfig allowed chnannels as required by the caller 1327 * 1328 * @return List of channel numbers that meet both criteria 1329 */ collectAllowedAcsChannels(@andType int band, String oemConfigString, int[] callerConfig)1330 public static List<Integer> collectAllowedAcsChannels(@BandType int band, 1331 String oemConfigString, int[] callerConfig) { 1332 1333 // Convert the OEM config string into a set of channel numbers 1334 Set<Integer> allowedChannelSet = getOemAllowedChannels(band, oemConfigString); 1335 1336 // Update the allowed channels with user configuration 1337 allowedChannelSet.retainAll(getCallerAllowedChannels(band, callerConfig)); 1338 1339 return new ArrayList<Integer>(allowedChannelSet); 1340 } 1341 getSetForAllChannelsInBand(@andType int band)1342 private static Set<Integer> getSetForAllChannelsInBand(@BandType int band) { 1343 switch(band) { 1344 case SoftApConfiguration.BAND_2GHZ: 1345 return IntStream.rangeClosed( 1346 ScanResult.BAND_24_GHZ_FIRST_CH_NUM, 1347 ScanResult.BAND_24_GHZ_LAST_CH_NUM) 1348 .boxed() 1349 .collect(Collectors.toSet()); 1350 1351 case SoftApConfiguration.BAND_5GHZ: 1352 return IntStream.rangeClosed( 1353 ScanResult.BAND_5_GHZ_FIRST_CH_NUM, 1354 ScanResult.BAND_5_GHZ_LAST_CH_NUM) 1355 .boxed() 1356 .collect(Collectors.toSet()); 1357 1358 case SoftApConfiguration.BAND_6GHZ: 1359 return IntStream.rangeClosed( 1360 ScanResult.BAND_6_GHZ_FIRST_CH_NUM, 1361 ScanResult.BAND_6_GHZ_LAST_CH_NUM) 1362 .boxed() 1363 .collect(Collectors.toSet()); 1364 default: 1365 Log.e(TAG, "Invalid band: " + bandToString(band)); 1366 return Collections.emptySet(); 1367 } 1368 } 1369 getOemAllowedChannels(@andType int band, String oemConfigString)1370 private static Set<Integer> getOemAllowedChannels(@BandType int band, String oemConfigString) { 1371 if (TextUtils.isEmpty(oemConfigString)) { 1372 // Empty string means all channels are allowed in this band 1373 return getSetForAllChannelsInBand(band); 1374 } 1375 1376 // String is not empty, parsing it 1377 Set<Integer> allowedChannelsOem = new HashSet<>(); 1378 1379 for (String channelRange : oemConfigString.split(",")) { 1380 try { 1381 if (channelRange.contains("-")) { 1382 String[] channels = channelRange.split("-"); 1383 if (channels.length != 2) { 1384 Log.e(TAG, "Unrecognized channel range, length is " + channels.length); 1385 continue; 1386 } 1387 int start = Integer.parseInt(channels[0].trim()); 1388 int end = Integer.parseInt(channels[1].trim()); 1389 if (start > end) { 1390 Log.e(TAG, "Invalid channel range, from " + start + " to " + end); 1391 continue; 1392 } 1393 1394 allowedChannelsOem.addAll(IntStream.rangeClosed(start, end) 1395 .boxed().collect(Collectors.toSet())); 1396 } else if (!TextUtils.isEmpty(channelRange)) { 1397 int channel = Integer.parseInt(channelRange.trim()); 1398 allowedChannelsOem.add(channel); 1399 } 1400 } catch (NumberFormatException e) { 1401 // Ignore malformed value 1402 Log.e(TAG, "Malformed channel value detected: " + e); 1403 continue; 1404 } 1405 } 1406 1407 return allowedChannelsOem; 1408 } 1409 getCallerAllowedChannels(@andType int band, int[] callerConfig)1410 private static Set<Integer> getCallerAllowedChannels(@BandType int band, int[] callerConfig) { 1411 if (callerConfig.length == 0) { 1412 // Empty set means all channels are allowed in this band 1413 return getSetForAllChannelsInBand(band); 1414 } 1415 1416 // Otherwise return the caller set as is 1417 return IntStream.of(callerConfig).boxed() 1418 .collect(Collectors.toCollection(HashSet::new)); 1419 } 1420 1421 /** 1422 * Deep copy for object Map<String, SoftApInfo> 1423 */ deepCopyForSoftApInfoMap( Map<String, SoftApInfo> originalMap)1424 public static Map<String, SoftApInfo> deepCopyForSoftApInfoMap( 1425 Map<String, SoftApInfo> originalMap) { 1426 if (originalMap == null) { 1427 return null; 1428 } 1429 Map<String, SoftApInfo> deepCopyMap = new HashMap<String, SoftApInfo>(); 1430 for (Map.Entry<String, SoftApInfo> entry: originalMap.entrySet()) { 1431 deepCopyMap.put(entry.getKey(), new SoftApInfo(entry.getValue())); 1432 } 1433 return deepCopyMap; 1434 } 1435 1436 /** 1437 * Deep copy for object Map<String, List<WifiClient>> 1438 */ deepCopyForWifiClientListMap( Map<String, List<WifiClient>> originalMap)1439 public static Map<String, List<WifiClient>> deepCopyForWifiClientListMap( 1440 Map<String, List<WifiClient>> originalMap) { 1441 if (originalMap == null) { 1442 return null; 1443 } 1444 Map<String, List<WifiClient>> deepCopyMap = new HashMap<String, List<WifiClient>>(); 1445 for (Map.Entry<String, List<WifiClient>> entry: originalMap.entrySet()) { 1446 List<WifiClient> clients = new ArrayList<>(); 1447 for (WifiClient client : entry.getValue()) { 1448 clients.add(new WifiClient(client.getMacAddress(), 1449 client.getApInstanceIdentifier())); 1450 } 1451 deepCopyMap.put(entry.getKey(), clients); 1452 } 1453 return deepCopyMap; 1454 } 1455 1456 1457 /** 1458 * Observer the available channel from native layer (vendor HAL if getUsableChannels is 1459 * supported, or wificond if not supported) and update the SoftApCapability 1460 * 1461 * @param softApCapability the current softap capability 1462 * @param context the caller context used to get value from resource file 1463 * @param wifiNative reference used to collect regulatory restrictions. * 1464 * @return updated soft AP capability 1465 */ updateSoftApCapabilityWithAvailableChannelList( @onNull SoftApCapability softApCapability, @NonNull Context context, @NonNull WifiNative wifiNative)1466 public static SoftApCapability updateSoftApCapabilityWithAvailableChannelList( 1467 @NonNull SoftApCapability softApCapability, @NonNull Context context, 1468 @NonNull WifiNative wifiNative) { 1469 SoftApCapability newSoftApCapability = new SoftApCapability(softApCapability); 1470 List<Integer> supportedChannelList = null; 1471 1472 for (int band : SoftApConfiguration.BAND_TYPES) { 1473 if (isSoftApBandSupported(context, band)) { 1474 supportedChannelList = getAvailableChannelFreqsForBand( 1475 band, wifiNative, context.getResources(), false); 1476 if (supportedChannelList != null) { 1477 newSoftApCapability.setSupportedChannelList( 1478 band, 1479 supportedChannelList.stream().mapToInt(Integer::intValue).toArray()); 1480 } 1481 } 1482 } 1483 return newSoftApCapability; 1484 } 1485 1486 /** 1487 * Helper function to check if security type can ignore password. 1488 * 1489 * @param security type for SoftApConfiguration. 1490 * @return true for Open/Owe-Transition SoftAp AKM. 1491 */ isNonPasswordAP(int security)1492 public static boolean isNonPasswordAP(int security) { 1493 return (security == SoftApConfiguration.SECURITY_TYPE_OPEN 1494 || security == SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION 1495 || security == SoftApConfiguration.SECURITY_TYPE_WPA3_OWE); 1496 } 1497 } 1498