• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.content.IntentFilter;
22 import android.net.MacAddress;
23 import android.net.wifi.SoftApConfiguration;
24 import android.net.wifi.SoftApConfiguration.BandType;
25 import android.os.Handler;
26 import android.os.Process;
27 import android.text.TextUtils;
28 import android.util.Log;
29 import android.util.SparseIntArray;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.modules.utils.build.SdkLevel;
33 import com.android.net.module.util.MacAddressUtils;
34 import com.android.server.wifi.util.ApConfigUtil;
35 import com.android.wifi.resources.R;
36 
37 import java.nio.charset.CharsetEncoder;
38 import java.nio.charset.StandardCharsets;
39 import java.security.SecureRandom;
40 import java.util.ArrayList;
41 import java.util.Objects;
42 import java.util.Random;
43 
44 import javax.annotation.Nullable;
45 
46 /**
47  * Provides API for reading/writing soft access point configuration.
48  */
49 public class WifiApConfigStore {
50 
51     // Intent when user has interacted with the softap settings change notification
52     public static final String ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT =
53             "com.android.server.wifi.WifiApConfigStoreUtil.HOTSPOT_CONFIG_USER_TAPPED_CONTENT";
54 
55     private static final String TAG = "WifiApConfigStore";
56 
57     private static final int RAND_SSID_INT_MIN = 1000;
58     private static final int RAND_SSID_INT_MAX = 9999;
59 
60     @VisibleForTesting
61     static final int SSID_MIN_LEN = 1;
62     @VisibleForTesting
63     static final int SSID_MAX_LEN = 32;
64     @VisibleForTesting
65     static final int PSK_MIN_LEN = 8;
66     @VisibleForTesting
67     static final int PSK_MAX_LEN = 63;
68 
69     private SoftApConfiguration mPersistentWifiApConfig = null;
70 
71     private final Context mContext;
72     private final Handler mHandler;
73     private final WifiMetrics mWifiMetrics;
74     private final BackupManagerProxy mBackupManagerProxy;
75     private final MacAddressUtil mMacAddressUtil;
76     private final WifiConfigManager mWifiConfigManager;
77     private final ActiveModeWarden mActiveModeWarden;
78     private boolean mHasNewDataToSerialize = false;
79     private boolean mForceApChannel = false;
80     private int mForcedApBand;
81     private int mForcedApChannel;
82 
83     /**
84      * Module to interact with the wifi config store.
85      */
86     private class SoftApStoreDataSource implements SoftApStoreData.DataSource {
87 
toSerialize()88         public SoftApConfiguration toSerialize() {
89             mHasNewDataToSerialize = false;
90             return mPersistentWifiApConfig;
91         }
92 
fromDeserialized(SoftApConfiguration config)93         public void fromDeserialized(SoftApConfiguration config) {
94             mPersistentWifiApConfig = new SoftApConfiguration.Builder(config).build();
95         }
96 
reset()97         public void reset() {
98             mPersistentWifiApConfig = null;
99         }
100 
hasNewDataToSerialize()101         public boolean hasNewDataToSerialize() {
102             return mHasNewDataToSerialize;
103         }
104     }
105 
WifiApConfigStore(Context context, WifiInjector wifiInjector, Handler handler, BackupManagerProxy backupManagerProxy, WifiConfigStore wifiConfigStore, WifiConfigManager wifiConfigManager, ActiveModeWarden activeModeWarden, WifiMetrics wifiMetrics)106     WifiApConfigStore(Context context,
107             WifiInjector wifiInjector,
108             Handler handler,
109             BackupManagerProxy backupManagerProxy,
110             WifiConfigStore wifiConfigStore,
111             WifiConfigManager wifiConfigManager,
112             ActiveModeWarden activeModeWarden,
113             WifiMetrics wifiMetrics) {
114         mContext = context;
115         mHandler = handler;
116         mBackupManagerProxy = backupManagerProxy;
117         mWifiConfigManager = wifiConfigManager;
118         mActiveModeWarden = activeModeWarden;
119         mWifiMetrics = wifiMetrics;
120 
121         // Register store data listener
122         wifiConfigStore.registerStoreData(
123                 wifiInjector.makeSoftApStoreData(new SoftApStoreDataSource()));
124 
125         IntentFilter filter = new IntentFilter();
126         filter.addAction(ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT);
127         mMacAddressUtil = wifiInjector.getMacAddressUtil();
128     }
129 
130     /**
131      * Return the current soft access point configuration.
132      */
getApConfiguration()133     public synchronized SoftApConfiguration getApConfiguration() {
134         if (mPersistentWifiApConfig == null) {
135             /* Use default configuration. */
136             Log.d(TAG, "Fallback to use default AP configuration");
137             persistConfigAndTriggerBackupManagerProxy(getDefaultApConfiguration());
138         }
139         SoftApConfiguration sanitizedPersistentconfig =
140                 sanitizePersistentApConfig(mPersistentWifiApConfig);
141         if (!Objects.equals(mPersistentWifiApConfig, sanitizedPersistentconfig)) {
142             Log.d(TAG, "persisted config was converted, need to resave it");
143             persistConfigAndTriggerBackupManagerProxy(sanitizedPersistentconfig);
144         }
145         if (mForceApChannel) {
146             Log.d(TAG, "getApConfiguration: Band force to " + mForcedApBand
147                     + ", and channel force to " + mForcedApChannel);
148             return mForcedApChannel == 0
149                     ? new SoftApConfiguration.Builder(mPersistentWifiApConfig)
150                             .setBand(mForcedApBand).build()
151                     : new SoftApConfiguration.Builder(mPersistentWifiApConfig)
152                             .setChannel(mForcedApChannel, mForcedApBand).build();
153         }
154         return mPersistentWifiApConfig;
155     }
156 
157     /**
158      * Update the current soft access point configuration.
159      * Restore to default AP configuration if null is provided.
160      * This can be invoked under context of binder threads (WifiManager.setWifiApConfiguration)
161      * and the main Wifi thread (CMD_START_AP).
162      */
setApConfiguration(SoftApConfiguration config)163     public synchronized void setApConfiguration(SoftApConfiguration config) {
164         if (config == null) {
165             config = getDefaultApConfiguration();
166         } else {
167             config = sanitizePersistentApConfig(config);
168         }
169         persistConfigAndTriggerBackupManagerProxy(
170                 new SoftApConfiguration.Builder(config).setUserConfiguration(true).build());
171     }
172 
173     /**
174      * Returns SoftApConfiguration in which some parameters might be upgrade to supported default
175      * configuration.
176      */
upgradeSoftApConfiguration(@onNull SoftApConfiguration config)177     public SoftApConfiguration upgradeSoftApConfiguration(@NonNull SoftApConfiguration config) {
178         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
179         if (SdkLevel.isAtLeastS() && ApConfigUtil.isBridgedModeSupported(mContext)
180                 && config.getBands().length == 1) {
181             int[] dual_bands = new int[] {
182                     SoftApConfiguration.BAND_2GHZ,
183                     SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
184             if (SdkLevel.isAtLeastS()) {
185                 configBuilder.setBands(dual_bands);
186             }
187             Log.i(TAG, "Device support bridged AP, upgrade band setting to bridged configuration");
188         }
189         return configBuilder.build();
190     }
191 
192     /**
193      * Returns SoftApConfiguration in which some parameters might be reset to supported default
194      * config since it depends on UI or HW.
195      *
196      * MaxNumberOfClients and isClientControlByUserEnabled will need HAL support client force
197      * disconnect, and Band setting (5g/6g) need HW support.
198      *
199      * HiddenSsid, Channel, ShutdownTimeoutMillis and AutoShutdownEnabled are features
200      * which need UI(Setting) support.
201      *
202      * SAE/SAE-Transition need hardware support, reset to secured WPA2 security type when device
203      * doesn't support it.
204      *
205      * Check band(s) setting to make sure all of the band(s) are supported.
206      * - If previous bands configuration is bridged mode. Reset to 2.4G when device doesn't support
207      *   it.
208      */
resetToDefaultForUnsupportedConfig( @onNull SoftApConfiguration config)209     public SoftApConfiguration resetToDefaultForUnsupportedConfig(
210             @NonNull SoftApConfiguration config) {
211         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
212         if ((!ApConfigUtil.isClientForceDisconnectSupported(mContext)
213                 || mContext.getResources().getBoolean(
214                 R.bool.config_wifiSoftapResetUserControlConfig))
215                 && (config.isClientControlByUserEnabled()
216                 || config.getBlockedClientList().size() != 0)) {
217             configBuilder.setClientControlByUserEnabled(false);
218             configBuilder.setBlockedClientList(new ArrayList<>());
219             Log.i(TAG, "Reset ClientControlByUser to false due to device doesn't support");
220         }
221 
222         if ((!ApConfigUtil.isClientForceDisconnectSupported(mContext)
223                 || mContext.getResources().getBoolean(
224                 R.bool.config_wifiSoftapResetMaxClientSettingConfig))
225                 && config.getMaxNumberOfClients() != 0) {
226             configBuilder.setMaxNumberOfClients(0);
227             Log.i(TAG, "Reset MaxNumberOfClients to 0 due to device doesn't support");
228         }
229 
230         if (!ApConfigUtil.isWpa3SaeSupported(mContext) && (config.getSecurityType()
231                 == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE
232                 || config.getSecurityType()
233                 == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)) {
234             configBuilder.setPassphrase(generatePassword(),
235                     SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
236             Log.i(TAG, "Device doesn't support WPA3-SAE, reset config to WPA2");
237         }
238 
239         if (mContext.getResources().getBoolean(R.bool.config_wifiSoftapResetChannelConfig)
240                 && config.getChannel() != 0) {
241             // The device might not support customize channel or forced channel might not
242             // work in some countries. Need to reset it.
243             configBuilder.setBand(ApConfigUtil.append24GToBandIf24GSupported(
244                     config.getBand(), mContext));
245             Log.i(TAG, "Reset SAP channel configuration");
246         }
247 
248         if (SdkLevel.isAtLeastS() && config.getBands().length > 1) {
249             if (!ApConfigUtil.isBridgedModeSupported(mContext)
250                     || !isBandsSupported(config.getBands(), mContext)) {
251                 int newSingleApBand = 0;
252                 for (int targetBand : config.getBands()) {
253                     int availableBand = ApConfigUtil.removeUnsupportedBands(
254                             mContext, targetBand);
255                     newSingleApBand |= availableBand;
256                 }
257                 newSingleApBand = ApConfigUtil.append24GToBandIf24GSupported(
258                         newSingleApBand, mContext);
259                 configBuilder.setBand(newSingleApBand);
260                 Log.i(TAG, "An unsupported band setting for the bridged mode, force to "
261                         + newSingleApBand);
262             }
263         } else {
264             // Single band case, check and remove unsupported band.
265             int newBand = ApConfigUtil.removeUnsupportedBands(mContext, config.getBand());
266             if (newBand != config.getBand()) {
267                 newBand = ApConfigUtil.append24GToBandIf24GSupported(newBand, mContext);
268                 Log.i(TAG, "Reset band from " + config.getBand() + " to "
269                         + newBand);
270                 configBuilder.setBand(newBand);
271             }
272         }
273 
274         if (mContext.getResources().getBoolean(R.bool.config_wifiSoftapResetHiddenConfig)
275                 && config.isHiddenSsid()) {
276             configBuilder.setHiddenSsid(false);
277             Log.i(TAG, "Reset SAP Hidden Network configuration");
278         }
279 
280         if (mContext.getResources().getBoolean(
281                 R.bool.config_wifiSoftapResetAutoShutdownTimerConfig)
282                 && config.getShutdownTimeoutMillis() != 0) {
283             configBuilder.setShutdownTimeoutMillis(0);
284             Log.i(TAG, "Reset SAP auto shutdown configuration");
285         }
286 
287         if (!ApConfigUtil.isApMacRandomizationSupported(mContext)) {
288             if (SdkLevel.isAtLeastS()) {
289                 configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
290                 Log.i(TAG, "Force set SAP MAC randomization to NONE when not supported");
291             }
292         }
293 
294         mWifiMetrics.noteSoftApConfigReset(config, configBuilder.build());
295         return configBuilder.build();
296     }
297 
sanitizePersistentApConfig(SoftApConfiguration config)298     private SoftApConfiguration sanitizePersistentApConfig(SoftApConfiguration config) {
299         SoftApConfiguration.Builder convertedConfigBuilder =
300                 new SoftApConfiguration.Builder(config);
301         int[] bands = config.getBands();
302         // The bands length should always 1 in R. Adding SdkLevel.isAtLeastS for lint check only.
303         if (bands.length > 1 && SdkLevel.isAtLeastS()) {
304             // Consider 2.4G instance may be shutdown, i.e. only left 5G instance. If the 5G
305             // configuration is 5G band only, it will cause that driver can't switch channel from
306             // 5G to 2.4G when coexistence happene. Always append 2.4G into band configuration to
307             // allow driver handle coexistence case after 2.4G instance shutdown.
308             SparseIntArray newChannels = new SparseIntArray();
309             for (int i = 0; i < bands.length; i++) {
310                 int channel = config.getChannels().valueAt(i);
311                 if (channel == 0 && (bands[i] & SoftApConfiguration.BAND_2GHZ) == 0
312                         && ApConfigUtil.isBandSupported(bands[i], mContext)) {
313                     newChannels.put(ApConfigUtil.append24GToBandIf24GSupported(bands[i], mContext),
314                             0);
315                 } else {
316                     newChannels.put(bands[i], channel);
317                 }
318             }
319             convertedConfigBuilder.setChannels(newChannels);
320         } else if (config.getChannel() == 0 && (bands[0] & SoftApConfiguration.BAND_2GHZ) == 0) {
321             // some countries are unable to support 5GHz only operation, always allow for 2GHz when
322             // config doesn't force channel
323             if (ApConfigUtil.isBandSupported(bands[0], mContext)) {
324                 Log.i(TAG, "Supplied ap config band without 2.4G, add allowing for 2.4GHz");
325                 convertedConfigBuilder.setBand(
326                         ApConfigUtil.append24GToBandIf24GSupported(bands[0], mContext));
327             }
328         }
329         return convertedConfigBuilder.build();
330     }
331 
persistConfigAndTriggerBackupManagerProxy(SoftApConfiguration config)332     private void persistConfigAndTriggerBackupManagerProxy(SoftApConfiguration config) {
333         mPersistentWifiApConfig = config;
334         mHasNewDataToSerialize = true;
335         mWifiConfigManager.saveToStore(true);
336         mBackupManagerProxy.notifyDataChanged();
337     }
338 
339     /**
340      * Generate a default WPA3 SAE transition (if supported) or WPA2 based
341      * configuration with a random password.
342      * We are changing the Wifi Ap configuration storage from secure settings to a
343      * flat file accessible only by the system. A WPA2 based default configuration
344      * will keep the device secure after the update.
345      */
getDefaultApConfiguration()346     private SoftApConfiguration getDefaultApConfiguration() {
347         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
348         configBuilder.setBand(generateDefaultBand(mContext));
349         configBuilder.setSsid(mContext.getResources().getString(
350                 R.string.wifi_tether_configure_ssid_default) + "_" + getRandomIntForDefaultSsid());
351         if (ApConfigUtil.isWpa3SaeSupported(mContext)) {
352             configBuilder.setPassphrase(generatePassword(),
353                     SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
354         } else {
355             configBuilder.setPassphrase(generatePassword(),
356                     SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
357         }
358 
359         // It is new overlay configuration, it should always false in R. Add SdkLevel.isAtLeastS for
360         // lint check
361         if (ApConfigUtil.isBridgedModeSupported(mContext)) {
362             if (SdkLevel.isAtLeastS()) {
363                 int[] dual_bands = new int[] {
364                         SoftApConfiguration.BAND_2GHZ,
365                         SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
366                 configBuilder.setBands(dual_bands);
367             }
368         }
369 
370         // Update default MAC randomization setting to NONE when feature doesn't support it.
371         if (!ApConfigUtil.isApMacRandomizationSupported(mContext)) {
372             if (SdkLevel.isAtLeastS()) {
373                 configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
374             }
375         }
376 
377         configBuilder.setUserConfiguration(false);
378         return configBuilder.build();
379     }
380 
getRandomIntForDefaultSsid()381     private static int getRandomIntForDefaultSsid() {
382         Random random = new Random();
383         return random.nextInt((RAND_SSID_INT_MAX - RAND_SSID_INT_MIN) + 1) + RAND_SSID_INT_MIN;
384     }
385 
generateLohsSsid(Context context)386     private static String generateLohsSsid(Context context) {
387         return context.getResources().getString(
388                 R.string.wifi_localhotspot_configure_ssid_default) + "_"
389                 + getRandomIntForDefaultSsid();
390     }
391 
392     /**
393      * Generate a temporary WPA2 based configuration for use by the local only hotspot.
394      * This config is not persisted and will not be stored by the WifiApConfigStore.
395      */
generateLocalOnlyHotspotConfig(Context context, int apBand, @Nullable SoftApConfiguration customConfig)396     public SoftApConfiguration generateLocalOnlyHotspotConfig(Context context, int apBand,
397             @Nullable SoftApConfiguration customConfig) {
398         SoftApConfiguration.Builder configBuilder;
399         if (customConfig != null) {
400             configBuilder = new SoftApConfiguration.Builder(customConfig);
401         } else {
402             configBuilder = new SoftApConfiguration.Builder();
403             // Default to disable the auto shutdown
404             configBuilder.setAutoShutdownEnabled(false);
405         }
406 
407         configBuilder.setBand(apBand);
408 
409         if (customConfig == null || customConfig.getSsid() == null) {
410             configBuilder.setSsid(generateLohsSsid(context));
411         }
412         if (customConfig == null) {
413             if (ApConfigUtil.isWpa3SaeSupported(context)) {
414                 configBuilder.setPassphrase(generatePassword(),
415                         SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
416             } else {
417                 configBuilder.setPassphrase(generatePassword(),
418                         SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
419             }
420         }
421 
422         // Update default MAC randomization setting to NONE when feature doesn't support it or
423         // It was disabled in tethered mode.
424         if (!ApConfigUtil.isApMacRandomizationSupported(context) || (mPersistentWifiApConfig != null
425                 && mPersistentWifiApConfig.getMacRandomizationSettingInternal()
426                 == SoftApConfiguration.RANDOMIZATION_NONE)) {
427             if (SdkLevel.isAtLeastS()) {
428                 configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
429             }
430         }
431 
432         return configBuilder.build();
433     }
434 
435     /**
436      * @return a copy of the given SoftApConfig with the BSSID randomized, unless a custom BSSID is
437      * already set.
438      */
randomizeBssidIfUnset(Context context, SoftApConfiguration config)439     SoftApConfiguration randomizeBssidIfUnset(Context context, SoftApConfiguration config) {
440         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
441         if (config.getBssid() == null && ApConfigUtil.isApMacRandomizationSupported(mContext)) {
442             if (config.getMacRandomizationSettingInternal()
443                     == SoftApConfiguration.RANDOMIZATION_NONE) {
444                 return configBuilder.build();
445             }
446 
447             MacAddress macAddress = mMacAddressUtil.calculatePersistentMac(config.getSsid(),
448                     mMacAddressUtil.obtainMacRandHashFunctionForSap(Process.WIFI_UID));
449             if (macAddress == null) {
450                 Log.e(TAG, "Failed to calculate MAC from SSID. "
451                         + "Generating new random MAC instead.");
452                 macAddress = MacAddressUtils.createRandomUnicastAddress();
453             }
454             configBuilder.setBssid(macAddress);
455         }
456         return configBuilder.build();
457     }
458 
459     /**
460      * Verify provided SSID for existence, length and conversion to bytes
461      *
462      * @param ssid String ssid name
463      * @return boolean indicating ssid met requirements
464      */
validateApConfigSsid(String ssid)465     private static boolean validateApConfigSsid(String ssid) {
466         if (TextUtils.isEmpty(ssid)) {
467             Log.d(TAG, "SSID for softap configuration must be set.");
468             return false;
469         }
470 
471         try {
472             byte[] ssid_bytes = ssid.getBytes(StandardCharsets.UTF_8);
473 
474             if (ssid_bytes.length < SSID_MIN_LEN || ssid_bytes.length > SSID_MAX_LEN) {
475                 Log.d(TAG, "softap SSID is defined as UTF-8 and it must be at least "
476                         + SSID_MIN_LEN + " byte and not more than " + SSID_MAX_LEN + " bytes");
477                 return false;
478             }
479         } catch (IllegalArgumentException e) {
480             Log.e(TAG, "softap config SSID verification failed: malformed string " + ssid);
481             return false;
482         }
483         return true;
484     }
485 
486     /**
487      * Verify provided preSharedKey in ap config for WPA2_PSK network meets requirements.
488      */
validateApConfigPreSharedKey(String preSharedKey)489     private static boolean validateApConfigPreSharedKey(String preSharedKey) {
490         if (preSharedKey.length() < PSK_MIN_LEN || preSharedKey.length() > PSK_MAX_LEN) {
491             Log.d(TAG, "softap network password string size must be at least " + PSK_MIN_LEN
492                     + " and no more than " + PSK_MAX_LEN);
493             return false;
494         }
495 
496         try {
497             preSharedKey.getBytes(StandardCharsets.UTF_8);
498         } catch (IllegalArgumentException e) {
499             Log.e(TAG, "softap network password verification failed: malformed string");
500             return false;
501         }
502         return true;
503     }
504 
505     /**
506      * Validate a SoftApConfiguration is properly configured for use by SoftApManager.
507      *
508      * This method checks the length of the SSID and for consistency between security settings (if
509      * it requires a password, was one provided?).
510      *
511      * @param apConfig {@link SoftApConfiguration} to use for softap mode
512      * @param isPrivileged indicate the caller can pass some fields check or not
513      * @return boolean true if the provided config meets the minimum set of details, false
514      * otherwise.
515      */
validateApWifiConfiguration(@onNull SoftApConfiguration apConfig, boolean isPrivileged, Context context)516     static boolean validateApWifiConfiguration(@NonNull SoftApConfiguration apConfig,
517             boolean isPrivileged, Context context) {
518         // first check the SSID
519         if (!validateApConfigSsid(apConfig.getSsid())) {
520             // failed SSID verificiation checks
521             return false;
522         }
523 
524         // BSSID can be set if caller own permission:android.Manifest.permission.NETWORK_SETTINGS.
525         if (apConfig.getBssid() != null && !isPrivileged) {
526             Log.e(TAG, "Config BSSID needs NETWORK_SETTINGS permission");
527             return false;
528         }
529 
530         String preSharedKey = apConfig.getPassphrase();
531         boolean hasPreSharedKey = !TextUtils.isEmpty(preSharedKey);
532         int authType;
533 
534         try {
535             authType = apConfig.getSecurityType();
536         } catch (IllegalStateException e) {
537             Log.d(TAG, "Unable to get AuthType for softap config: " + e.getMessage());
538             return false;
539         }
540 
541         if (authType == SoftApConfiguration.SECURITY_TYPE_OPEN) {
542             // open networks should not have a password
543             if (hasPreSharedKey) {
544                 Log.d(TAG, "open softap network should not have a password");
545                 return false;
546             }
547         } else if (authType == SoftApConfiguration.SECURITY_TYPE_WPA2_PSK
548                 || authType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION
549                 || authType == SoftApConfiguration.SECURITY_TYPE_WPA3_SAE) {
550             // this is a config that should have a password - check that first
551             if (!hasPreSharedKey) {
552                 Log.d(TAG, "softap network password must be set");
553                 return false;
554             }
555 
556             if (context.getResources().getBoolean(
557                     R.bool.config_wifiSoftapPassphraseAsciiEncodableCheck)) {
558                 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
559                 if (!asciiEncoder.canEncode(preSharedKey)) {
560                     Log.d(TAG, "passphrase not ASCII encodable");
561                     return false;
562                 }
563             }
564 
565             if (authType != SoftApConfiguration.SECURITY_TYPE_WPA3_SAE
566                     && !validateApConfigPreSharedKey(preSharedKey)) {
567                 // failed preSharedKey checks for WPA2 and WPA3 SAE Transition mode.
568                 return false;
569             }
570         } else {
571             // this is not a supported security type
572             Log.d(TAG, "softap configs must either be open or WPA2 PSK networks");
573             return false;
574         }
575 
576         if (SdkLevel.isAtLeastS()) {
577             if (!isBandsSupported(apConfig.getBands(), context)) {
578                 return false;
579             }
580         } else {
581             if (!ApConfigUtil.isBandSupported(apConfig.getBand(), context)) {
582                 return false;
583             }
584         }
585 
586         return true;
587     }
588 
generatePassword()589     private static String generatePassword() {
590         // Characters that will be used for password generation. Some characters commonly known to
591         // be confusing like 0 and O excluded from this list.
592         final String allowed = "23456789abcdefghijkmnpqrstuvwxyz";
593         final int passLength = 15;
594 
595         StringBuilder sb = new StringBuilder(passLength);
596         SecureRandom random = new SecureRandom();
597         for (int i = 0; i < passLength; i++) {
598             sb.append(allowed.charAt(random.nextInt(allowed.length())));
599         }
600         return sb.toString();
601     }
602 
603     /**
604      * Generate default band base on supported band configuration.
605      *
606      * @param context The caller context used to get value from resource file.
607      * @return A band which will be used for a default band in default configuration.
608      */
generateDefaultBand(Context context)609     public static @BandType int generateDefaultBand(Context context) {
610         for (int band : SoftApConfiguration.BAND_TYPES) {
611             if (ApConfigUtil.isBandSupported(band, context)) {
612                 return band;
613             }
614         }
615         Log.e(TAG, "Invalid overlay configuration! No any band supported on SoftAp");
616         return SoftApConfiguration.BAND_2GHZ;
617     }
618 
isBandsSupported(@onNull int[] apBands, Context context)619     private static boolean isBandsSupported(@NonNull int[] apBands, Context context) {
620         for (int band : apBands) {
621             if (!ApConfigUtil.isBandSupported(band, context)) {
622                 return false;
623             }
624         }
625         return true;
626     }
627 
628     /**
629      * Enable force-soft-AP-channel mode which takes effect when soft AP starts next time
630      *
631      * @param forcedApBand The forced band.
632      * @param forcedApChannel The forced IEEE channel number or 0 when forced AP band only.
633      */
enableForceSoftApBandOrChannel(@andType int forcedApBand, int forcedApChannel)634     public void enableForceSoftApBandOrChannel(@BandType int forcedApBand, int forcedApChannel) {
635         mForceApChannel = true;
636         mForcedApChannel = forcedApChannel;
637         mForcedApBand = forcedApBand;
638     }
639 
640     /**
641      * Disable force-soft-AP-channel mode which take effect when soft AP starts next time
642      */
disableForceSoftApBandOrChannel()643     public void disableForceSoftApBandOrChannel() {
644         mForceApChannel = false;
645     }
646 }
647