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