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