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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.net.wifi.CoexUnsafeChannel; 24 import android.net.wifi.ScanResult; 25 import android.net.wifi.SoftApCapability; 26 import android.net.wifi.SoftApConfiguration; 27 import android.net.wifi.SoftApConfiguration.BandType; 28 import android.net.wifi.SoftApInfo; 29 import android.net.wifi.WifiClient; 30 import android.net.wifi.WifiConfiguration; 31 import android.net.wifi.WifiManager; 32 import android.net.wifi.WifiScanner; 33 import android.text.TextUtils; 34 import android.util.Log; 35 import android.util.SparseArray; 36 37 import com.android.modules.utils.build.SdkLevel; 38 import com.android.server.wifi.WifiNative; 39 import com.android.server.wifi.coex.CoexManager; 40 import com.android.wifi.resources.R; 41 42 import java.util.ArrayList; 43 import java.util.HashMap; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Objects; 48 import java.util.Random; 49 import java.util.Set; 50 51 52 /** 53 * Provide utility functions for updating soft AP related configuration. 54 */ 55 public class ApConfigUtil { 56 private static final String TAG = "ApConfigUtil"; 57 58 public static final int INVALID_VALUE_FOR_BAND_OR_CHANNEL = -1; 59 public static final int DEFAULT_AP_BAND = SoftApConfiguration.BAND_2GHZ; 60 public static final int DEFAULT_AP_CHANNEL = 6; 61 public static final int HIGHEST_2G_AP_CHANNEL = 14; 62 63 /* Return code for updateConfiguration. */ 64 public static final int SUCCESS = 0; 65 public static final int ERROR_NO_CHANNEL = 1; 66 public static final int ERROR_GENERIC = 2; 67 public static final int ERROR_UNSUPPORTED_CONFIGURATION = 3; 68 69 /* Random number generator used for AP channel selection. */ 70 private static final Random sRandom = new Random(); 71 72 /** 73 * Valid Global Operating classes in each wifi band 74 * Reference: Table E-4 in IEEE Std 802.11-2016. 75 */ 76 private static final SparseArray<int[]> sBandToOperatingClass = new SparseArray<>(); 77 static { sBandToOperatingClass.append(SoftApConfiguration.BAND_2GHZ, new int[]{81, 82, 83, 84})78 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})79 sBandToOperatingClass.append(SoftApConfiguration.BAND_5GHZ, new int[]{115, 116, 117, 118, 80 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})81 sBandToOperatingClass.append(SoftApConfiguration.BAND_6GHZ, new int[]{131, 132, 133, 134, 82 135, 136}); 83 } 84 85 /** 86 * Helper function to get the band corresponding to the operating class. 87 * 88 * @param operatingClass Global operating class. 89 * @return band, -1 if no match. 90 * 91 */ getBandFromOperatingClass(int operatingClass)92 public static int getBandFromOperatingClass(int operatingClass) { 93 for (int i = 0; i < sBandToOperatingClass.size(); i++) { 94 int band = sBandToOperatingClass.keyAt(i); 95 int[] operatingClasses = sBandToOperatingClass.get(band); 96 97 for (int j = 0; j < operatingClasses.length; j++) { 98 if (operatingClasses[j] == operatingClass) { 99 return band; 100 } 101 } 102 } 103 return -1; 104 } 105 106 /** 107 * Convert band from SoftApConfiguration.BandType to WifiScanner.WifiBand 108 * @param band in SoftApConfiguration.BandType 109 * @return band in WifiScanner.WifiBand 110 */ apConfig2wifiScannerBand(@andType int band)111 public static @WifiScanner.WifiBand int apConfig2wifiScannerBand(@BandType int band) { 112 switch(band) { 113 case SoftApConfiguration.BAND_2GHZ: 114 return WifiScanner.WIFI_BAND_24_GHZ; 115 case SoftApConfiguration.BAND_5GHZ: 116 return WifiScanner.WIFI_BAND_5_GHZ; 117 case SoftApConfiguration.BAND_6GHZ: 118 return WifiScanner.WIFI_BAND_6_GHZ; 119 case SoftApConfiguration.BAND_60GHZ: 120 return WifiScanner.WIFI_BAND_60_GHZ; 121 default: 122 return WifiScanner.WIFI_BAND_UNSPECIFIED; 123 } 124 } 125 126 /** 127 * Convert channel/band to frequency. 128 * Note: the utility does not perform any regulatory domain compliance. 129 * @param channel number to convert 130 * @param band of channel to convert 131 * @return center frequency in Mhz of the channel, -1 if no match 132 */ convertChannelToFrequency(int channel, @BandType int band)133 public static int convertChannelToFrequency(int channel, @BandType int band) { 134 return ScanResult.convertChannelToFrequencyMhzIfSupported(channel, 135 apConfig2wifiScannerBand(band)); 136 } 137 138 /** 139 * Convert frequency to band. 140 * Note: the utility does not perform any regulatory domain compliance. 141 * @param frequency frequency to convert 142 * @return band, -1 if no match 143 */ convertFrequencyToBand(int frequency)144 public static int convertFrequencyToBand(int frequency) { 145 if (ScanResult.is24GHz(frequency)) { 146 return SoftApConfiguration.BAND_2GHZ; 147 } else if (ScanResult.is5GHz(frequency)) { 148 return SoftApConfiguration.BAND_5GHZ; 149 } else if (ScanResult.is6GHz(frequency)) { 150 return SoftApConfiguration.BAND_6GHZ; 151 } else if (ScanResult.is60GHz(frequency)) { 152 return SoftApConfiguration.BAND_60GHZ; 153 } 154 155 return -1; 156 } 157 158 /** 159 * Convert band from WifiConfiguration into SoftApConfiguration 160 * 161 * @param wifiConfigBand band encoded as WifiConfiguration.AP_BAND_xxxx 162 * @return band as encoded as SoftApConfiguration.BAND_xxx 163 */ convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand)164 public static int convertWifiConfigBandToSoftApConfigBand(int wifiConfigBand) { 165 switch (wifiConfigBand) { 166 case WifiConfiguration.AP_BAND_2GHZ: 167 return SoftApConfiguration.BAND_2GHZ; 168 case WifiConfiguration.AP_BAND_5GHZ: 169 return SoftApConfiguration.BAND_5GHZ; 170 case WifiConfiguration.AP_BAND_ANY: 171 return SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ; 172 default: 173 return SoftApConfiguration.BAND_2GHZ; 174 } 175 } 176 177 /** 178 * Add 2.4Ghz to target band when 2.4Ghz SoftAp supported. 179 * 180 * @param targetBand The band is needed to add 2.4G. 181 * @return The band includes 2.4Ghz when 2.4G SoftAp supported. 182 */ append24GToBandIf24GSupported(@andType int targetBand, Context context)183 public static @BandType int append24GToBandIf24GSupported(@BandType int targetBand, 184 Context context) { 185 if (isBandSupported(SoftApConfiguration.BAND_2GHZ, context)) { 186 return targetBand | SoftApConfiguration.BAND_2GHZ; 187 } 188 return targetBand; 189 } 190 191 /** 192 * Checks if band is a valid combination of {link SoftApConfiguration#BandType} values 193 */ isBandValid(@andType int band)194 public static boolean isBandValid(@BandType int band) { 195 int bandAny = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ 196 | SoftApConfiguration.BAND_6GHZ | SoftApConfiguration.BAND_60GHZ; 197 return ((band != 0) && ((band & ~bandAny) == 0)); 198 } 199 200 /** 201 * Check if the band contains a certain sub-band 202 * 203 * @param band The combination of bands to validate 204 * @param testBand the test band to validate on 205 * @return true if band contains testBand, false otherwise 206 */ containsBand(@andType int band, @BandType int testBand)207 public static boolean containsBand(@BandType int band, @BandType int testBand) { 208 return ((band & testBand) != 0); 209 } 210 211 /** 212 * Checks if band contains multiple sub-bands 213 * @param band a combination of sub-bands 214 * @return true if band has multiple sub-bands, false otherwise 215 */ isMultiband(@andType int band)216 public static boolean isMultiband(@BandType int band) { 217 return ((band & (band - 1)) != 0); 218 } 219 220 221 /** 222 * Checks whether or not band configuration is supported. 223 * @param apBand a combination of the bands 224 * @param context the caller context used to get value from resource file. 225 * @return true if band is supported, false otherwise 226 */ isBandSupported(@andType int apBand, Context context)227 public static boolean isBandSupported(@BandType int apBand, Context context) { 228 if (!isBandValid(apBand)) { 229 Log.e(TAG, "Invalid SoftAp band. "); 230 return false; 231 } 232 233 if (containsBand(apBand, SoftApConfiguration.BAND_2GHZ) 234 && !isSoftAp24GhzSupported(context)) { 235 Log.e(TAG, "Can not start softAp with 2GHz band, not supported."); 236 return false; 237 } 238 239 if (containsBand(apBand, SoftApConfiguration.BAND_5GHZ) 240 && !isSoftAp5GhzSupported(context)) { 241 Log.e(TAG, "Can not start softAp with 5GHz band, not supported."); 242 return false; 243 } 244 245 if (containsBand(apBand, SoftApConfiguration.BAND_6GHZ) 246 && !isSoftAp6GhzSupported(context)) { 247 Log.e(TAG, "Can not start softAp with 6GHz band, not supported."); 248 return false; 249 } 250 251 if (containsBand(apBand, SoftApConfiguration.BAND_60GHZ) 252 && !isSoftAp60GhzSupported(context)) { 253 Log.e(TAG, "Can not start softAp with 6GHz band, not supported."); 254 return false; 255 } 256 257 return true; 258 } 259 260 /** 261 * Convert string to channel list 262 * Format of the list is a comma separated channel numbers, or range of channel numbers 263 * Example, "34-48, 149". 264 * @param channelString for a comma separated channel numbers, or range of channel numbers 265 * such as "34-48, 149" 266 * @return list of channel numbers 267 */ convertStringToChannelList(String channelString)268 public static List<Integer> convertStringToChannelList(String channelString) { 269 if (channelString == null) { 270 return null; 271 } 272 273 List<Integer> channelList = new ArrayList<Integer>(); 274 275 for (String channelRange : channelString.split(",")) { 276 try { 277 if (channelRange.contains("-")) { 278 String[] channels = channelRange.split("-"); 279 if (channels.length != 2) { 280 Log.e(TAG, "Unrecognized channel range, Length is " + channels.length); 281 continue; 282 } 283 int start = Integer.parseInt(channels[0].trim()); 284 int end = Integer.parseInt(channels[1].trim()); 285 if (start > end) { 286 Log.e(TAG, "Invalid channel range, from " + start + " to " + end); 287 continue; 288 } 289 290 for (int channel = start; channel <= end; channel++) { 291 channelList.add(channel); 292 } 293 } else { 294 channelList.add(Integer.parseInt(channelRange.trim())); 295 } 296 } catch (NumberFormatException e) { 297 // Ignore malformed string 298 Log.e(TAG, "Malformed channel value detected: " + e); 299 continue; 300 } 301 } 302 return channelList; 303 } 304 305 /** 306 * Returns the unsafe channels frequency from coex module. 307 * 308 * @param coexManager reference used to get unsafe channels to avoid for coex. 309 */ 310 @NonNull getUnsafeChannelFreqsFromCoex(@onNull CoexManager coexManager)311 public static Set<Integer> getUnsafeChannelFreqsFromCoex(@NonNull CoexManager coexManager) { 312 Set<Integer> unsafeFreqs = new HashSet<>(); 313 if (SdkLevel.isAtLeastS()) { 314 for (CoexUnsafeChannel unsafeChannel : coexManager.getCoexUnsafeChannels()) { 315 unsafeFreqs.add(ScanResult.convertChannelToFrequencyMhzIfSupported( 316 unsafeChannel.getChannel(), unsafeChannel.getBand())); 317 } 318 } 319 return unsafeFreqs; 320 } 321 322 /** 323 * Get channels or frequencies for band that are allowed by both regulatory 324 * and OEM configuration. 325 * 326 * @param band to get channels for 327 * @param wifiNative reference used to get regulatory restrictionsimport java.util.Arrays; 328 * @param resources used to get OEM restrictions 329 * @param inFrequencyMHz true to convert channel to frequency. 330 * @return A list of frequencies that are allowed, null on error. 331 */ getAvailableChannelFreqsForBand( @andType int band, WifiNative wifiNative, Resources resources, boolean inFrequencyMHz)332 public static List<Integer> getAvailableChannelFreqsForBand( 333 @BandType int band, WifiNative wifiNative, Resources resources, 334 boolean inFrequencyMHz) { 335 if (!isBandValid(band) || isMultiband(band)) { 336 return null; 337 } 338 339 List<Integer> configuredList; 340 int scannerBand; 341 switch (band) { 342 case SoftApConfiguration.BAND_2GHZ: 343 configuredList = convertStringToChannelList(resources.getString( 344 R.string.config_wifiSoftap2gChannelList)); 345 scannerBand = WifiScanner.WIFI_BAND_24_GHZ; 346 break; 347 case SoftApConfiguration.BAND_5GHZ: 348 configuredList = convertStringToChannelList(resources.getString( 349 R.string.config_wifiSoftap5gChannelList)); 350 scannerBand = WifiScanner.WIFI_BAND_5_GHZ; 351 break; 352 case SoftApConfiguration.BAND_6GHZ: 353 configuredList = convertStringToChannelList(resources.getString( 354 R.string.config_wifiSoftap6gChannelList)); 355 scannerBand = WifiScanner.WIFI_BAND_6_GHZ; 356 break; 357 case SoftApConfiguration.BAND_60GHZ: 358 configuredList = convertStringToChannelList(resources.getString( 359 R.string.config_wifiSoftap60gChannelList)); 360 scannerBand = WifiScanner.WIFI_BAND_60_GHZ; 361 break; 362 default: 363 return null; 364 } 365 366 // Get the allowed list of channel frequencies in MHz 367 int[] regulatoryArray = wifiNative.getChannelsForBand(scannerBand); 368 List<Integer> regulatoryList = new ArrayList<Integer>(); 369 for (int freq : regulatoryArray) { 370 if (inFrequencyMHz) { 371 regulatoryList.add(freq); 372 } else { 373 regulatoryList.add(ScanResult.convertFrequencyMhzToChannelIfSupported(freq)); 374 } 375 } 376 377 if (configuredList == null || configuredList.isEmpty()) { 378 return regulatoryList; 379 } 380 List<Integer> filteredList = new ArrayList<Integer>(); 381 // Otherwise, filter the configured list 382 for (int channel : configuredList) { 383 if (inFrequencyMHz) { 384 int channelFreq = convertChannelToFrequency(channel, band); 385 if (regulatoryList.contains(channelFreq)) { 386 filteredList.add(channelFreq); 387 } 388 } else if (regulatoryList.contains(channel)) { 389 filteredList.add(channel); 390 } 391 } 392 return filteredList; 393 } 394 395 /** 396 * Return a channel frequency for AP setup based on the frequency band. 397 * @param apBand one or combination of the values of SoftApConfiguration.BAND_*. 398 * @param wifiNative reference used to collect regulatory restrictions. 399 * @param coexManager reference used to get unsafe channels to avoid for coex. 400 * @param resources the resources to use to get configured allowed channels. 401 * @return a valid channel frequency on success, -1 on failure. 402 */ chooseApChannel(int apBand, @NonNull WifiNative wifiNative, @NonNull CoexManager coexManager, @NonNull Resources resources)403 public static int chooseApChannel(int apBand, @NonNull WifiNative wifiNative, 404 @NonNull CoexManager coexManager, @NonNull Resources resources) { 405 if (!isBandValid(apBand)) { 406 Log.e(TAG, "Invalid band: " + apBand); 407 return -1; 408 } 409 410 Set<Integer> unsafeFreqs = new HashSet<>(); 411 if (SdkLevel.isAtLeastS()) { 412 unsafeFreqs = getUnsafeChannelFreqsFromCoex(coexManager); 413 } 414 final int[] bandPreferences = new int[]{ 415 SoftApConfiguration.BAND_60GHZ, 416 SoftApConfiguration.BAND_6GHZ, 417 SoftApConfiguration.BAND_5GHZ, 418 SoftApConfiguration.BAND_2GHZ}; 419 int selectedUnsafeFreq = 0; 420 for (int band : bandPreferences) { 421 if ((apBand & band) == 0) { 422 continue; 423 } 424 final List<Integer> availableFreqs = 425 getAvailableChannelFreqsForBand(band, wifiNative, resources, true); 426 if (availableFreqs == null || availableFreqs.isEmpty()) { 427 continue; 428 } 429 // Separate the available freqs by safe and unsafe. 430 List<Integer> availableSafeFreqs = new ArrayList<>(); 431 List<Integer> availableUnsafeFreqs = new ArrayList<>(); 432 for (int freq : availableFreqs) { 433 if (unsafeFreqs.contains(freq)) { 434 availableUnsafeFreqs.add(freq); 435 } else { 436 availableSafeFreqs.add(freq); 437 } 438 } 439 // If there are safe freqs available for this band, randomly select one. 440 if (!availableSafeFreqs.isEmpty()) { 441 return availableSafeFreqs.get(sRandom.nextInt(availableSafeFreqs.size())); 442 } else if (!availableUnsafeFreqs.isEmpty() && selectedUnsafeFreq == 0) { 443 // Save an unsafe freq from the first preferred band to fall back on later. 444 selectedUnsafeFreq = availableUnsafeFreqs.get( 445 sRandom.nextInt(availableUnsafeFreqs.size())); 446 } 447 } 448 // If all available channels are soft unsafe, select a random one of the highest band. 449 boolean isHardUnsafe = false; 450 if (SdkLevel.isAtLeastS()) { 451 isHardUnsafe = 452 (coexManager.getCoexRestrictions() & WifiManager.COEX_RESTRICTION_SOFTAP) != 0; 453 } 454 if (!isHardUnsafe && selectedUnsafeFreq != 0) { 455 return selectedUnsafeFreq; 456 } 457 458 // If all available channels are hard unsafe, select the default AP channel. 459 if (containsBand(apBand, DEFAULT_AP_BAND)) { 460 final int defaultChannelFreq = convertChannelToFrequency(DEFAULT_AP_CHANNEL, 461 DEFAULT_AP_BAND); 462 Log.e(TAG, "Allowed channel list not specified, selecting default channel"); 463 if (isHardUnsafe && unsafeFreqs.contains(defaultChannelFreq)) { 464 Log.e(TAG, "Default channel is hard restricted due to coex"); 465 } 466 return defaultChannelFreq; 467 } 468 Log.e(TAG, "No available channels"); 469 return -1; 470 } 471 472 /** 473 * Remove unavailable bands from the input band and return the resulting 474 * (remaining) available bands. Unavailable bands are those which don't have channels available. 475 * 476 * @param capability SoftApCapability which inidcates supported channel list. 477 * @param targetBand The target band which plan to enable 478 * @param coexManager reference to CoexManager 479 * 480 * @return the available band which removed the unsupported band. 481 * 0 when all of the band is not supported. 482 */ removeUnavailableBands(SoftApCapability capability, @NonNull int targetBand, CoexManager coexManager)483 public static @BandType int removeUnavailableBands(SoftApCapability capability, 484 @NonNull int targetBand, CoexManager coexManager) { 485 int availableBand = targetBand; 486 for (int band : SoftApConfiguration.BAND_TYPES) { 487 Set<Integer> availableChannelFreqsList = new HashSet<>(); 488 if ((targetBand & band) != 0) { 489 for (int channel : capability.getSupportedChannelList(band)) { 490 availableChannelFreqsList.add(convertChannelToFrequency(channel, band)); 491 } 492 // Only remove hard unsafe channels 493 if (SdkLevel.isAtLeastS() 494 && (coexManager.getCoexRestrictions() & WifiManager.COEX_RESTRICTION_SOFTAP) 495 != 0) { 496 availableChannelFreqsList.removeAll(getUnsafeChannelFreqsFromCoex(coexManager)); 497 } 498 if (availableChannelFreqsList.size() == 0) { 499 availableBand &= ~band; 500 } 501 } 502 } 503 return availableBand; 504 } 505 506 /** 507 * Remove all unsupported bands from the input band and return the resulting 508 * (remaining) support bands. Unsupported bands are those which don't have channels available. 509 * 510 * @param context The caller context used to get value from resource file. 511 * @param band The target band which plan to enable 512 * 513 * @return the available band which removed the unsupported band. 514 * 0 when all of the band is not supported. 515 */ removeUnsupportedBands(Context context, @NonNull int band)516 public static @BandType int removeUnsupportedBands(Context context, 517 @NonNull int band) { 518 int availableBand = band; 519 if (((band & SoftApConfiguration.BAND_2GHZ) != 0) && !isSoftAp24GhzSupported(context)) { 520 availableBand &= ~SoftApConfiguration.BAND_2GHZ; 521 } 522 if (((band & SoftApConfiguration.BAND_5GHZ) != 0) && !isSoftAp5GhzSupported(context)) { 523 availableBand &= ~SoftApConfiguration.BAND_5GHZ; 524 } 525 if (((band & SoftApConfiguration.BAND_6GHZ) != 0) && !isSoftAp6GhzSupported(context)) { 526 availableBand &= ~SoftApConfiguration.BAND_6GHZ; 527 } 528 if (((band & SoftApConfiguration.BAND_60GHZ) != 0) && !isSoftAp60GhzSupported(context)) { 529 availableBand &= ~SoftApConfiguration.BAND_60GHZ; 530 } 531 return availableBand; 532 } 533 534 /** 535 * Update AP band and channel based on the provided country code and band. 536 * This will also set 537 * @param wifiNative reference to WifiNative 538 * @param coexManager reference to CoexManager 539 * @param resources the resources to use to get configured allowed channels. 540 * @param countryCode country code 541 * @param config configuration to update 542 * @return an integer result code 543 */ updateApChannelConfig(WifiNative wifiNative, @NonNull CoexManager coexManager, Resources resources, String countryCode, SoftApConfiguration.Builder configBuilder, SoftApConfiguration config, boolean acsEnabled)544 public static int updateApChannelConfig(WifiNative wifiNative, 545 @NonNull CoexManager coexManager, 546 Resources resources, 547 String countryCode, 548 SoftApConfiguration.Builder configBuilder, 549 SoftApConfiguration config, 550 boolean acsEnabled) { 551 /* Use default band and channel for device without HAL. */ 552 if (!wifiNative.isHalStarted()) { 553 configBuilder.setChannel(DEFAULT_AP_CHANNEL, DEFAULT_AP_BAND); 554 return SUCCESS; 555 } 556 557 /* Country code is mandatory for 5GHz band. */ 558 if (config.getBand() == SoftApConfiguration.BAND_5GHZ 559 && countryCode == null) { 560 Log.e(TAG, "5GHz band is not allowed without country code"); 561 return ERROR_GENERIC; 562 } 563 564 /* Select a channel if it is not specified and ACS is not enabled */ 565 if ((config.getChannel() == 0) && !acsEnabled) { 566 int freq = chooseApChannel(config.getBand(), wifiNative, coexManager, resources); 567 if (freq == -1) { 568 /* We're not able to get channel from wificond. */ 569 Log.e(TAG, "Failed to get available channel."); 570 return ERROR_NO_CHANNEL; 571 } 572 configBuilder.setChannel( 573 ScanResult.convertFrequencyMhzToChannelIfSupported(freq), 574 convertFrequencyToBand(freq)); 575 } 576 577 return SUCCESS; 578 } 579 580 /** 581 * Helper function for converting WifiConfiguration to SoftApConfiguration. 582 * 583 * Only Support None and WPA2 configuration conversion. 584 * Note that WifiConfiguration only Supports 2GHz, 5GHz, 2GHz+5GHz bands, 585 * so conversion is limited to these bands. 586 * 587 * @param wifiConfig the WifiConfiguration which need to convert. 588 * @return the SoftApConfiguration if wifiConfig is valid, null otherwise. 589 */ 590 @Nullable fromWifiConfiguration( @onNull WifiConfiguration wifiConfig)591 public static SoftApConfiguration fromWifiConfiguration( 592 @NonNull WifiConfiguration wifiConfig) { 593 SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); 594 try { 595 configBuilder.setSsid(wifiConfig.SSID); 596 if (wifiConfig.getAuthType() == WifiConfiguration.KeyMgmt.WPA2_PSK) { 597 configBuilder.setPassphrase(wifiConfig.preSharedKey, 598 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); 599 } 600 configBuilder.setHiddenSsid(wifiConfig.hiddenSSID); 601 602 int band; 603 switch (wifiConfig.apBand) { 604 case WifiConfiguration.AP_BAND_2GHZ: 605 band = SoftApConfiguration.BAND_2GHZ; 606 break; 607 case WifiConfiguration.AP_BAND_5GHZ: 608 band = SoftApConfiguration.BAND_5GHZ; 609 break; 610 case WifiConfiguration.AP_BAND_60GHZ: 611 band = SoftApConfiguration.BAND_60GHZ; 612 break; 613 default: 614 // WifiConfiguration.AP_BAND_ANY means only 2GHz and 5GHz bands 615 band = SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ; 616 break; 617 } 618 if (wifiConfig.apChannel == 0) { 619 configBuilder.setBand(band); 620 } else { 621 configBuilder.setChannel(wifiConfig.apChannel, band); 622 } 623 } catch (IllegalArgumentException iae) { 624 Log.e(TAG, "Invalid WifiConfiguration" + iae); 625 return null; 626 } catch (IllegalStateException ise) { 627 Log.e(TAG, "Invalid WifiConfiguration" + ise); 628 return null; 629 } 630 return configBuilder.build(); 631 } 632 633 /** 634 * Helper function to creating SoftApCapability instance with initial field from resource file. 635 * 636 * @param context the caller context used to get value from resource file. 637 * @return SoftApCapability which updated the feature support or not from resource. 638 */ 639 @NonNull updateCapabilityFromResource(@onNull Context context)640 public static SoftApCapability updateCapabilityFromResource(@NonNull Context context) { 641 long features = 0; 642 if (isAcsSupported(context)) { 643 Log.d(TAG, "Update Softap capability, add acs feature support"); 644 features |= SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD; 645 } 646 647 if (isClientForceDisconnectSupported(context)) { 648 Log.d(TAG, "Update Softap capability, add client control feature support"); 649 features |= SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT; 650 } 651 652 if (isWpa3SaeSupported(context)) { 653 Log.d(TAG, "Update Softap capability, add SAE feature support"); 654 features |= SoftApCapability.SOFTAP_FEATURE_WPA3_SAE; 655 } 656 657 if (isMacCustomizationSupported(context)) { 658 Log.d(TAG, "Update Softap capability, add MAC customization support"); 659 features |= SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION; 660 } 661 662 if (isSoftAp24GhzSupported(context)) { 663 Log.d(TAG, "Update Softap capability, add 2.4G support"); 664 features |= SoftApCapability.SOFTAP_FEATURE_BAND_24G_SUPPORTED; 665 } 666 667 if (isSoftAp5GhzSupported(context)) { 668 Log.d(TAG, "Update Softap capability, add 5G support"); 669 features |= SoftApCapability.SOFTAP_FEATURE_BAND_5G_SUPPORTED; 670 } 671 672 if (isSoftAp6GhzSupported(context)) { 673 Log.d(TAG, "Update Softap capability, add 6G support"); 674 features |= SoftApCapability.SOFTAP_FEATURE_BAND_6G_SUPPORTED; 675 } 676 677 if (isSoftAp60GhzSupported(context)) { 678 Log.d(TAG, "Update Softap capability, add 60G support"); 679 features |= SoftApCapability.SOFTAP_FEATURE_BAND_60G_SUPPORTED; 680 } 681 682 SoftApCapability capability = new SoftApCapability(features); 683 int hardwareSupportedMaxClient = context.getResources().getInteger( 684 R.integer.config_wifiHardwareSoftapMaxClientCount); 685 if (hardwareSupportedMaxClient > 0) { 686 Log.d(TAG, "Update Softap capability, max client = " + hardwareSupportedMaxClient); 687 capability.setMaxSupportedClients(hardwareSupportedMaxClient); 688 } 689 690 return capability; 691 } 692 693 /** 694 * Helper function to get device support AP MAC randomization or not. 695 * 696 * @param context the caller context used to get value from resource file. 697 * @return true if supported, false otherwise. 698 */ isApMacRandomizationSupported(@onNull Context context)699 public static boolean isApMacRandomizationSupported(@NonNull Context context) { 700 return context.getResources().getBoolean( 701 R.bool.config_wifi_ap_mac_randomization_supported); 702 } 703 704 /** 705 * Helper function to get HAL support bridged AP or not. 706 * 707 * @param context the caller context used to get value from resource file. 708 * @return true if supported, false otherwise. 709 */ isBridgedModeSupported(@onNull Context context)710 public static boolean isBridgedModeSupported(@NonNull Context context) { 711 return SdkLevel.isAtLeastS() && context.getResources().getBoolean( 712 R.bool.config_wifiBridgedSoftApSupported); 713 } 714 715 /** 716 * Helper function to get HAL support client force disconnect or not. 717 * 718 * @param context the caller context used to get value from resource file. 719 * @return true if supported, false otherwise. 720 */ isClientForceDisconnectSupported(@onNull Context context)721 public static boolean isClientForceDisconnectSupported(@NonNull Context context) { 722 return context.getResources().getBoolean( 723 R.bool.config_wifiSofapClientForceDisconnectSupported); 724 } 725 726 /** 727 * Helper function to get SAE support or not. 728 * 729 * @param context the caller context used to get value from resource file. 730 * @return true if supported, false otherwise. 731 */ isWpa3SaeSupported(@onNull Context context)732 public static boolean isWpa3SaeSupported(@NonNull Context context) { 733 return context.getResources().getBoolean( 734 R.bool.config_wifi_softap_sae_supported); 735 } 736 737 /** 738 * Helper function to get ACS support or not. 739 * 740 * @param context the caller context used to get value from resource file. 741 * @return true if supported, false otherwise. 742 */ isAcsSupported(@onNull Context context)743 public static boolean isAcsSupported(@NonNull Context context) { 744 return context.getResources().getBoolean( 745 R.bool.config_wifi_softap_acs_supported); 746 } 747 748 /** 749 * Helper function to get MAC Address customization or not. 750 * 751 * @param context the caller context used to get value from resource file. 752 * @return true if supported, false otherwise. 753 */ isMacCustomizationSupported(@onNull Context context)754 public static boolean isMacCustomizationSupported(@NonNull Context context) { 755 return context.getResources().getBoolean( 756 R.bool.config_wifiSoftapMacAddressCustomizationSupported); 757 } 758 759 /** 760 * Helper function to get whether or not 2.4G Soft AP support. 761 * 762 * @param context the caller context used to get value from resource file. 763 * @return true if supported, false otherwise. 764 */ isSoftAp24GhzSupported(@onNull Context context)765 public static boolean isSoftAp24GhzSupported(@NonNull Context context) { 766 return context.getResources().getBoolean(R.bool.config_wifi24ghzSupport) 767 && context.getResources().getBoolean( 768 R.bool.config_wifiSoftap24ghzSupported); 769 } 770 771 /** 772 * Helper function to get whether or not 5G Soft AP support. 773 * 774 * @param context the caller context used to get value from resource file. 775 * @return true if supported, false otherwise. 776 */ isSoftAp5GhzSupported(@onNull Context context)777 public static boolean isSoftAp5GhzSupported(@NonNull Context context) { 778 return context.getResources().getBoolean(R.bool.config_wifi5ghzSupport) 779 && context.getResources().getBoolean( 780 R.bool.config_wifiSoftap5ghzSupported); 781 } 782 783 /** 784 * Helper function to get whether or not 6G Soft AP support 785 * 786 * @param context the caller context used to get value from resource file. 787 * @return true if supported, false otherwise. 788 */ isSoftAp6GhzSupported(@onNull Context context)789 public static boolean isSoftAp6GhzSupported(@NonNull Context context) { 790 return context.getResources().getBoolean(R.bool.config_wifi6ghzSupport) 791 && context.getResources().getBoolean( 792 R.bool.config_wifiSoftap6ghzSupported); 793 } 794 795 /** 796 * Helper function to get whether or not 60G Soft AP support. 797 * 798 * @param context the caller context used to get value from resource file. 799 * @return true if supported, false otherwise. 800 */ isSoftAp60GhzSupported(@onNull Context context)801 public static boolean isSoftAp60GhzSupported(@NonNull Context context) { 802 return context.getResources().getBoolean(R.bool.config_wifi60ghzSupport) 803 && context.getResources().getBoolean( 804 R.bool.config_wifiSoftap60ghzSupported); 805 } 806 807 /** 808 * Helper function to get whether or not dynamic country code update is supported when Soft AP 809 * enabled. 810 * 811 * @param context the caller context used to get value from resource file. 812 * @return true if supported, false otherwise. 813 */ isSoftApDynamicCountryCodeSupported(@onNull Context context)814 public static boolean isSoftApDynamicCountryCodeSupported(@NonNull Context context) { 815 return context.getResources().getBoolean( 816 R.bool.config_wifiSoftApDynamicCountryCodeUpdateSupported); 817 } 818 819 /** 820 * Helper function for comparing two SoftApConfiguration. 821 * 822 * @param currentConfig the original configuration. 823 * @param newConfig the new configuration which plan to apply. 824 * @return true if the difference between the two configurations requires a restart to apply, 825 * false otherwise. 826 */ checkConfigurationChangeNeedToRestart( SoftApConfiguration currentConfig, SoftApConfiguration newConfig)827 public static boolean checkConfigurationChangeNeedToRestart( 828 SoftApConfiguration currentConfig, SoftApConfiguration newConfig) { 829 return !Objects.equals(currentConfig.getSsid(), newConfig.getSsid()) 830 || !Objects.equals(currentConfig.getBssid(), newConfig.getBssid()) 831 || currentConfig.getSecurityType() != newConfig.getSecurityType() 832 || !Objects.equals(currentConfig.getPassphrase(), newConfig.getPassphrase()) 833 || currentConfig.isHiddenSsid() != newConfig.isHiddenSsid() 834 || currentConfig.getBand() != newConfig.getBand() 835 || currentConfig.getChannel() != newConfig.getChannel() 836 || (SdkLevel.isAtLeastS() && !currentConfig.getChannels().toString() 837 .equals(newConfig.getChannels().toString())); 838 } 839 840 841 /** 842 * Helper function for checking all of the configuration are supported or not. 843 * 844 * @param config target configuration want to check. 845 * @param capability the capability which indicate feature support or not. 846 * @return true if supported, false otherwise. 847 */ checkSupportAllConfiguration(SoftApConfiguration config, SoftApCapability capability)848 public static boolean checkSupportAllConfiguration(SoftApConfiguration config, 849 SoftApCapability capability) { 850 if (!capability.areFeaturesSupported( 851 SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT) 852 && (config.getMaxNumberOfClients() != 0 || config.isClientControlByUserEnabled() 853 || config.getBlockedClientList().size() != 0)) { 854 Log.d(TAG, "Error, Client control requires HAL support"); 855 return false; 856 } 857 858 if (!capability.areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_WPA3_SAE) 859 && (config.getSecurityType() 860 == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION 861 || config.getSecurityType() == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE)) { 862 Log.d(TAG, "Error, SAE requires HAL support"); 863 return false; 864 } 865 866 // The bands length should always 1 in R. Adding SdkLevel.isAtLeastS for lint check only. 867 if (config.getBands().length > 1 && SdkLevel.isAtLeastS()) { 868 int[] bands = config.getBands(); 869 if ((bands[0] & SoftApConfiguration.BAND_6GHZ) != 0 870 || (bands[0] & SoftApConfiguration.BAND_60GHZ) != 0 871 || (bands[1] & SoftApConfiguration.BAND_6GHZ) != 0 872 || (bands[1] & SoftApConfiguration.BAND_60GHZ) != 0) { 873 Log.d(TAG, "Error, dual APs doesn't support on 6GHz and 60GHz"); 874 return false; 875 } 876 if (!capability.areFeaturesSupported(SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD) 877 && (config.getChannels().valueAt(0) == 0 878 || config.getChannels().valueAt(1) == 0)) { 879 Log.d(TAG, "Error, dual APs requires HAL ACS support when channel isn't specified"); 880 return false; 881 } 882 } 883 return true; 884 } 885 886 887 /** 888 * Check if need to provide freq range for ACS. 889 * 890 * @param band in SoftApConfiguration.BandType 891 * @return true when freq ranges is needed, otherwise false. 892 */ isSendFreqRangesNeeded(@andType int band, Context context)893 public static boolean isSendFreqRangesNeeded(@BandType int band, Context context) { 894 // Fist we check if one of the selected bands has restrictions in the overlay file. 895 // Note, 896 // - We store the config string here for future use, hence we need to check all bands. 897 // - If there is no OEM restriction, we store the full band 898 boolean retVal = false; 899 String channelList = ""; 900 if ((band & SoftApConfiguration.BAND_2GHZ) != 0) { 901 channelList = 902 context.getResources().getString(R.string.config_wifiSoftap2gChannelList); 903 if (!TextUtils.isEmpty(channelList)) { 904 retVal = true; 905 } 906 } 907 908 if ((band & SoftApConfiguration.BAND_5GHZ) != 0) { 909 channelList = 910 context.getResources().getString(R.string.config_wifiSoftap5gChannelList); 911 if (!TextUtils.isEmpty(channelList)) { 912 retVal = true; 913 } 914 } 915 916 if ((band & SoftApConfiguration.BAND_6GHZ) != 0) { 917 channelList = 918 context.getResources().getString(R.string.config_wifiSoftap6gChannelList); 919 if (!TextUtils.isEmpty(channelList)) { 920 retVal = true; 921 } 922 } 923 924 // If any of the selected band has restriction in the overlay file, we return true. 925 if (retVal) { 926 return true; 927 } 928 929 // Next, if only one of 5G or 6G is selected, then we need freqList to separate them 930 // Since there is no other way. 931 if (((band & SoftApConfiguration.BAND_5GHZ) != 0) 932 && ((band & SoftApConfiguration.BAND_6GHZ) == 0)) { 933 return true; 934 } 935 if (((band & SoftApConfiguration.BAND_5GHZ) == 0) 936 && ((band & SoftApConfiguration.BAND_6GHZ) != 0)) { 937 return true; 938 } 939 940 // In all other cases, we don't need to set the freqList 941 return false; 942 } 943 944 /** 945 * Deep copy for object Map<String, SoftApInfo> 946 */ deepCopyForSoftApInfoMap( Map<String, SoftApInfo> originalMap)947 public static Map<String, SoftApInfo> deepCopyForSoftApInfoMap( 948 Map<String, SoftApInfo> originalMap) { 949 if (originalMap == null) { 950 return null; 951 } 952 Map<String, SoftApInfo> deepCopyMap = new HashMap<String, SoftApInfo>(); 953 for (Map.Entry<String, SoftApInfo> entry: originalMap.entrySet()) { 954 deepCopyMap.put(entry.getKey(), new SoftApInfo(entry.getValue())); 955 } 956 return deepCopyMap; 957 } 958 959 /** 960 * Deep copy for object Map<String, List<WifiClient>> 961 */ deepCopyForWifiClientListMap( Map<String, List<WifiClient>> originalMap)962 public static Map<String, List<WifiClient>> deepCopyForWifiClientListMap( 963 Map<String, List<WifiClient>> originalMap) { 964 if (originalMap == null) { 965 return null; 966 } 967 Map<String, List<WifiClient>> deepCopyMap = new HashMap<String, List<WifiClient>>(); 968 for (Map.Entry<String, List<WifiClient>> entry: originalMap.entrySet()) { 969 List<WifiClient> clients = new ArrayList<>(); 970 for (WifiClient client : entry.getValue()) { 971 clients.add(new WifiClient(client.getMacAddress(), 972 client.getApInstanceIdentifier())); 973 } 974 deepCopyMap.put(entry.getKey(), clients); 975 } 976 return deepCopyMap; 977 } 978 } 979