• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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