• 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 static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA2_PSK;
20 import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION;
21 import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA3_SAE;
22 import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION;
23 
24 import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_STATIC_CHIP_INFO;
25 
26 import android.annotation.NonNull;
27 import android.app.compat.CompatChanges;
28 import android.content.Context;
29 import android.content.IntentFilter;
30 import android.content.pm.PackageManager;
31 import android.net.MacAddress;
32 import android.net.wifi.SoftApCapability;
33 import android.net.wifi.SoftApConfiguration;
34 import android.net.wifi.SoftApConfiguration.BandType;
35 import android.net.wifi.WifiContext;
36 import android.net.wifi.WifiSsid;
37 import android.net.wifi.util.WifiResourceCache;
38 import android.os.Handler;
39 import android.os.Process;
40 import android.text.TextUtils;
41 import android.util.Log;
42 import android.util.SparseIntArray;
43 
44 import com.android.internal.annotations.VisibleForTesting;
45 import com.android.modules.utils.build.SdkLevel;
46 import com.android.net.module.util.MacAddressUtils;
47 import com.android.server.wifi.util.ApConfigUtil;
48 import com.android.wifi.resources.R;
49 
50 import java.nio.charset.CharsetEncoder;
51 import java.nio.charset.StandardCharsets;
52 import java.security.SecureRandom;
53 import java.util.ArrayList;
54 import java.util.Objects;
55 import java.util.Random;
56 
57 import javax.annotation.Nullable;
58 
59 /**
60  * Provides API for reading/writing soft access point configuration.
61  */
62 public class WifiApConfigStore {
63 
64     // Intent when user has interacted with the softap settings change notification
65     public static final String ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT =
66             "com.android.server.wifi.WifiApConfigStoreUtil.HOTSPOT_CONFIG_USER_TAPPED_CONTENT";
67 
68     private static final String TAG = "WifiApConfigStore";
69 
70     private static final int RAND_SSID_INT_MIN = 1000;
71     private static final int RAND_SSID_INT_MAX = 9999;
72 
73     @VisibleForTesting
74     static final int SAE_ASCII_MIN_LEN = 1;
75     @VisibleForTesting
76     static final int PSK_ASCII_MIN_LEN = 8;
77     @VisibleForTesting
78     static final int PSK_SAE_ASCII_MAX_LEN = 63;
79 
80     // Should only be accessed via synchronized methods.
81     private SoftApConfiguration mPersistentWifiApConfig = null;
82     private String mLastConfiguredPassphrase = null;
83 
84     private final WifiContext mContext;
85     private final Handler mHandler;
86     private final WifiMetrics mWifiMetrics;
87     private final BackupManagerProxy mBackupManagerProxy;
88     private final MacAddressUtil mMacAddressUtil;
89     private final WifiConfigManager mWifiConfigManager;
90     private final ActiveModeWarden mActiveModeWarden;
91     private final WifiNative mWifiNative;
92     private final HalDeviceManager mHalDeviceManager;
93     private final WifiSettingsConfigStore mWifiSettingsConfigStore;
94     private boolean mHasNewDataToSerialize = false;
95     private boolean mForceApChannel = false;
96     private int mForcedApBand;
97     private int mForcedApChannel;
98     private int mForcedApMaximumChannelBandWidth;
99     private final boolean mIsAutoAppendLowerBandEnabled;
100     private final WifiResourceCache mResourceCache;
101 
102     /**
103      * Module to interact with the wifi config store.
104      */
105     private class SoftApStoreDataSource implements SoftApStoreData.DataSource {
106 
toSerialize()107         public SoftApConfiguration toSerialize() {
108             mHasNewDataToSerialize = false;
109             return mPersistentWifiApConfig;
110         }
111 
fromDeserialized(SoftApConfiguration config)112         public void fromDeserialized(SoftApConfiguration config) {
113             if (config.getPersistentRandomizedMacAddress() == null) {
114                 config = updatePersistentRandomizedMacAddress(config);
115             }
116             mPersistentWifiApConfig = new SoftApConfiguration.Builder(config).build();
117             if (!TextUtils.isEmpty(mPersistentWifiApConfig.getPassphrase())) {
118                 mLastConfiguredPassphrase = mPersistentWifiApConfig.getPassphrase();
119             }
120         }
121 
reset()122         public void reset() {
123             mPersistentWifiApConfig = null;
124         }
125 
hasNewDataToSerialize()126         public boolean hasNewDataToSerialize() {
127             return mHasNewDataToSerialize;
128         }
129     }
130 
WifiApConfigStore(WifiContext context, WifiInjector wifiInjector, Handler handler, BackupManagerProxy backupManagerProxy, WifiConfigStore wifiConfigStore, WifiConfigManager wifiConfigManager, ActiveModeWarden activeModeWarden, WifiMetrics wifiMetrics)131     WifiApConfigStore(WifiContext context,
132             WifiInjector wifiInjector,
133             Handler handler,
134             BackupManagerProxy backupManagerProxy,
135             WifiConfigStore wifiConfigStore,
136             WifiConfigManager wifiConfigManager,
137             ActiveModeWarden activeModeWarden,
138             WifiMetrics wifiMetrics) {
139         mContext = context;
140         mHandler = handler;
141         mBackupManagerProxy = backupManagerProxy;
142         mWifiConfigManager = wifiConfigManager;
143         mActiveModeWarden = activeModeWarden;
144         mWifiMetrics = wifiMetrics;
145         mWifiNative = wifiInjector.getWifiNative();
146         // Register store data listener
147         wifiConfigStore.registerStoreData(
148                 wifiInjector.makeSoftApStoreData(new SoftApStoreDataSource()));
149 
150         IntentFilter filter = new IntentFilter();
151         filter.addAction(ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT);
152         mMacAddressUtil = wifiInjector.getMacAddressUtil();
153         mResourceCache = context.getResourceCache();
154         mIsAutoAppendLowerBandEnabled = mResourceCache.getBoolean(
155                 R.bool.config_wifiSoftapAutoAppendLowerBandsToBandConfigurationEnabled);
156         mHalDeviceManager = wifiInjector.getHalDeviceManager();
157         mWifiSettingsConfigStore = wifiInjector.getSettingsConfigStore();
158         mWifiSettingsConfigStore.registerChangeListener(WIFI_STATIC_CHIP_INFO,
159                 (key, value) -> {
160                     if (mPersistentWifiApConfig != null
161                             && mHalDeviceManager.isConcurrencyComboLoadedFromDriver()) {
162                         Log.i(TAG, "Chip capability is updated, check config");
163                         SoftApConfiguration.Builder configBuilder =
164                                 new SoftApConfiguration.Builder(mPersistentWifiApConfig);
165                         if (SdkLevel.isAtLeastS()
166                                 && mPersistentWifiApConfig.getBands().length > 1) {
167                             // Current band setting is dual band, check if device supports it.
168                             if (!ApConfigUtil.isBridgedModeSupported(mContext, mWifiNative)) {
169                                 Log.i(TAG, "Chip doesn't support bridgedAp, reset to default band");
170                                 configBuilder.setBand(generateDefaultBand(mContext));
171                                 persistConfigAndTriggerBackupManagerProxy(configBuilder.build());
172                             }
173                         }
174                     }
175                 }, mHandler);
176     }
177 
178     /**
179      * Return the current soft access point configuration.
180      */
getApConfiguration()181     public synchronized SoftApConfiguration getApConfiguration() {
182         if (mPersistentWifiApConfig == null) {
183             /* Use default configuration. */
184             Log.d(TAG, "Fallback to use default AP configuration");
185             persistConfigAndTriggerBackupManagerProxy(
186                     updatePersistentRandomizedMacAddress(getDefaultApConfiguration()));
187         }
188         SoftApConfiguration sanitizedPersistentconfig =
189                 sanitizePersistentApConfig(mPersistentWifiApConfig);
190         if (!Objects.equals(mPersistentWifiApConfig, sanitizedPersistentconfig)) {
191             Log.d(TAG, "persisted config was converted, need to resave it");
192             persistConfigAndTriggerBackupManagerProxy(sanitizedPersistentconfig);
193         }
194 
195         if (mForceApChannel) {
196             Log.d(TAG, "getApConfiguration: Band force to "
197                     + mForcedApBand
198                     + ", and channel force to "
199                     + mForcedApChannel
200                     + ", and maximum channel width limited to "
201                     + mForcedApMaximumChannelBandWidth);
202             if (SdkLevel.isAtLeastT()) {
203                 return mForcedApChannel == 0
204                         ? new SoftApConfiguration.Builder(mPersistentWifiApConfig)
205                                 .setBand(mForcedApBand)
206                                 .setMaxChannelBandwidth(mForcedApMaximumChannelBandWidth)
207                                 .build()
208                         : new SoftApConfiguration.Builder(mPersistentWifiApConfig)
209                                 .setChannel(mForcedApChannel, mForcedApBand)
210                                 .setMaxChannelBandwidth(mForcedApMaximumChannelBandWidth)
211                                 .build();
212             } else {
213                 return mForcedApChannel == 0
214                         ? new SoftApConfiguration.Builder(mPersistentWifiApConfig)
215                                 .setBand(mForcedApBand)
216                                 .build()
217                         : new SoftApConfiguration.Builder(mPersistentWifiApConfig)
218                                 .setChannel(mForcedApChannel, mForcedApBand)
219                                 .build();
220             }
221         }
222         return mPersistentWifiApConfig;
223     }
224 
225     /**
226      * Update the current soft access point configuration.
227      * Restore to default AP configuration if null is provided.
228      * This can be invoked under context of binder threads (WifiManager.setWifiApConfiguration)
229      * and the main Wifi thread (CMD_START_AP).
230      */
setApConfiguration(SoftApConfiguration config)231     public synchronized void setApConfiguration(SoftApConfiguration config) {
232         SoftApConfiguration newConfig = config == null ? getDefaultApConfiguration()
233                 : new SoftApConfiguration.Builder(sanitizePersistentApConfig(config))
234                         .setUserConfiguration(true).build();
235         persistConfigAndTriggerBackupManagerProxy(
236                 updatePersistentRandomizedMacAddress(newConfig));
237     }
238 
239     /**
240      * Returns SoftApConfiguration in which some parameters might be upgrade to supported default
241      * configuration.
242      */
upgradeSoftApConfiguration( @onNull SoftApConfiguration config)243     public synchronized SoftApConfiguration upgradeSoftApConfiguration(
244             @NonNull SoftApConfiguration config) {
245         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
246         if (SdkLevel.isAtLeastS() && ApConfigUtil.isBridgedModeSupported(mContext, mWifiNative)
247                 && config.getBands().length == 1 && mResourceCache.getBoolean(
248                         R.bool.config_wifiSoftapAutoUpgradeToBridgedConfigWhenSupported)) {
249             int[] dual_bands = new int[] {
250                     SoftApConfiguration.BAND_2GHZ,
251                     SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
252             if (SdkLevel.isAtLeastS()) {
253                 configBuilder.setBands(dual_bands);
254             }
255             Log.i(TAG, "Device support bridged AP, upgrade band setting to bridged configuration");
256         }
257         return configBuilder.build();
258     }
259 
260     /**
261      * Returns SoftApConfiguration in which some parameters might be reset to supported default
262      * config since it depends on UI or HW.
263      *
264      * MaxNumberOfClients and isClientControlByUserEnabled will need HAL support client force
265      * disconnect, and Band setting (5g/6g) need HW support.
266      *
267      * HiddenSsid, Channel, ShutdownTimeoutMillis and AutoShutdownEnabled are features
268      * which need UI(Setting) support.
269      *
270      * SAE/SAE-Transition need hardware support, reset to secured WPA2 security type when device
271      * doesn't support it.
272      *
273      * Check band(s) setting to make sure all of the band(s) are supported.
274      * - If previous bands configuration is bridged mode. Reset to 2.4G when device doesn't support
275      *   it.
276      */
resetToDefaultForUnsupportedConfig( @onNull SoftApConfiguration config)277     public synchronized SoftApConfiguration resetToDefaultForUnsupportedConfig(
278             @NonNull SoftApConfiguration config) {
279         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
280         if ((!ApConfigUtil.isClientForceDisconnectSupported(mContext)
281                 || mResourceCache.getBoolean(
282                 R.bool.config_wifiSoftapResetUserControlConfig))
283                 && (config.isClientControlByUserEnabled()
284                 || config.getBlockedClientList().size() != 0)) {
285             configBuilder.setClientControlByUserEnabled(false);
286             configBuilder.setBlockedClientList(new ArrayList<>());
287             Log.i(TAG, "Reset ClientControlByUser to false due to device doesn't support");
288         }
289 
290         if ((!ApConfigUtil.isClientForceDisconnectSupported(mContext)
291                 || mResourceCache.getBoolean(
292                 R.bool.config_wifiSoftapResetMaxClientSettingConfig))
293                 && config.getMaxNumberOfClients() != 0) {
294             configBuilder.setMaxNumberOfClients(0);
295             Log.i(TAG, "Reset MaxNumberOfClients to 0 due to device doesn't support");
296         }
297 
298         if (!ApConfigUtil.isWpa3SaeSupported(mContext) && (config.getSecurityType()
299                 == SECURITY_TYPE_WPA3_SAE
300                 || config.getSecurityType()
301                 == SECURITY_TYPE_WPA3_SAE_TRANSITION)) {
302             try {
303                 configBuilder.setPassphrase(generatePassword(),
304                         SECURITY_TYPE_WPA2_PSK);
305             } catch (IllegalArgumentException e) {
306                 Log.wtf(TAG, "Generated password was invalid: " + e);
307             }
308             Log.i(TAG, "Device doesn't support WPA3-SAE, reset config to WPA2");
309         }
310 
311         if (mResourceCache.getBoolean(R.bool.config_wifiSoftapResetChannelConfig)
312                 && config.getChannel() != 0) {
313             // The device might not support customize channel or forced channel might not
314             // work in some countries. Need to reset it.
315             configBuilder.setBand(ApConfigUtil.append24GToBandIf24GSupported(
316                     config.getBand(), mContext));
317             Log.i(TAG, "Reset SAP channel configuration");
318         }
319 
320         if (SdkLevel.isAtLeastS() && config.getBands().length > 1) {
321             if (!ApConfigUtil.isBridgedModeSupported(mContext, mWifiNative)
322                     || !isBandsSupported(config.getBands(), mContext)) {
323                 int newSingleApBand = 0;
324                 for (int targetBand : config.getBands()) {
325                     int availableBand = ApConfigUtil.removeUnsupportedBands(
326                             mContext, targetBand);
327                     newSingleApBand |= availableBand;
328                 }
329                 newSingleApBand = ApConfigUtil.append24GToBandIf24GSupported(
330                         newSingleApBand, mContext);
331                 configBuilder.setBand(newSingleApBand);
332                 Log.i(TAG, "An unsupported band setting for the bridged mode, force to "
333                         + newSingleApBand);
334             }
335         } else {
336             // Single band case, check and remove unsupported band.
337             int newBand = ApConfigUtil.removeUnsupportedBands(mContext, config.getBand());
338             if (newBand != config.getBand()) {
339                 newBand = ApConfigUtil.append24GToBandIf24GSupported(newBand, mContext);
340                 Log.i(TAG, "Reset band from " + config.getBand() + " to "
341                         + newBand);
342                 configBuilder.setBand(newBand);
343             }
344         }
345 
346         if (mResourceCache.getBoolean(R.bool.config_wifiSoftapResetHiddenConfig)
347                 && config.isHiddenSsid()) {
348             configBuilder.setHiddenSsid(false);
349             Log.i(TAG, "Reset SAP Hidden Network configuration");
350         }
351 
352         if (mResourceCache.getBoolean(
353                 R.bool.config_wifiSoftapResetAutoShutdownTimerConfig)
354                 && config.getShutdownTimeoutMillis() > 0) {
355             if (CompatChanges.isChangeEnabled(
356                     SoftApConfiguration.REMOVE_ZERO_FOR_TIMEOUT_SETTING)) {
357                 configBuilder.setShutdownTimeoutMillis(SoftApConfiguration.DEFAULT_TIMEOUT);
358             } else {
359                 configBuilder.setShutdownTimeoutMillis(0);
360             }
361             Log.i(TAG, "Reset SAP auto shutdown configuration");
362         }
363 
364         if (!ApConfigUtil.isApMacRandomizationSupported(mContext)) {
365             if (SdkLevel.isAtLeastS()) {
366                 configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
367                 Log.i(TAG, "Force set SAP MAC randomization to NONE when not supported");
368             }
369         }
370 
371         mWifiMetrics.noteSoftApConfigReset(config, configBuilder.build());
372         return configBuilder.build();
373     }
374 
sanitizePersistentApConfig(SoftApConfiguration config)375     private SoftApConfiguration sanitizePersistentApConfig(SoftApConfiguration config) {
376         SoftApConfiguration.Builder convertedConfigBuilder =
377                 new SoftApConfiguration.Builder(config);
378         int[] bands = config.getBands();
379         SparseIntArray newChannels = new SparseIntArray();
380         // The bands length should always 1 in R. Adding SdkLevel.isAtLeastS for lint check only.
381         for (int i = 0; i < bands.length; i++) {
382             int channel = SdkLevel.isAtLeastS()
383                     ? config.getChannels().valueAt(i) : config.getChannel();
384             int newBand = bands[i];
385             if (channel == 0 && mIsAutoAppendLowerBandEnabled
386                     && ApConfigUtil.isBandSupported(newBand, mContext)) {
387                 // some countries are unable to support 5GHz only operation, always allow for 2GHz
388                 // when config doesn't force channel
389                 if ((newBand & SoftApConfiguration.BAND_2GHZ) == 0) {
390                     newBand = ApConfigUtil.append24GToBandIf24GSupported(newBand, mContext);
391                 }
392                 // If the 6G configuration doesn't includes 5G band (2.4G have appended because
393                 // countries reason), it will cause that driver can't switch channel from 6G to
394                 // 5G/2.4G when coexistence happened (For instance: wifi connected to 2.4G or 5G
395                 // channel). Always append 5G into band configuration when configured band includes
396                 // 6G.
397                 if ((newBand & SoftApConfiguration.BAND_6GHZ) != 0
398                         && (newBand & SoftApConfiguration.BAND_5GHZ) == 0) {
399                     newBand = ApConfigUtil.append5GToBandIf5GSupported(newBand, mContext);
400                 }
401             }
402             newChannels.put(newBand, channel);
403         }
404         if (SdkLevel.isAtLeastS()) {
405             convertedConfigBuilder.setChannels(newChannels);
406         } else if (bands.length > 0 && newChannels.valueAt(0) == 0) {
407             convertedConfigBuilder.setBand(newChannels.keyAt(0));
408         }
409         return convertedConfigBuilder.build();
410     }
411 
persistConfigAndTriggerBackupManagerProxy( SoftApConfiguration config)412     private synchronized void persistConfigAndTriggerBackupManagerProxy(
413             SoftApConfiguration config) {
414         mPersistentWifiApConfig = config;
415         if (!TextUtils.isEmpty(config.getPassphrase())) {
416             mLastConfiguredPassphrase = config.getPassphrase();
417         }
418         mHasNewDataToSerialize = true;
419         mHandler.post(() -> mWifiConfigManager.saveToStore());
420         mBackupManagerProxy.notifyDataChanged();
421     }
422 
423     /**
424      * Generate a default WPA3 SAE transition (if supported) or WPA2 based
425      * configuration with a random password.
426      * We are changing the Wifi Ap configuration storage from secure settings to a
427      * flat file accessible only by the system. A WPA2 based default configuration
428      * will keep the device secure after the update.
429      */
getDefaultApConfiguration()430     private SoftApConfiguration getDefaultApConfiguration() {
431         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
432         configBuilder.setBand(generateDefaultBand(mContext));
433         configBuilder.setSsid(mResourceCache.getString(
434                 R.string.wifi_tether_configure_ssid_default) + "_" + getRandomIntForDefaultSsid());
435         try {
436             if (ApConfigUtil.isWpa3SaeSupported(mContext)) {
437                 configBuilder.setPassphrase(generatePassword(),
438                         SECURITY_TYPE_WPA3_SAE_TRANSITION);
439             } else {
440                 configBuilder.setPassphrase(generatePassword(),
441                         SECURITY_TYPE_WPA2_PSK);
442             }
443         } catch (IllegalArgumentException e) {
444             Log.wtf(TAG, "Generated password was invalid: " + e);
445         }
446 
447         // It is new overlay configuration, it should always false in R. Add SdkLevel.isAtLeastS for
448         // lint check
449         if (SdkLevel.isAtLeastS()) {
450             boolean isBridgedModeSupported = mHalDeviceManager.isConcurrencyComboLoadedFromDriver()
451                     ? ApConfigUtil.isBridgedModeSupported(mContext, mWifiNative)
452                             : ApConfigUtil.isBridgedModeSupportedInConfig(mContext);
453             if (isBridgedModeSupported) {
454                 int[] dual_bands = new int[] {
455                         SoftApConfiguration.BAND_2GHZ,
456                         SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
457                 configBuilder.setBands(dual_bands);
458             }
459         }
460 
461         // Update default MAC randomization setting to NONE when feature doesn't support it.
462         if (!ApConfigUtil.isApMacRandomizationSupported(mContext)) {
463             if (SdkLevel.isAtLeastS()) {
464                 configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
465             }
466         }
467 
468         configBuilder.setUserConfiguration(false);
469         return configBuilder.build();
470     }
471 
getRandomIntForDefaultSsid()472     private static int getRandomIntForDefaultSsid() {
473         Random random = new Random();
474         return random.nextInt((RAND_SSID_INT_MAX - RAND_SSID_INT_MIN) + 1) + RAND_SSID_INT_MIN;
475     }
476 
generateLohsSsid(WifiContext context)477     private static String generateLohsSsid(WifiContext context) {
478         return context.getResourceCache().getString(
479                 R.string.wifi_localhotspot_configure_ssid_default) + "_"
480                 + getRandomIntForDefaultSsid();
481     }
482 
hasAutomotiveFeature(Context context)483     private static boolean hasAutomotiveFeature(Context context) {
484         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
485     }
486 
487     /**
488      * Generate a temporary WPA2 based configuration for use by the local only hotspot.
489      * This config is not persisted and will not be stored by the WifiApConfigStore.
490      *
491      * @param context the context of wifi.
492      * @param customConfig customzied softap configuration.
493      * @param capability current softap capability.
494      * @param isExclusive whether customConfig is exclusive (set by privledged app).
495      * @return configuration of local only hotspot.
496      */
generateLocalOnlyHotspotConfig(@onNull WifiContext context, @Nullable SoftApConfiguration customConfig, @NonNull SoftApCapability capability, boolean isExclusive)497     public SoftApConfiguration generateLocalOnlyHotspotConfig(@NonNull WifiContext context,
498             @Nullable SoftApConfiguration customConfig, @NonNull SoftApCapability capability,
499             boolean isExclusive) {
500         SoftApConfiguration.Builder configBuilder;
501         boolean wasSsidAssigned = false;
502         if (customConfig != null && isExclusive) {
503             if (!TextUtils.isEmpty(customConfig.getSsid())) {
504                 wasSsidAssigned = true;
505             }
506             configBuilder = new SoftApConfiguration.Builder(customConfig);
507             // Make sure that we use available band on old build.
508             if (!SdkLevel.isAtLeastT()
509                     && !isBandsSupported(customConfig.getBands(), context)) {
510                 configBuilder.setBand(generateDefaultBand(context));
511             }
512         } else {
513             configBuilder = new SoftApConfiguration.Builder();
514             if (customConfig != null && SdkLevel.isAtLeastS()) {
515                 configBuilder.setChannels(customConfig.getChannels());
516             } else {
517                 // Make sure the default band configuration is supported.
518                 configBuilder.setBand(generateDefaultBand(context));
519             }
520             // Default to disable the auto shutdown
521             configBuilder.setAutoShutdownEnabled(false);
522             try {
523                 if (ApConfigUtil.isWpa3SaeSupported(context)) {
524                     if (customConfig != null
525                             && customConfig.getBand() == SoftApConfiguration.BAND_6GHZ) {
526                         // Requested band is limited to 6GHz only, use SAE.
527                         configBuilder.setPassphrase(generatePassword(),
528                                 SECURITY_TYPE_WPA3_SAE);
529                     } else {
530                         configBuilder.setPassphrase(generatePassword(),
531                                 SECURITY_TYPE_WPA3_SAE_TRANSITION);
532                     }
533                 } else {
534                     configBuilder.setPassphrase(generatePassword(),
535                             SECURITY_TYPE_WPA2_PSK);
536                 }
537             } catch (IllegalArgumentException e) {
538                 Log.wtf(TAG, "Generated password was invalid: " + e);
539             }
540             synchronized (this) {
541                 // Update default MAC randomization setting to NONE when feature doesn't support
542                 // it, or it was disabled in tethered mode.
543                 if (!ApConfigUtil.isApMacRandomizationSupported(context)
544                         || (mPersistentWifiApConfig != null
545                         && mPersistentWifiApConfig.getMacRandomizationSettingInternal()
546                         == SoftApConfiguration.RANDOMIZATION_NONE)) {
547                     if (SdkLevel.isAtLeastS()) {
548                         configBuilder.setMacRandomizationSetting(
549                                 SoftApConfiguration.RANDOMIZATION_NONE);
550                     }
551                 }
552             }
553         }
554 
555         // Automotive mode can force the LOHS to specific bands
556         if (hasAutomotiveFeature(context)) {
557             int desiredBand = SoftApConfiguration.BAND_2GHZ;
558             if (context.getResourceCache().getBoolean(R.bool.config_wifiLocalOnlyHotspot6ghz)
559                     && ApConfigUtil.isBandSupported(SoftApConfiguration.BAND_6GHZ, mContext)) {
560                 desiredBand |= SoftApConfiguration.BAND_6GHZ;
561             }
562             if (context.getResourceCache().getBoolean(R.bool.config_wifi_local_only_hotspot_5ghz)
563                     && ApConfigUtil.isBandSupported(SoftApConfiguration.BAND_5GHZ, mContext)) {
564                 desiredBand |= SoftApConfiguration.BAND_5GHZ;
565             }
566             configBuilder.setBand(desiredBand);
567         }
568 
569         if (!wasSsidAssigned) {
570             configBuilder.setSsid(generateLohsSsid(context));
571         }
572 
573         return updatePersistentRandomizedMacAddress(configBuilder.build());
574     }
575 
576     /**
577      * @return a copy of the given SoftApConfig with the BSSID randomized, unless a custom BSSID is
578      * already set.
579      */
randomizeBssidIfUnset(Context context, SoftApConfiguration config)580     SoftApConfiguration randomizeBssidIfUnset(Context context, SoftApConfiguration config) {
581         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(config);
582         if (config.getBssid() == null && ApConfigUtil.isApMacRandomizationSupported(mContext)
583                 && config.getMacRandomizationSettingInternal()
584                     != SoftApConfiguration.RANDOMIZATION_NONE) {
585             MacAddress macAddress = null;
586             if (config.getMacRandomizationSettingInternal()
587                     == SoftApConfiguration.RANDOMIZATION_PERSISTENT) {
588                 macAddress = config.getPersistentRandomizedMacAddress();
589                 if (macAddress == null) {
590                     WifiSsid ssid = config.getWifiSsid();
591                     macAddress = mMacAddressUtil.calculatePersistentMacForSap(
592                             ssid != null ? ssid.toString() : null, Process.WIFI_UID);
593                     if (macAddress == null) {
594                         Log.e(TAG, "Failed to calculate MAC from SSID. "
595                                 + "Generating new random MAC instead.");
596                     }
597                 }
598             }
599             if (macAddress == null) {
600                 macAddress = MacAddressUtils.createRandomUnicastAddress();
601             }
602             configBuilder.setBssid(macAddress);
603             if (macAddress != null && SdkLevel.isAtLeastS()) {
604                 configBuilder.setMacRandomizationSetting(SoftApConfiguration.RANDOMIZATION_NONE);
605             }
606         }
607         return configBuilder.build();
608     }
609 
610     /**
611      * Verify provided preSharedKey in ap config for WPA2_PSK/WPA3_SAE (Transition) network
612      * meets requirements.
613      */
614     @SuppressWarnings("ReturnValueIgnored")
validateApConfigAsciiPreSharedKey( @oftApConfiguration.SecurityType int securityType, String preSharedKey)615     private static boolean validateApConfigAsciiPreSharedKey(
616             @SoftApConfiguration.SecurityType int securityType, String preSharedKey) {
617         final int sharedKeyLen = preSharedKey.length();
618         final int keyMinLen = securityType == SECURITY_TYPE_WPA3_SAE
619                 ? SAE_ASCII_MIN_LEN : PSK_ASCII_MIN_LEN;
620         if (sharedKeyLen < keyMinLen || sharedKeyLen > PSK_SAE_ASCII_MAX_LEN) {
621             Log.d(TAG, "softap network password string size must be at least " + keyMinLen
622                     + " and no more than " + PSK_SAE_ASCII_MAX_LEN + " when type is "
623                     + securityType);
624             return false;
625         }
626 
627         try {
628             preSharedKey.getBytes(StandardCharsets.UTF_8);
629         } catch (IllegalArgumentException e) {
630             Log.e(TAG, "softap network password verification failed: malformed string");
631             return false;
632         }
633         return true;
634     }
635 
636     /**
637      * Validate a SoftApConfiguration is properly configured for use by SoftApManager.
638      *
639      * This method checks for consistency between security settings (if it requires a password, was
640      * one provided?).
641      *
642      * @param apConfig {@link SoftApConfiguration} to use for softap mode
643      * @param isPrivileged indicate the caller can pass some fields check or not
644      * @param wifiNative to use native API to get iface combinations.
645      * @return boolean true if the provided config meets the minimum set of details, false
646      * otherwise.
647      */
validateApWifiConfiguration(@onNull SoftApConfiguration apConfig, boolean isPrivileged, WifiContext context, WifiNative wifiNative)648     static boolean validateApWifiConfiguration(@NonNull SoftApConfiguration apConfig,
649             boolean isPrivileged, WifiContext context, WifiNative wifiNative) {
650         // first check the SSID
651         WifiSsid ssid = apConfig.getWifiSsid();
652         if (ssid == null || ssid.getBytes().length == 0) {
653             Log.d(TAG, "SSID for softap configuration cannot be null or 0 length.");
654             return false;
655         }
656 
657         // BSSID can be set if caller own permission:android.Manifest.permission.NETWORK_SETTINGS.
658         if (apConfig.getBssid() != null && !isPrivileged) {
659             Log.e(TAG, "Config BSSID needs NETWORK_SETTINGS permission");
660             return false;
661         }
662 
663         String preSharedKey = apConfig.getPassphrase();
664         boolean hasPreSharedKey = !TextUtils.isEmpty(preSharedKey);
665         int authType;
666 
667         try {
668             authType = apConfig.getSecurityType();
669         } catch (IllegalStateException e) {
670             Log.d(TAG, "Unable to get AuthType for softap config: " + e.getMessage());
671             return false;
672         }
673 
674         if (ApConfigUtil.isNonPasswordAP(authType)) {
675             // open networks should not have a password
676             if (hasPreSharedKey) {
677                 Log.d(TAG, "open softap network should not have a password");
678                 return false;
679             }
680         } else if (authType == SECURITY_TYPE_WPA2_PSK
681                 || authType == SECURITY_TYPE_WPA3_SAE_TRANSITION
682                 || authType == SECURITY_TYPE_WPA3_SAE) {
683             // this is a config that should have a password - check that first
684             if (!hasPreSharedKey) {
685                 Log.d(TAG, "softap network password must be set");
686                 return false;
687             }
688 
689             if (context.getResourceCache().getBoolean(
690                     R.bool.config_wifiSoftapPassphraseAsciiEncodableCheck)) {
691                 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
692                 if (!asciiEncoder.canEncode(preSharedKey)) {
693                     Log.d(TAG, "passphrase not ASCII encodable");
694                     return false;
695                 }
696                 if (!validateApConfigAsciiPreSharedKey(authType, preSharedKey)) {
697                     // failed preSharedKey checks for WPA2 and WPA3 SAE (Transition) mode.
698                     return false;
699                 }
700             }
701         } else {
702             // this is not a supported security type
703             Log.d(TAG, "softap configs must either be open or WPA2 PSK networks");
704             return false;
705         }
706 
707         if (!isBandsSupported(apConfig.getBands(), context)) {
708             return false;
709         }
710 
711         if (ApConfigUtil.isSecurityTypeRestrictedFor6gBand(authType)) {
712             for (int band : apConfig.getBands()) {
713                 // Only return failure if requested band is limited to 6GHz only
714                 if (band == SoftApConfiguration.BAND_6GHZ
715                         && !ApConfigUtil.canHALConvertRestrictedSecurityTypeFor6GHz(
716                                 context.getResourceCache(), authType)) {
717                     Log.d(TAG, "security type: " +  authType
718                             + " is not allowed for softap in 6GHz band");
719                     return false;
720                 }
721             }
722         }
723 
724         if (SdkLevel.isAtLeastT()
725                 && authType == SECURITY_TYPE_WPA3_OWE_TRANSITION) {
726             if (!ApConfigUtil.isBridgedModeSupported(context, wifiNative)) {
727                 Log.d(TAG, "softap owe transition needs bridge mode support");
728                 return false;
729             } else if (apConfig.getBands().length > 1) {
730                 Log.d(TAG, "softap owe transition must use single band");
731                 return false;
732             }
733         }
734 
735         // Hostapd requires 11AX to configure 11BE
736         if (SdkLevel.isAtLeastB() && apConfig.isIeee80211beEnabled()
737                 && !apConfig.isIeee80211axEnabledInternal()) {
738             Log.d(TAG, "11AX is required when configuring 11BE");
739             return false;
740         }
741 
742         return true;
743     }
744 
generatePassword()745     private static String generatePassword() {
746         // Characters that will be used for password generation. Some characters commonly known to
747         // be confusing like 0 and O excluded from this list.
748         final String allowed = "23456789abcdefghijkmnpqrstuvwxyz";
749         final int passLength = 15;
750 
751         StringBuilder sb = new StringBuilder(passLength);
752         SecureRandom random = new SecureRandom();
753         for (int i = 0; i < passLength; i++) {
754             sb.append(allowed.charAt(random.nextInt(allowed.length())));
755         }
756         return sb.toString();
757     }
758 
759     /**
760      * Generate default band base on supported band configuration.
761      *
762      * @param context The caller context used to get value from resource file.
763      * @return A band which will be used for a default band in default configuration.
764      */
generateDefaultBand(WifiContext context)765     public static @BandType int generateDefaultBand(WifiContext context) {
766         for (int band : SoftApConfiguration.BAND_TYPES) {
767             if (ApConfigUtil.isBandSupported(band, context)) {
768                 return band;
769             }
770         }
771         Log.e(TAG, "Invalid overlay configuration! No any band supported on SoftAp");
772         return SoftApConfiguration.BAND_2GHZ;
773     }
774 
isBandsSupported(@onNull int[] apBands, WifiContext context)775     private static boolean isBandsSupported(@NonNull int[] apBands, WifiContext context) {
776         for (int band : apBands) {
777             if (!ApConfigUtil.isBandSupported(band, context)) {
778                 return false;
779             }
780         }
781         return true;
782     }
783 
784     /**
785      * Enable force-soft-AP-channel mode which takes effect when soft AP starts next time
786      *
787      * @param forcedApBand The forced band.
788      * @param forcedApChannel The forced IEEE channel number or 0 when forced AP band only.
789      * @param forcedApMaximumChannelBandWidth The forced maximum channel bandwidth.
790      */
enableForceSoftApBandOrChannel( @andType int forcedApBand, int forcedApChannel, int forcedApMaximumChannelBandWidth)791     public synchronized void enableForceSoftApBandOrChannel(
792             @BandType int forcedApBand, int forcedApChannel, int forcedApMaximumChannelBandWidth) {
793         mForceApChannel = true;
794         mForcedApChannel = forcedApChannel;
795         mForcedApBand = forcedApBand;
796         mForcedApMaximumChannelBandWidth = forcedApMaximumChannelBandWidth;
797     }
798 
799     /**
800      * Disable force-soft-AP-channel mode which take effect when soft AP starts next time
801      */
disableForceSoftApBandOrChannel()802     public synchronized void disableForceSoftApBandOrChannel() {
803         mForceApChannel = false;
804     }
805 
updatePersistentRandomizedMacAddress(SoftApConfiguration config)806     private SoftApConfiguration updatePersistentRandomizedMacAddress(SoftApConfiguration config) {
807         // Update randomized MacAddress
808         WifiSsid ssid = config.getWifiSsid();
809         MacAddress randomizedMacAddress = mMacAddressUtil.calculatePersistentMacForSap(
810                 ssid != null ? ssid.toString() : null, Process.WIFI_UID);
811         if (randomizedMacAddress != null) {
812             return new SoftApConfiguration.Builder(config)
813                     .setRandomizedMacAddress(randomizedMacAddress).build();
814         }
815 
816         if (config.getPersistentRandomizedMacAddress() != null) {
817             return config;
818         }
819 
820         randomizedMacAddress = MacAddressUtils.createRandomUnicastAddress();
821         return new SoftApConfiguration.Builder(config)
822                 .setRandomizedMacAddress(randomizedMacAddress).build();
823     }
824 
825     /**
826      * Returns the last configured Wi-Fi tethered AP passphrase.
827      */
getLastConfiguredTetheredApPassphraseSinceBoot()828     public synchronized String getLastConfiguredTetheredApPassphraseSinceBoot() {
829         return mLastConfiguredPassphrase;
830     }
831 }
832