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