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; 18 19 import static android.net.wifi.WifiManager.SAP_CLIENT_DISCONNECT_REASON_CODE_UNSPECIFIED; 20 21 import static com.android.server.wifi.util.ApConfigUtil.ERROR_GENERIC; 22 import static com.android.server.wifi.util.ApConfigUtil.ERROR_NO_CHANNEL; 23 import static com.android.server.wifi.util.ApConfigUtil.ERROR_UNSUPPORTED_CONFIGURATION; 24 import static com.android.server.wifi.util.ApConfigUtil.SUCCESS; 25 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.compat.Compatibility; 29 import android.content.BroadcastReceiver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.net.MacAddress; 34 import android.net.wifi.ScanResult; 35 import android.net.wifi.SoftApCapability; 36 import android.net.wifi.SoftApConfiguration; 37 import android.net.wifi.SoftApInfo; 38 import android.net.wifi.WifiAnnotations; 39 import android.net.wifi.WifiClient; 40 import android.net.wifi.WifiContext; 41 import android.net.wifi.WifiInfo; 42 import android.net.wifi.WifiManager; 43 import android.net.wifi.WifiSsid; 44 import android.os.BatteryManager; 45 import android.os.Handler; 46 import android.os.Looper; 47 import android.os.Message; 48 import android.os.SystemClock; 49 import android.os.UserHandle; 50 import android.os.WorkSource; 51 import android.text.TextUtils; 52 import android.util.Log; 53 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.internal.util.IState; 56 import com.android.internal.util.Preconditions; 57 import com.android.internal.util.State; 58 import com.android.internal.util.StateMachine; 59 import com.android.internal.util.WakeupMessage; 60 import com.android.modules.utils.build.SdkLevel; 61 import com.android.server.wifi.WifiNative.InterfaceCallback; 62 import com.android.server.wifi.WifiNative.SoftApHalCallback; 63 import com.android.server.wifi.coex.CoexManager; 64 import com.android.server.wifi.coex.CoexManager.CoexListener; 65 import com.android.server.wifi.util.ApConfigUtil; 66 import com.android.wifi.resources.R; 67 68 import java.io.FileDescriptor; 69 import java.io.PrintWriter; 70 import java.text.SimpleDateFormat; 71 import java.util.ArrayList; 72 import java.util.Date; 73 import java.util.HashMap; 74 import java.util.HashSet; 75 import java.util.Iterator; 76 import java.util.List; 77 import java.util.Locale; 78 import java.util.Map; 79 import java.util.Set; 80 import java.util.stream.Collectors; 81 82 /** 83 * Manage WiFi in AP mode. 84 * The internal state machine runs under the ClientModeImpl handler thread context. 85 */ 86 public class SoftApManager implements ActiveModeManager { 87 private static final String TAG = "SoftApManager"; 88 89 @VisibleForTesting 90 public static final String SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG = TAG 91 + " Soft AP Send Message Timeout on "; 92 private final WifiContext mContext; 93 private final FrameworkFacade mFrameworkFacade; 94 private final WifiNative mWifiNative; 95 // This will only be null if SdkLevel is not at least S 96 @Nullable private final CoexManager mCoexManager; 97 private final ClientModeImplMonitor mCmiMonitor; 98 private final ActiveModeWarden mActiveModeWarden; 99 private final SoftApNotifier mSoftApNotifier; 100 private final BatteryManager mBatteryManager; 101 102 @VisibleForTesting 103 static final long SOFT_AP_PENDING_DISCONNECTION_CHECK_DELAY_MS = 1000; 104 105 private String mCountryCode; 106 107 private final SoftApStateMachine mStateMachine; 108 109 private final Listener<SoftApManager> mModeListener; 110 private final WifiServiceImpl.SoftApCallbackInternal mSoftApCallback; 111 112 private String mApInterfaceName; 113 private boolean mIfaceIsUp; 114 private boolean mIfaceIsDestroyed; 115 116 private final WifiApConfigStore mWifiApConfigStore; 117 118 private final WifiMetrics mWifiMetrics; 119 private final long mId; 120 121 private boolean mIsUnsetBssid; 122 123 private boolean mVerboseLoggingEnabled = false; 124 125 /** 126 * Original configuration, which is the passed configuration when init or 127 * the user-configured {@code WifiApConfigStore#getApConfiguration}tethering} 128 * settings when input is null. 129 * 130 * Use it when doing configuration update to know if the input configuration was changed. 131 * For others use case, it should use {@code mCurrentSoftApConfiguration}. 132 */ 133 @NonNull 134 private final SoftApModeConfiguration mOriginalModeConfiguration; 135 136 137 /** 138 * Current Soft AP configuration which is used to start Soft AP. 139 * The configuration may be changed because 140 * 1. bssid is changed because MAC randomization 141 * 2. bands are changed because fallback to single AP mode mechanism. 142 */ 143 @Nullable 144 private SoftApConfiguration mCurrentSoftApConfiguration; 145 146 @NonNull 147 private Map<String, SoftApInfo> mCurrentSoftApInfoMap = new HashMap<>(); 148 149 @NonNull 150 private SoftApCapability mCurrentSoftApCapability; 151 152 private Map<String, List<WifiClient>> mConnectedClientWithApInfoMap = new HashMap<>(); 153 @VisibleForTesting 154 Map<WifiClient, Integer> mPendingDisconnectClients = new HashMap<>(); 155 156 private boolean mTimeoutEnabled = false; 157 private boolean mBridgedModeOpportunisticsShutdownTimeoutEnabled = false; 158 159 private final SarManager mSarManager; 160 161 private String mStartTimestamp; 162 163 private long mDefaultShutdownTimeoutMillis; 164 165 private long mDefaultShutdownIdleInstanceInBridgedModeTimeoutMillis; 166 167 private final boolean mIsDisableShutDownBridgedModeIdleInstanceTimerWhenCharging; 168 169 private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); 170 171 private WifiDiagnostics mWifiDiagnostics; 172 173 @Nullable 174 private SoftApRole mRole = null; 175 @Nullable 176 private WorkSource mRequestorWs = null; 177 178 private boolean mEverReportMetricsForMaxClient = false; 179 180 @NonNull 181 private Set<MacAddress> mBlockedClientList = new HashSet<>(); 182 183 @NonNull 184 private Set<MacAddress> mAllowedClientList = new HashSet<>(); 185 186 @NonNull 187 private Set<Integer> mSafeChannelFrequencyList = new HashSet<>(); 188 189 private boolean mIsCharging = false; 190 191 /** 192 * A map stores shutdown timeouts for each Soft Ap instance. 193 * There are three timeout messages now. 194 * 1. <mApInterfaceName, timeout> which uses to monitor whole Soft AP interface. 195 * It works on single AP mode and bridged AP mode. 196 * 197 * 2. <instance_lower_band, timeout> which is used to shutdown the AP when there are no 198 * connected devices. It is scheduled only in bridged mode to move dual mode AP to single 199 * mode AP in lower band. 200 * 201 * 3. <instance_higher_band, timeout> which is used to shutdown the AP when there are no 202 * connected devices. It is scheduled only in bridged mode to move dual mode AP to single 203 * mode AP in higher band. 204 */ 205 @VisibleForTesting 206 public Map<String, WakeupMessage> mSoftApTimeoutMessageMap = new HashMap<>(); 207 208 /** 209 * Listener for soft AP events. 210 */ 211 private final SoftApHalCallback mSoftApHalCallback = new SoftApHalCallback() { 212 @Override 213 public void onFailure() { 214 mStateMachine.sendMessage(SoftApStateMachine.CMD_FAILURE); 215 } 216 217 @Override 218 public void onInstanceFailure(String instanceName) { 219 mStateMachine.sendMessage(SoftApStateMachine.CMD_FAILURE, instanceName); 220 } 221 222 @Override 223 public void onInfoChanged(String apIfaceInstance, int frequency, 224 @WifiAnnotations.Bandwidth int bandwidth, 225 @WifiAnnotations.WifiStandard int generation, 226 MacAddress apIfaceInstanceMacAddress) { 227 SoftApInfo apInfo = new SoftApInfo(); 228 apInfo.setFrequency(frequency); 229 apInfo.setBandwidth(bandwidth); 230 apInfo.setWifiStandard(generation); 231 if (apIfaceInstanceMacAddress != null) { 232 apInfo.setBssid(apIfaceInstanceMacAddress); 233 } 234 apInfo.setApInstanceIdentifier(apIfaceInstance != null 235 ? apIfaceInstance : mApInterfaceName); 236 mStateMachine.sendMessage( 237 SoftApStateMachine.CMD_AP_INFO_CHANGED, 0, 0, apInfo); 238 } 239 240 @Override 241 public void onConnectedClientsChanged(String apIfaceInstance, MacAddress clientAddress, 242 boolean isConnected) { 243 if (clientAddress != null) { 244 WifiClient client = new WifiClient(clientAddress, apIfaceInstance != null 245 ? apIfaceInstance : mApInterfaceName); 246 mStateMachine.sendMessage(SoftApStateMachine.CMD_ASSOCIATED_STATIONS_CHANGED, 247 isConnected ? 1 : 0, 0, client); 248 } else { 249 Log.e(getTag(), "onConnectedClientsChanged: Invalid type returned"); 250 } 251 } 252 }; 253 254 // This will only be null if SdkLevel is not at least S 255 @Nullable private final CoexListener mCoexListener; 256 updateSafeChannelFrequencyList()257 private void updateSafeChannelFrequencyList() { 258 if (!SdkLevel.isAtLeastS() || mCurrentSoftApConfiguration == null) { 259 return; 260 } 261 mSafeChannelFrequencyList.clear(); 262 for (int configuredBand : mCurrentSoftApConfiguration.getBands()) { 263 for (int band : SoftApConfiguration.BAND_TYPES) { 264 if ((band & configuredBand) == 0) { 265 continue; 266 } 267 for (int channel : mCurrentSoftApCapability.getSupportedChannelList(band)) { 268 mSafeChannelFrequencyList.add( 269 ApConfigUtil.convertChannelToFrequency(channel, band)); 270 } 271 } 272 } 273 if ((mCoexManager.getCoexRestrictions() & WifiManager.COEX_RESTRICTION_SOFTAP) != 0) { 274 mSafeChannelFrequencyList.removeAll( 275 ApConfigUtil.getUnsafeChannelFreqsFromCoex(mCoexManager)); 276 } 277 if (isBridgedMode() && mCurrentSoftApInfoMap.size() == 2) { 278 // Logging only for bridged use case since it only used to fallback to single AP mode. 279 Log.d(getTag(), "SafeChannelFrequencyList = " + mSafeChannelFrequencyList); 280 } 281 } 282 configureInternalConfiguration()283 private void configureInternalConfiguration() { 284 if (mCurrentSoftApConfiguration == null) { 285 return; 286 } 287 mBlockedClientList = new HashSet<>(mCurrentSoftApConfiguration.getBlockedClientList()); 288 mAllowedClientList = new HashSet<>(mCurrentSoftApConfiguration.getAllowedClientList()); 289 mTimeoutEnabled = mCurrentSoftApConfiguration.isAutoShutdownEnabled(); 290 mBridgedModeOpportunisticsShutdownTimeoutEnabled = 291 mCurrentSoftApConfiguration.isBridgedModeOpportunisticShutdownEnabledInternal(); 292 } 293 updateChangeableConfiguration(SoftApConfiguration newConfig)294 private void updateChangeableConfiguration(SoftApConfiguration newConfig) { 295 if (mCurrentSoftApConfiguration == null || newConfig == null) { 296 return; 297 } 298 /** 299 * update configurations only which mentioned in WifiManager#setSoftApConfiguration 300 */ 301 long newShutdownTimeoutMillis = newConfig.getShutdownTimeoutMillis(); 302 // Compatibility check is used for unit test only since the SoftApManager is created by 303 // the unit test thread (not the system_server) when running unit test. In other cases, 304 // the SoftApManager would run in system server(i.e. always bypasses the app compat check). 305 if (Compatibility.isChangeEnabled(SoftApConfiguration.REMOVE_ZERO_FOR_TIMEOUT_SETTING) 306 && newShutdownTimeoutMillis == 0) { 307 newShutdownTimeoutMillis = SoftApConfiguration.DEFAULT_TIMEOUT; 308 } 309 SoftApConfiguration.Builder newConfigurBuilder = 310 new SoftApConfiguration.Builder(mCurrentSoftApConfiguration) 311 .setAllowedClientList(newConfig.getAllowedClientList()) 312 .setBlockedClientList(newConfig.getBlockedClientList()) 313 .setClientControlByUserEnabled(newConfig.isClientControlByUserEnabled()) 314 .setMaxNumberOfClients(newConfig.getMaxNumberOfClients()) 315 .setShutdownTimeoutMillis(newShutdownTimeoutMillis) 316 .setAutoShutdownEnabled(newConfig.isAutoShutdownEnabled()); 317 if (SdkLevel.isAtLeastS()) { 318 newConfigurBuilder.setBridgedModeOpportunisticShutdownEnabled( 319 newConfig.isBridgedModeOpportunisticShutdownEnabledInternal()); 320 } 321 mCurrentSoftApConfiguration = newConfigurBuilder.build(); 322 configureInternalConfiguration(); 323 } 324 SoftApManager( @onNull WifiContext context, @NonNull Looper looper, @NonNull FrameworkFacade framework, @NonNull WifiNative wifiNative, @NonNull CoexManager coexManager, @NonNull BatteryManager batteryManager, String countryCode, @NonNull Listener<SoftApManager> listener, @NonNull WifiServiceImpl.SoftApCallbackInternal callback, @NonNull WifiApConfigStore wifiApConfigStore, @NonNull SoftApModeConfiguration apConfig, @NonNull WifiMetrics wifiMetrics, @NonNull SarManager sarManager, @NonNull WifiDiagnostics wifiDiagnostics, @NonNull SoftApNotifier softApNotifier, @NonNull ClientModeImplMonitor cmiMonitor, @NonNull ActiveModeWarden activeModeWarden, long id, @NonNull WorkSource requestorWs, @NonNull SoftApRole role, boolean verboseLoggingEnabled)325 public SoftApManager( 326 @NonNull WifiContext context, 327 @NonNull Looper looper, 328 @NonNull FrameworkFacade framework, 329 @NonNull WifiNative wifiNative, 330 @NonNull CoexManager coexManager, 331 @NonNull BatteryManager batteryManager, 332 String countryCode, 333 @NonNull Listener<SoftApManager> listener, 334 @NonNull WifiServiceImpl.SoftApCallbackInternal callback, 335 @NonNull WifiApConfigStore wifiApConfigStore, 336 @NonNull SoftApModeConfiguration apConfig, 337 @NonNull WifiMetrics wifiMetrics, 338 @NonNull SarManager sarManager, 339 @NonNull WifiDiagnostics wifiDiagnostics, 340 @NonNull SoftApNotifier softApNotifier, 341 @NonNull ClientModeImplMonitor cmiMonitor, 342 @NonNull ActiveModeWarden activeModeWarden, 343 long id, 344 @NonNull WorkSource requestorWs, 345 @NonNull SoftApRole role, 346 boolean verboseLoggingEnabled) { 347 mContext = context; 348 mFrameworkFacade = framework; 349 mSoftApNotifier = softApNotifier; 350 mWifiNative = wifiNative; 351 mCoexManager = coexManager; 352 mBatteryManager = batteryManager; 353 if (SdkLevel.isAtLeastS()) { 354 mCoexListener = new CoexListener() { 355 @Override 356 public void onCoexUnsafeChannelsChanged() { 357 if (mCurrentSoftApConfiguration == null) { 358 return; 359 } 360 mStateMachine.sendMessage( 361 SoftApStateMachine.CMD_SAFE_CHANNEL_FREQUENCY_CHANGED); 362 } 363 }; 364 } else { 365 mCoexListener = null; 366 } 367 mCountryCode = countryCode; 368 mModeListener = listener; 369 mSoftApCallback = callback; 370 mWifiApConfigStore = wifiApConfigStore; 371 mCurrentSoftApConfiguration = apConfig.getSoftApConfiguration(); 372 mCurrentSoftApCapability = apConfig.getCapability(); 373 // null is a valid input and means we use the user-configured tethering settings. 374 if (mCurrentSoftApConfiguration == null) { 375 mCurrentSoftApConfiguration = mWifiApConfigStore.getApConfiguration(); 376 // may still be null if we fail to load the default config 377 } 378 // Store mode configuration before update the configuration. 379 mOriginalModeConfiguration = new SoftApModeConfiguration(apConfig.getTargetMode(), 380 mCurrentSoftApConfiguration, mCurrentSoftApCapability); 381 if (mCurrentSoftApConfiguration != null) { 382 mIsUnsetBssid = mCurrentSoftApConfiguration.getBssid() == null; 383 if (mCurrentSoftApCapability.areFeaturesSupported( 384 SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)) { 385 mCurrentSoftApConfiguration = mWifiApConfigStore.randomizeBssidIfUnset( 386 mContext, mCurrentSoftApConfiguration); 387 } 388 } 389 mWifiMetrics = wifiMetrics; 390 mSarManager = sarManager; 391 mWifiDiagnostics = wifiDiagnostics; 392 mStateMachine = new SoftApStateMachine(looper); 393 configureInternalConfiguration(); 394 mDefaultShutdownTimeoutMillis = mContext.getResources().getInteger( 395 R.integer.config_wifiFrameworkSoftApShutDownTimeoutMilliseconds); 396 mDefaultShutdownIdleInstanceInBridgedModeTimeoutMillis = mContext.getResources().getInteger( 397 R.integer 398 .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond); 399 400 mIsDisableShutDownBridgedModeIdleInstanceTimerWhenCharging = mContext.getResources() 401 .getBoolean(R.bool 402 .config_wifiFrameworkSoftApDisableBridgedModeShutdownIdleInstanceWhenCharging); 403 mCmiMonitor = cmiMonitor; 404 mActiveModeWarden = activeModeWarden; 405 mCmiMonitor.registerListener(new ClientModeImplListener() { 406 @Override 407 public void onL2Connected(@NonNull ConcreteClientModeManager clientModeManager) { 408 SoftApManager.this.onL2Connected(clientModeManager); 409 } 410 }); 411 updateSafeChannelFrequencyList(); 412 mId = id; 413 mRole = role; 414 enableVerboseLogging(verboseLoggingEnabled); 415 mStateMachine.sendMessage(SoftApStateMachine.CMD_START, requestorWs); 416 } 417 418 @Override getId()419 public long getId() { 420 return mId; 421 } 422 getTag()423 private String getTag() { 424 return TAG + "[" + (mApInterfaceName == null ? "unknown" : mApInterfaceName) + "]"; 425 } 426 427 /** 428 * Stop soft AP. 429 */ 430 @Override stop()431 public void stop() { 432 Log.d(getTag(), " currentstate: " + getCurrentStateName()); 433 mStateMachine.sendMessage(SoftApStateMachine.CMD_STOP); 434 } 435 isOweTransition()436 private boolean isOweTransition() { 437 return (SdkLevel.isAtLeastT() && mCurrentSoftApConfiguration != null 438 && mCurrentSoftApConfiguration.getSecurityType() 439 == SoftApConfiguration.SECURITY_TYPE_WPA3_OWE_TRANSITION); 440 } 441 isBridgedMode()442 private boolean isBridgedMode() { 443 return (SdkLevel.isAtLeastS() && mCurrentSoftApConfiguration != null 444 && (mCurrentSoftApConfiguration.getBands().length > 1)); 445 } 446 isBridgeRequired()447 private boolean isBridgeRequired() { 448 return isBridgedMode() || isOweTransition(); 449 } 450 getShutdownTimeoutMillis()451 private long getShutdownTimeoutMillis() { 452 long timeout = mCurrentSoftApConfiguration.getShutdownTimeoutMillis(); 453 return timeout > 0 ? timeout : mDefaultShutdownTimeoutMillis; 454 } 455 getShutdownIdleInstanceInBridgedModeTimeoutMillis()456 private long getShutdownIdleInstanceInBridgedModeTimeoutMillis() { 457 long timeout = mCurrentSoftApConfiguration 458 .getBridgedModeOpportunisticShutdownTimeoutMillisInternal(); 459 return timeout > 0 ? timeout : mDefaultShutdownIdleInstanceInBridgedModeTimeoutMillis; 460 } 461 getHighestFrequencyInstance(Set<String> candidateInstances)462 private String getHighestFrequencyInstance(Set<String> candidateInstances) { 463 int currentHighestFrequencyOnAP = 0; 464 String highestFrequencyInstance = null; 465 for (String instance : candidateInstances) { 466 SoftApInfo info = mCurrentSoftApInfoMap.get(instance); 467 if (info == null) { 468 Log.wtf(getTag(), "Invalid instance name, no way to get the frequency"); 469 return ""; 470 } 471 int frequencyOnInstance = info.getFrequency(); 472 if (frequencyOnInstance > currentHighestFrequencyOnAP) { 473 currentHighestFrequencyOnAP = frequencyOnInstance; 474 highestFrequencyInstance = instance; 475 } 476 } 477 return highestFrequencyInstance; 478 } 479 480 @Override getRole()481 @Nullable public SoftApRole getRole() { 482 return mRole; 483 } 484 485 @Override getPreviousRole()486 @Nullable public ClientRole getPreviousRole() { 487 return null; 488 } 489 490 @Override getLastRoleChangeSinceBootMs()491 public long getLastRoleChangeSinceBootMs() { 492 return 0; 493 } 494 495 /** Set the role of this SoftApManager */ setRole(SoftApRole role)496 public void setRole(SoftApRole role) { 497 // softap does not allow in-place switching of roles. 498 Preconditions.checkState(mRole == null); 499 mRole = role; 500 } 501 502 @Override getInterfaceName()503 public String getInterfaceName() { 504 return mApInterfaceName; 505 } 506 507 @Override getRequestorWs()508 public WorkSource getRequestorWs() { 509 return mRequestorWs; 510 } 511 512 /** 513 * Update AP capability. Called when carrier config or device resouce config changed. 514 * 515 * @param capability new AP capability. 516 */ updateCapability(@onNull SoftApCapability capability)517 public void updateCapability(@NonNull SoftApCapability capability) { 518 mStateMachine.sendMessage(SoftApStateMachine.CMD_UPDATE_CAPABILITY, capability); 519 } 520 521 /** 522 * Update AP configuration. Called when setting update config via 523 * {@link WifiManager#setSoftApConfiguration(SoftApConfiguration)} 524 * 525 * @param config new AP config. 526 */ updateConfiguration(@onNull SoftApConfiguration config)527 public void updateConfiguration(@NonNull SoftApConfiguration config) { 528 mStateMachine.sendMessage(SoftApStateMachine.CMD_UPDATE_CONFIG, config); 529 } 530 531 /** 532 * Retrieve the {@link SoftApModeConfiguration} instance associated with this mode manager. 533 */ getSoftApModeConfiguration()534 public SoftApModeConfiguration getSoftApModeConfiguration() { 535 return new SoftApModeConfiguration(mOriginalModeConfiguration.getTargetMode(), 536 mCurrentSoftApConfiguration, mCurrentSoftApCapability); 537 } 538 539 /** 540 * Retrieve the name of the Bridged AP iface instance to remove for a downgrade, or null if a 541 * downgrade is not possible. 542 */ getBridgedApDowngradeIfaceInstanceForRemoval()543 public String getBridgedApDowngradeIfaceInstanceForRemoval() { 544 if (mCurrentSoftApInfoMap.size() <= 1) { 545 return null; 546 } 547 return getHighestFrequencyInstance(mCurrentSoftApInfoMap.keySet()); 548 } 549 550 /** 551 * Dump info about this softap manager. 552 */ 553 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)554 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 555 pw.println("Dump of SoftApManager id=" + mId); 556 557 pw.println("current StateMachine mode: " + getCurrentStateName()); 558 pw.println("mRole: " + mRole); 559 pw.println("mApInterfaceName: " + mApInterfaceName); 560 pw.println("mIfaceIsUp: " + mIfaceIsUp); 561 pw.println("mSoftApCountryCode: " + mCountryCode); 562 pw.println("mOriginalModeConfiguration.targetMode: " 563 + mOriginalModeConfiguration.getTargetMode()); 564 pw.println("mCurrentSoftApConfiguration: " + mCurrentSoftApConfiguration); 565 pw.println("mCurrentSoftApCapability: " + mCurrentSoftApCapability); 566 pw.println("getConnectedClientList().size(): " + getConnectedClientList().size()); 567 pw.println("mTimeoutEnabled: " + mTimeoutEnabled); 568 pw.println("mBridgedModeOpportunisticsShutdownTimeoutEnabled: " 569 + mBridgedModeOpportunisticsShutdownTimeoutEnabled); 570 pw.println("mCurrentSoftApInfoMap " + mCurrentSoftApInfoMap); 571 pw.println("mStartTimestamp: " + mStartTimestamp); 572 pw.println("mSafeChannelFrequencyList: " + mSafeChannelFrequencyList.stream() 573 .map(Object::toString) 574 .collect(Collectors.joining(","))); 575 mStateMachine.dump(fd, pw, args); 576 } 577 578 @Override enableVerboseLogging(boolean verbose)579 public void enableVerboseLogging(boolean verbose) { 580 mVerboseLoggingEnabled = verbose; 581 } 582 583 @Override toString()584 public String toString() { 585 return "SoftApManager{id=" + getId() 586 + " iface=" + getInterfaceName() 587 + " role=" + getRole() 588 + "}"; 589 } 590 591 /** 592 * A ClientModeImpl instance has been L2 connected. 593 * 594 * @param newPrimary the corresponding ConcreteClientModeManager instance for the ClientModeImpl 595 * that has been L2 connected. 596 */ onL2Connected(@onNull ConcreteClientModeManager clientModeManager)597 private void onL2Connected(@NonNull ConcreteClientModeManager clientModeManager) { 598 Log.d(getTag(), "onL2Connected called"); 599 mStateMachine.sendMessage(SoftApStateMachine.CMD_HANDLE_WIFI_CONNECTED, 600 clientModeManager.syncRequestConnectionInfo()); 601 } 602 603 getCurrentStateName()604 private String getCurrentStateName() { 605 IState currentState = mStateMachine.getCurrentState(); 606 607 if (currentState != null) { 608 return currentState.getName(); 609 } 610 611 return "StateMachine not active"; 612 } 613 614 /** 615 * Update AP state. 616 * 617 * @param newState new AP state 618 * @param currentState current AP state 619 * @param reason Failure reason if the new AP state is in failure state 620 */ updateApState(int newState, int currentState, int reason)621 private void updateApState(int newState, int currentState, int reason) { 622 mSoftApCallback.onStateChanged(newState, reason); 623 624 //send the AP state change broadcast 625 final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); 626 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 627 intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, newState); 628 intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, currentState); 629 if (newState == WifiManager.WIFI_AP_STATE_FAILED) { 630 //only set reason number when softAP start failed 631 intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason); 632 } 633 634 intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, mApInterfaceName); 635 intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mOriginalModeConfiguration.getTargetMode()); 636 mContext.sendBroadcastAsUser(intent, UserHandle.ALL, 637 android.Manifest.permission.ACCESS_WIFI_STATE); 638 } 639 setMacAddress()640 private int setMacAddress() { 641 MacAddress mac = mCurrentSoftApConfiguration.getBssid(); 642 643 if (mac == null) { 644 // If no BSSID is explicitly requested, (re-)configure the factory MAC address. Some 645 // drivers may not support setting the MAC at all, so fail soft in this case. 646 if (!mWifiNative.resetApMacToFactoryMacAddress(mApInterfaceName)) { 647 Log.w(getTag(), "failed to reset to factory MAC address; " 648 + "continuing with current MAC"); 649 } 650 } else { 651 if (mWifiNative.isApSetMacAddressSupported(mApInterfaceName)) { 652 if (!mWifiNative.setApMacAddress(mApInterfaceName, mac)) { 653 Log.e(getTag(), "failed to set explicitly requested MAC address"); 654 return ERROR_GENERIC; 655 } 656 } else if (!mIsUnsetBssid) { 657 // If hardware does not support MAC address setter, 658 // only report the error for non randomization. 659 return ERROR_UNSUPPORTED_CONFIGURATION; 660 } 661 } 662 663 return SUCCESS; 664 } 665 666 /** 667 * Dynamic update the country code when Soft AP enabled. 668 * 669 * @param countryCode 2 byte ASCII string. For ex: US, CA. 670 * @return true if request is sent successfully, false otherwise. 671 */ updateCountryCode(@onNull String countryCode)672 public boolean updateCountryCode(@NonNull String countryCode) { 673 if (ApConfigUtil.isSoftApDynamicCountryCodeSupported(mContext) 674 && mCurrentSoftApCapability.areFeaturesSupported( 675 SoftApCapability.SOFTAP_FEATURE_ACS_OFFLOAD)) { 676 mStateMachine.sendMessage(SoftApStateMachine.CMD_UPDATE_COUNTRY_CODE, countryCode); 677 return true; 678 } 679 return false; 680 } 681 setCountryCode()682 private int setCountryCode() { 683 int band = mCurrentSoftApConfiguration.getBand(); 684 if (TextUtils.isEmpty(mCountryCode)) { 685 if (band == SoftApConfiguration.BAND_5GHZ || band == SoftApConfiguration.BAND_6GHZ) { 686 // Country code is mandatory for 5GHz/6GHz band. 687 Log.e(getTag(), "Invalid country code, " 688 + "required for setting up soft ap in band:" + band); 689 return ERROR_GENERIC; 690 } 691 // Absence of country code is not fatal for 2Ghz & Any band options. 692 return SUCCESS; 693 } 694 if (!mWifiNative.setApCountryCode( 695 mApInterfaceName, mCountryCode.toUpperCase(Locale.ROOT))) { 696 if (band == SoftApConfiguration.BAND_5GHZ || band == SoftApConfiguration.BAND_6GHZ) { 697 // Return an error if failed to set country code when AP is configured for 698 // 5GHz/6GHz band. 699 Log.e(getTag(), "Failed to set country code, " 700 + "required for setting up soft ap in band: " + band); 701 return ERROR_GENERIC; 702 } 703 // Failure to set country code is not fatal for other band options. 704 } 705 return SUCCESS; 706 } 707 708 /** 709 * Start a soft AP instance as configured. 710 * 711 * @return integer result code 712 */ startSoftAp()713 private int startSoftAp() { 714 Log.d(getTag(), "startSoftAp: band " + mCurrentSoftApConfiguration.getBand() 715 + " iface " + mApInterfaceName + " country " + mCountryCode); 716 717 int result = setMacAddress(); 718 if (result != SUCCESS) { 719 return result; 720 } 721 722 result = setCountryCode(); 723 if (result != SUCCESS) { 724 return result; 725 } 726 727 // Make a copy of configuration for updating AP band and channel. 728 SoftApConfiguration.Builder localConfigBuilder = 729 new SoftApConfiguration.Builder(mCurrentSoftApConfiguration); 730 731 result = ApConfigUtil.updateApChannelConfig( 732 mWifiNative, mCoexManager, mContext.getResources(), mCountryCode, 733 localConfigBuilder, mCurrentSoftApConfiguration, mCurrentSoftApCapability); 734 if (result != SUCCESS) { 735 Log.e(getTag(), "Failed to update AP band and channel"); 736 return result; 737 } 738 739 if (mCurrentSoftApConfiguration.isHiddenSsid()) { 740 Log.d(getTag(), "SoftAP is a hidden network"); 741 } 742 743 if (!ApConfigUtil.checkSupportAllConfiguration( 744 mCurrentSoftApConfiguration, mCurrentSoftApCapability)) { 745 Log.d(getTag(), "Unsupported Configuration detect! config = " 746 + mCurrentSoftApConfiguration); 747 return ERROR_UNSUPPORTED_CONFIGURATION; 748 } 749 750 if (!mWifiNative.startSoftAp(mApInterfaceName, 751 localConfigBuilder.build(), 752 mOriginalModeConfiguration.getTargetMode() == WifiManager.IFACE_IP_MODE_TETHERED, 753 mSoftApHalCallback)) { 754 Log.e(getTag(), "Soft AP start failed"); 755 return ERROR_GENERIC; 756 } 757 758 mWifiDiagnostics.startLogging(mApInterfaceName); 759 mStartTimestamp = FORMATTER.format(new Date(System.currentTimeMillis())); 760 Log.d(getTag(), "Soft AP is started "); 761 762 return SUCCESS; 763 } 764 765 /** 766 * Disconnect all connected clients on active softap interface(s). 767 * This is usually done just before stopSoftAp(). 768 */ disconnectAllClients()769 private void disconnectAllClients() { 770 for (WifiClient client : getConnectedClientList()) { 771 mWifiNative.forceClientDisconnect(mApInterfaceName, client.getMacAddress(), 772 SAP_CLIENT_DISCONNECT_REASON_CODE_UNSPECIFIED); 773 } 774 } 775 776 /** 777 * Teardown soft AP and teardown the interface. 778 */ stopSoftAp()779 private void stopSoftAp() { 780 disconnectAllClients(); 781 mWifiDiagnostics.stopLogging(mApInterfaceName); 782 mWifiNative.teardownInterface(mApInterfaceName); 783 Log.d(getTag(), "Soft AP is stopped"); 784 } 785 addClientToPendingDisconnectionList(WifiClient client, int reason)786 private void addClientToPendingDisconnectionList(WifiClient client, int reason) { 787 Log.d(getTag(), "Fail to disconnect client: " + client.getMacAddress() 788 + ", add it into pending list"); 789 mPendingDisconnectClients.put(client, reason); 790 mStateMachine.getHandler().removeMessages( 791 SoftApStateMachine.CMD_FORCE_DISCONNECT_PENDING_CLIENTS); 792 mStateMachine.sendMessageDelayed( 793 SoftApStateMachine.CMD_FORCE_DISCONNECT_PENDING_CLIENTS, 794 SOFT_AP_PENDING_DISCONNECTION_CHECK_DELAY_MS); 795 } 796 getConnectedClientList()797 private List<WifiClient> getConnectedClientList() { 798 List<WifiClient> connectedClientList = new ArrayList<>(); 799 for (List<WifiClient> it : mConnectedClientWithApInfoMap.values()) { 800 connectedClientList.addAll(it); 801 } 802 return connectedClientList; 803 } 804 checkSoftApClient(SoftApConfiguration config, WifiClient newClient)805 private boolean checkSoftApClient(SoftApConfiguration config, WifiClient newClient) { 806 if (!mCurrentSoftApCapability.areFeaturesSupported( 807 SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT)) { 808 return true; 809 } 810 811 if (mBlockedClientList.contains(newClient.getMacAddress())) { 812 Log.d(getTag(), "Force disconnect for client: " + newClient + "in blocked list"); 813 if (!mWifiNative.forceClientDisconnect( 814 mApInterfaceName, newClient.getMacAddress(), 815 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER)) { 816 addClientToPendingDisconnectionList(newClient, 817 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER); 818 } 819 return false; 820 } 821 if (config.isClientControlByUserEnabled() 822 && !mAllowedClientList.contains(newClient.getMacAddress())) { 823 mSoftApCallback.onBlockedClientConnecting(newClient, 824 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER); 825 Log.d(getTag(), "Force disconnect for unauthorized client: " + newClient); 826 if (!mWifiNative.forceClientDisconnect( 827 mApInterfaceName, newClient.getMacAddress(), 828 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER)) { 829 addClientToPendingDisconnectionList(newClient, 830 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER); 831 } 832 return false; 833 } 834 int maxConfig = mCurrentSoftApCapability.getMaxSupportedClients(); 835 if (config.getMaxNumberOfClients() > 0) { 836 maxConfig = Math.min(maxConfig, config.getMaxNumberOfClients()); 837 } 838 839 if (getConnectedClientList().size() >= maxConfig) { 840 Log.i(getTag(), "No more room for new client:" + newClient); 841 if (!mWifiNative.forceClientDisconnect( 842 mApInterfaceName, newClient.getMacAddress(), 843 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS)) { 844 addClientToPendingDisconnectionList(newClient, 845 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS); 846 } 847 mSoftApCallback.onBlockedClientConnecting(newClient, 848 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS); 849 // Avoid report the max client blocked in the same settings. 850 if (!mEverReportMetricsForMaxClient) { 851 mWifiMetrics.noteSoftApClientBlocked(maxConfig); 852 mEverReportMetricsForMaxClient = true; 853 } 854 return false; 855 } 856 return true; 857 } 858 859 private class SoftApStateMachine extends StateMachine { 860 // Commands for the state machine. 861 public static final int CMD_START = 0; 862 public static final int CMD_STOP = 1; 863 public static final int CMD_FAILURE = 2; 864 public static final int CMD_INTERFACE_STATUS_CHANGED = 3; 865 public static final int CMD_ASSOCIATED_STATIONS_CHANGED = 4; 866 public static final int CMD_NO_ASSOCIATED_STATIONS_TIMEOUT = 5; 867 public static final int CMD_INTERFACE_DESTROYED = 7; 868 public static final int CMD_INTERFACE_DOWN = 8; 869 public static final int CMD_AP_INFO_CHANGED = 9; 870 public static final int CMD_UPDATE_CAPABILITY = 10; 871 public static final int CMD_UPDATE_CONFIG = 11; 872 public static final int CMD_FORCE_DISCONNECT_PENDING_CLIENTS = 12; 873 public static final int CMD_NO_ASSOCIATED_STATIONS_TIMEOUT_ON_ONE_INSTANCE = 13; 874 public static final int CMD_SAFE_CHANNEL_FREQUENCY_CHANGED = 14; 875 public static final int CMD_HANDLE_WIFI_CONNECTED = 15; 876 public static final int CMD_UPDATE_COUNTRY_CODE = 16; 877 public static final int CMD_CHARGING_STATE_CHANGED = 17; 878 879 private final State mIdleState = new IdleState(); 880 private final State mStartedState = new StartedState(); 881 882 private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() { 883 @Override 884 public void onDestroyed(String ifaceName) { 885 if (mApInterfaceName != null && mApInterfaceName.equals(ifaceName)) { 886 sendMessage(CMD_INTERFACE_DESTROYED); 887 } 888 } 889 890 @Override 891 public void onUp(String ifaceName) { 892 if (mApInterfaceName != null && mApInterfaceName.equals(ifaceName)) { 893 sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1); 894 } 895 } 896 897 @Override 898 public void onDown(String ifaceName) { 899 if (mApInterfaceName != null && mApInterfaceName.equals(ifaceName)) { 900 sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0); 901 } 902 } 903 }; 904 SoftApStateMachine(Looper looper)905 SoftApStateMachine(Looper looper) { 906 super(TAG, looper); 907 908 // CHECKSTYLE:OFF IndentationCheck 909 addState(mIdleState); 910 addState(mStartedState, mIdleState); 911 // CHECKSTYLE:ON IndentationCheck 912 913 setInitialState(mIdleState); 914 start(); 915 } 916 917 private class IdleState extends State { 918 @Override enter()919 public void enter() { 920 mApInterfaceName = null; 921 mIfaceIsUp = false; 922 mIfaceIsDestroyed = false; 923 } 924 925 @Override exit()926 public void exit() { 927 mModeListener.onStopped(SoftApManager.this); 928 } 929 930 @Override processMessage(Message message)931 public boolean processMessage(Message message) { 932 switch (message.what) { 933 case CMD_STOP: 934 mStateMachine.quitNow(); 935 break; 936 case CMD_START: 937 mRequestorWs = (WorkSource) message.obj; 938 WifiSsid wifiSsid = mCurrentSoftApConfiguration != null 939 ? mCurrentSoftApConfiguration.getWifiSsid() : null; 940 if (wifiSsid == null || wifiSsid.getBytes().length == 0) { 941 Log.e(getTag(), "Unable to start soft AP without valid configuration"); 942 updateApState(WifiManager.WIFI_AP_STATE_FAILED, 943 WifiManager.WIFI_AP_STATE_DISABLED, 944 WifiManager.SAP_START_FAILURE_GENERAL); 945 mWifiMetrics.incrementSoftApStartResult( 946 false, WifiManager.SAP_START_FAILURE_GENERAL); 947 mModeListener.onStartFailure(SoftApManager.this); 948 break; 949 } 950 if (isBridgedMode()) { 951 boolean isFallbackToSingleAp = false; 952 int newSingleApBand = 0; 953 final List<ClientModeManager> cmms = 954 mActiveModeWarden.getClientModeManagers(); 955 // Checking STA status only when device supports STA + AP concurrency 956 // since STA would be dropped when device doesn't support it. 957 if (cmms.size() != 0 && mWifiNative.isStaApConcurrencySupported()) { 958 if (ApConfigUtil.isStaWithBridgedModeSupported(mContext)) { 959 for (ClientModeManager cmm 960 : mActiveModeWarden.getClientModeManagers()) { 961 WifiInfo wifiConnectedInfo = 962 cmm.syncRequestConnectionInfo(); 963 int wifiFrequency = wifiConnectedInfo.getFrequency(); 964 if (wifiFrequency > 0 965 && !mSafeChannelFrequencyList.contains( 966 wifiFrequency)) { 967 Log.d(getTag(), "Wifi connected to unavailable freq: " 968 + wifiFrequency); 969 isFallbackToSingleAp = true; 970 break; 971 } 972 } 973 } else { 974 // The client mode exist but DUT doesn't support 975 // STA + bridged AP, we should fallback to single AP mode. 976 Log.d(getTag(), " STA iface exist but device doesn't support" 977 + " STA + Bridged AP"); 978 isFallbackToSingleAp = true; 979 } 980 } 981 if (mWifiNative.isSoftApInstanceDiedHandlerSupported() 982 && !TextUtils.equals(mCountryCode, 983 mCurrentSoftApCapability.getCountryCode())) { 984 Log.i(getTag(), "CountryCode changed, bypass the supported band" 985 + "capability check, mCountryCode = " + mCountryCode 986 + ", base country in SoftApCapability = " 987 + mCurrentSoftApCapability.getCountryCode()); 988 } else { 989 for (int configuredBand : mCurrentSoftApConfiguration.getBands()) { 990 int availableBand = ApConfigUtil.removeUnavailableBands( 991 mCurrentSoftApCapability, 992 configuredBand, mCoexManager); 993 if (configuredBand != availableBand) { 994 isFallbackToSingleAp = true; 995 } 996 newSingleApBand |= availableBand; 997 } 998 } 999 // Fall back to Single AP if it's not possible to create a Bridged AP. 1000 if (!mWifiNative.isItPossibleToCreateBridgedApIface(mRequestorWs)) { 1001 isFallbackToSingleAp = true; 1002 } 1003 // Fall back to single AP if creating a single AP does not require 1004 // destroying an existing iface, but creating a bridged AP does. 1005 if (mWifiNative.shouldDowngradeToSingleApForConcurrency(mRequestorWs)) { 1006 Log.d(getTag(), "Creating bridged AP will destroy an existing" 1007 + " iface, but single AP will not."); 1008 isFallbackToSingleAp = true; 1009 } 1010 if (isFallbackToSingleAp) { 1011 newSingleApBand = ApConfigUtil.append24GToBandIf24GSupported( 1012 newSingleApBand, mContext); 1013 Log.i(getTag(), "Fallback to single AP mode with band " 1014 + newSingleApBand); 1015 mCurrentSoftApConfiguration = 1016 new SoftApConfiguration.Builder(mCurrentSoftApConfiguration) 1017 .setBand(newSingleApBand) 1018 .build(); 1019 } 1020 } 1021 1022 // Remove 6GHz from requested bands if security type is restricted 1023 // Note: 6GHz only band is already handled by initial validation 1024 mCurrentSoftApConfiguration = 1025 ApConfigUtil.remove6gBandForUnsupportedSecurity( 1026 mCurrentSoftApConfiguration); 1027 1028 mApInterfaceName = mWifiNative.setupInterfaceForSoftApMode( 1029 mWifiNativeInterfaceCallback, mRequestorWs, 1030 mCurrentSoftApConfiguration.getBand(), isBridgeRequired(), 1031 SoftApManager.this); 1032 if (TextUtils.isEmpty(mApInterfaceName)) { 1033 Log.e(getTag(), "setup failure when creating ap interface."); 1034 updateApState(WifiManager.WIFI_AP_STATE_FAILED, 1035 WifiManager.WIFI_AP_STATE_DISABLED, 1036 WifiManager.SAP_START_FAILURE_GENERAL); 1037 mWifiMetrics.incrementSoftApStartResult( 1038 false, WifiManager.SAP_START_FAILURE_GENERAL); 1039 mModeListener.onStartFailure(SoftApManager.this); 1040 break; 1041 } 1042 mSoftApNotifier.dismissSoftApShutdownTimeoutExpiredNotification(); 1043 updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 1044 WifiManager.WIFI_AP_STATE_DISABLED, 0); 1045 int result = startSoftAp(); 1046 if (result != SUCCESS) { 1047 int failureReason = WifiManager.SAP_START_FAILURE_GENERAL; 1048 if (result == ERROR_NO_CHANNEL) { 1049 failureReason = WifiManager.SAP_START_FAILURE_NO_CHANNEL; 1050 } else if (result == ERROR_UNSUPPORTED_CONFIGURATION) { 1051 failureReason = WifiManager 1052 .SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION; 1053 } 1054 updateApState(WifiManager.WIFI_AP_STATE_FAILED, 1055 WifiManager.WIFI_AP_STATE_ENABLING, 1056 failureReason); 1057 stopSoftAp(); 1058 mWifiMetrics.incrementSoftApStartResult(false, failureReason); 1059 mModeListener.onStartFailure(SoftApManager.this); 1060 break; 1061 } 1062 transitionTo(mStartedState); 1063 break; 1064 case CMD_UPDATE_CAPABILITY: 1065 SoftApCapability capability = (SoftApCapability) message.obj; 1066 mCurrentSoftApCapability = new SoftApCapability(capability); 1067 updateSafeChannelFrequencyList(); 1068 break; 1069 case CMD_UPDATE_CONFIG: 1070 SoftApConfiguration newConfig = (SoftApConfiguration) message.obj; 1071 Log.d(getTag(), "Configuration changed to " + newConfig); 1072 // Idle mode, update all configurations. 1073 mCurrentSoftApConfiguration = newConfig; 1074 configureInternalConfiguration(); 1075 break; 1076 case CMD_UPDATE_COUNTRY_CODE: 1077 String countryCode = (String) message.obj; 1078 if (!TextUtils.isEmpty(countryCode)) { 1079 mCountryCode = countryCode; 1080 } 1081 break; 1082 default: 1083 // Ignore all other commands. 1084 break; 1085 } 1086 1087 return HANDLED; 1088 } 1089 } 1090 1091 private class StartedState extends State { 1092 BroadcastReceiver mBatteryChargingReceiver = new BroadcastReceiver() { 1093 @Override 1094 public void onReceive(Context context, Intent intent) { 1095 String action = intent.getAction(); 1096 if (action.equals(Intent.ACTION_POWER_CONNECTED)) { 1097 sendMessage(CMD_CHARGING_STATE_CHANGED, 1); 1098 } else if (action.equals(Intent.ACTION_POWER_DISCONNECTED)) { 1099 sendMessage(CMD_CHARGING_STATE_CHANGED, 0); 1100 } 1101 } 1102 }; 1103 1104 /** 1105 * Schedule timeout message depends on Soft Ap instance 1106 * 1107 * @param changedInstance the schedule should change on specific instance only. 1108 * If changedInstance is mApInterfaceName, it means that 1109 * we need to reschedule all of timeout message. 1110 */ rescheduleTimeoutMessages(@onNull String changedInstance)1111 private void rescheduleTimeoutMessages(@NonNull String changedInstance) { 1112 // Don't trigger bridged mode shutdown timeout when only one active instance 1113 // In Dual AP, one instance may already be closed due to LTE coexistence or DFS 1114 // restrictions or due to inactivity. i.e. mCurrentSoftApInfoMap.size() is 1) 1115 if (isBridgedMode() && mCurrentSoftApInfoMap.size() == 2) { 1116 if (TextUtils.equals(mApInterfaceName, changedInstance)) { 1117 Set<String> instances = mCurrentSoftApInfoMap.keySet(); 1118 String lowerFrequencyInstance = null; 1119 String HighestFrequencyInstance = getHighestFrequencyInstance(instances); 1120 // Schedule bridged mode timeout on all instances 1121 for (String instance : instances) { 1122 // Make sure that we schedule the higher frequency instance first since 1123 // the current shutdown design is shutdown higher band instance first 1124 // when both of intstances are idle. 1125 if (TextUtils.equals(instance, HighestFrequencyInstance)) { 1126 rescheduleTimeoutMessageIfNeeded(instance); 1127 } else { 1128 lowerFrequencyInstance = instance; 1129 } 1130 } 1131 // Make sure that we schedule the lower frequency instance later. 1132 rescheduleTimeoutMessageIfNeeded(lowerFrequencyInstance); 1133 } else { 1134 rescheduleTimeoutMessageIfNeeded(changedInstance); 1135 } 1136 } 1137 1138 // Always evaluate timeout schedule on tetheringInterface 1139 rescheduleTimeoutMessageIfNeeded(mApInterfaceName); 1140 } 1141 removeIfaceInstanceFromBridgedApIface(String instanceName)1142 private void removeIfaceInstanceFromBridgedApIface(String instanceName) { 1143 if (TextUtils.isEmpty(instanceName)) { 1144 return; 1145 } 1146 if (mCurrentSoftApInfoMap.containsKey(instanceName)) { 1147 Log.i(getTag(), "remove instance " + instanceName + "(" 1148 + mCurrentSoftApInfoMap.get(instanceName).getFrequency() 1149 + ") from bridged iface " + mApInterfaceName); 1150 mWifiNative.removeIfaceInstanceFromBridgedApIface(mApInterfaceName, 1151 instanceName); 1152 // Remove the info and update it. 1153 updateSoftApInfo(mCurrentSoftApInfoMap.get(instanceName), true); 1154 } 1155 } 1156 1157 /** 1158 * Schedule the timeout message when timeout control is enabled and there is no client 1159 * connect to the instance. 1160 * 1161 * @param instance The key of the {@code mSoftApTimeoutMessageMap}, 1162 * @see mSoftApTimeoutMessageMap for details. 1163 */ rescheduleTimeoutMessageIfNeeded(String instance)1164 private void rescheduleTimeoutMessageIfNeeded(String instance) { 1165 final boolean isTetheringInterface = 1166 TextUtils.equals(mApInterfaceName, instance); 1167 final boolean timeoutEnabled = isTetheringInterface ? mTimeoutEnabled 1168 : (mBridgedModeOpportunisticsShutdownTimeoutEnabled && !mIsCharging); 1169 final int clientNumber = isTetheringInterface 1170 ? getConnectedClientList().size() 1171 : mConnectedClientWithApInfoMap.get(instance).size(); 1172 final long timeoutValue = isTetheringInterface 1173 ? getShutdownTimeoutMillis() 1174 : getShutdownIdleInstanceInBridgedModeTimeoutMillis(); 1175 Log.d(getTag(), "rescheduleTimeoutMessageIfNeeded " + instance + ", timeoutEnabled=" 1176 + timeoutEnabled + ", isCharging" + mIsCharging + ", clientNumber=" 1177 + clientNumber); 1178 if (!timeoutEnabled || clientNumber != 0) { 1179 cancelTimeoutMessage(instance); 1180 return; 1181 } 1182 scheduleTimeoutMessage(instance, timeoutValue); 1183 } 1184 scheduleTimeoutMessage(String instance, long timeout)1185 private void scheduleTimeoutMessage(String instance, long timeout) { 1186 if (mSoftApTimeoutMessageMap.containsKey(instance)) { 1187 mSoftApTimeoutMessageMap.get(instance).schedule( 1188 SystemClock.elapsedRealtime() + timeout); 1189 Log.d(getTag(), "Timeout message scheduled, on " + instance + ", delay = " 1190 + timeout); 1191 } 1192 } 1193 cancelTimeoutMessage(String instance)1194 private void cancelTimeoutMessage(String instance) { 1195 if (mSoftApTimeoutMessageMap.containsKey(instance)) { 1196 mSoftApTimeoutMessageMap.get(instance).cancel(); 1197 Log.d(getTag(), "Timeout message canceled on " + instance); 1198 } 1199 } 1200 1201 /** 1202 * When configuration changed, it need to force some clients disconnect to match the 1203 * configuration. 1204 */ updateClientConnection()1205 private void updateClientConnection() { 1206 if (!mCurrentSoftApCapability.areFeaturesSupported( 1207 SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT)) { 1208 return; 1209 } 1210 final int maxAllowedClientsByHardwareAndCarrier = 1211 mCurrentSoftApCapability.getMaxSupportedClients(); 1212 final int userApConfigMaxClientCount = 1213 mCurrentSoftApConfiguration.getMaxNumberOfClients(); 1214 int finalMaxClientCount = maxAllowedClientsByHardwareAndCarrier; 1215 if (userApConfigMaxClientCount > 0) { 1216 finalMaxClientCount = Math.min(userApConfigMaxClientCount, 1217 maxAllowedClientsByHardwareAndCarrier); 1218 } 1219 List<WifiClient> currentClients = getConnectedClientList(); 1220 int targetDisconnectClientNumber = currentClients.size() - finalMaxClientCount; 1221 List<WifiClient> allowedConnectedList = new ArrayList<>(); 1222 Iterator<WifiClient> iterator = currentClients.iterator(); 1223 while (iterator.hasNext()) { 1224 WifiClient client = iterator.next(); 1225 if (mBlockedClientList.contains(client.getMacAddress()) 1226 || (mCurrentSoftApConfiguration.isClientControlByUserEnabled() 1227 && !mAllowedClientList.contains(client.getMacAddress()))) { 1228 Log.d(getTag(), "Force disconnect for not allowed client: " + client); 1229 if (!mWifiNative.forceClientDisconnect( 1230 mApInterfaceName, client.getMacAddress(), 1231 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER)) { 1232 addClientToPendingDisconnectionList(client, 1233 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER); 1234 } 1235 targetDisconnectClientNumber--; 1236 } else { 1237 allowedConnectedList.add(client); 1238 } 1239 } 1240 1241 if (targetDisconnectClientNumber > 0) { 1242 Iterator<WifiClient> allowedClientIterator = allowedConnectedList.iterator(); 1243 while (allowedClientIterator.hasNext()) { 1244 if (targetDisconnectClientNumber == 0) break; 1245 WifiClient allowedClient = allowedClientIterator.next(); 1246 Log.d(getTag(), "Force disconnect for client due to no more room: " 1247 + allowedClient); 1248 if (!mWifiNative.forceClientDisconnect( 1249 mApInterfaceName, allowedClient.getMacAddress(), 1250 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS)) { 1251 addClientToPendingDisconnectionList(allowedClient, 1252 WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS); 1253 } 1254 targetDisconnectClientNumber--; 1255 } 1256 } 1257 } 1258 1259 /** 1260 * Set stations associated with this soft AP 1261 * @param client The station for which connection state changed. 1262 * @param isConnected True for the connection changed to connect, otherwise false. 1263 */ updateConnectedClients(WifiClient client, boolean isConnected)1264 private void updateConnectedClients(WifiClient client, boolean isConnected) { 1265 if (client == null) { 1266 return; 1267 } 1268 1269 if (null != mPendingDisconnectClients.remove(client)) { 1270 Log.d(getTag(), "Remove client: " + client.getMacAddress() 1271 + "from pending disconnectionlist"); 1272 } 1273 1274 String apInstanceIdentifier = client.getApInstanceIdentifier(); 1275 List clientList = mConnectedClientWithApInfoMap.computeIfAbsent( 1276 apInstanceIdentifier, k -> new ArrayList<>()); 1277 int index = clientList.indexOf(client); 1278 1279 if ((index != -1) == isConnected) { 1280 Log.e(getTag(), "Drop client connection event, client " 1281 + client + "isConnected: " + isConnected 1282 + " , duplicate event or client is blocked"); 1283 return; 1284 } 1285 if (isConnected) { 1286 boolean isAllow = checkSoftApClient(mCurrentSoftApConfiguration, client); 1287 if (isAllow) { 1288 clientList.add(client); 1289 } else { 1290 return; 1291 } 1292 } else { 1293 if (null == clientList.remove(index)) { 1294 Log.e(getTag(), "client doesn't exist in list, it should NOT happen"); 1295 } 1296 } 1297 1298 // Update clients list. 1299 mConnectedClientWithApInfoMap.put(apInstanceIdentifier, clientList); 1300 SoftApInfo currentInfoWithClientsChanged = mCurrentSoftApInfoMap 1301 .get(apInstanceIdentifier); 1302 Log.d(getTag(), "The connected wifi stations have changed with count: " 1303 + clientList.size() + ": " + clientList + " on the AP which info is " 1304 + currentInfoWithClientsChanged); 1305 1306 if (mSoftApCallback != null) { 1307 mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap, 1308 mConnectedClientWithApInfoMap, isBridgeRequired()); 1309 } else { 1310 Log.e(getTag(), 1311 "SoftApCallback is null. Dropping ConnectedClientsChanged event."); 1312 } 1313 1314 mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent( 1315 getConnectedClientList().size(), 1316 mConnectedClientWithApInfoMap.get(apInstanceIdentifier).size(), 1317 mOriginalModeConfiguration.getTargetMode(), 1318 mCurrentSoftApInfoMap.get(apInstanceIdentifier)); 1319 1320 rescheduleTimeoutMessages(apInstanceIdentifier); 1321 } 1322 1323 /** 1324 * @param apInfo, the new SoftApInfo changed. Null used to clean up. 1325 */ updateSoftApInfo(@ullable SoftApInfo apInfo, boolean isRemoved)1326 private void updateSoftApInfo(@Nullable SoftApInfo apInfo, boolean isRemoved) { 1327 Log.d(getTag(), "SoftApInfo update " + apInfo + ", isRemoved: " + isRemoved); 1328 if (apInfo == null) { 1329 // Clean up 1330 mCurrentSoftApInfoMap.clear(); 1331 mConnectedClientWithApInfoMap.clear(); 1332 mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap, 1333 mConnectedClientWithApInfoMap, isBridgeRequired()); 1334 return; 1335 } 1336 String changedInstance = apInfo.getApInstanceIdentifier(); 1337 if (apInfo.equals(mCurrentSoftApInfoMap.get(changedInstance))) { 1338 if (isRemoved) { 1339 boolean isClientConnected = 1340 mConnectedClientWithApInfoMap.get(changedInstance).size() > 0; 1341 mCurrentSoftApInfoMap.remove(changedInstance); 1342 mSoftApTimeoutMessageMap.remove(changedInstance); 1343 mConnectedClientWithApInfoMap.remove(changedInstance); 1344 mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap, 1345 mConnectedClientWithApInfoMap, isBridgeRequired()); 1346 if (isClientConnected) { 1347 mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent( 1348 getConnectedClientList().size(), 0, 1349 mOriginalModeConfiguration.getTargetMode(), apInfo); 1350 } 1351 if (isBridgeRequired()) { 1352 mWifiMetrics.addSoftApInstanceDownEventInDualMode( 1353 mOriginalModeConfiguration.getTargetMode(), apInfo); 1354 } 1355 } 1356 return; 1357 } 1358 1359 // Make sure an empty client list is created when info updated 1360 List clientList = mConnectedClientWithApInfoMap.computeIfAbsent( 1361 changedInstance, k -> new ArrayList<>()); 1362 1363 if (clientList.size() != 0) { 1364 Log.e(getTag(), "The info: " + apInfo 1365 + " changed when client connected, it should NOT happen!!"); 1366 } 1367 1368 mCurrentSoftApInfoMap.put(changedInstance, new SoftApInfo(apInfo)); 1369 mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap, 1370 mConnectedClientWithApInfoMap, isBridgeRequired()); 1371 1372 boolean isNeedToScheduleTimeoutMessage = false; 1373 if (!mSoftApTimeoutMessageMap.containsKey(mApInterfaceName)) { 1374 // First info update, create WakeupMessage for mApInterfaceName. 1375 mSoftApTimeoutMessageMap.put(mApInterfaceName, new WakeupMessage( 1376 mContext, mStateMachine.getHandler(), 1377 SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + mApInterfaceName, 1378 SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT)); 1379 isNeedToScheduleTimeoutMessage = true; 1380 } 1381 1382 if (isBridgedMode() 1383 && !mSoftApTimeoutMessageMap.containsKey(changedInstance)) { 1384 mSoftApTimeoutMessageMap.put(changedInstance, 1385 new WakeupMessage(mContext, mStateMachine.getHandler(), 1386 SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + changedInstance, 1387 SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT_ON_ONE_INSTANCE, 1388 0, 0, changedInstance)); 1389 isNeedToScheduleTimeoutMessage = true; 1390 } 1391 1392 // Trigger schedule after mCurrentSoftApInfoMap is updated. 1393 if (isNeedToScheduleTimeoutMessage) { 1394 rescheduleTimeoutMessages(mApInterfaceName); 1395 } 1396 1397 // ignore invalid freq and softap disable case for metrics 1398 if (apInfo.getFrequency() > 0 1399 && apInfo.getBandwidth() != SoftApInfo.CHANNEL_WIDTH_INVALID) { 1400 mWifiMetrics.addSoftApChannelSwitchedEvent( 1401 new ArrayList<>(mCurrentSoftApInfoMap.values()), 1402 mOriginalModeConfiguration.getTargetMode(), isBridgeRequired()); 1403 updateUserBandPreferenceViolationMetricsIfNeeded(apInfo); 1404 } 1405 } 1406 onUpChanged(boolean isUp)1407 private void onUpChanged(boolean isUp) { 1408 if (isUp == mIfaceIsUp) { 1409 return; // no change 1410 } 1411 1412 mIfaceIsUp = isUp; 1413 if (isUp) { 1414 Log.d(getTag(), "SoftAp is ready for use"); 1415 updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 1416 WifiManager.WIFI_AP_STATE_ENABLING, 0); 1417 mModeListener.onStarted(SoftApManager.this); 1418 mWifiMetrics.incrementSoftApStartResult(true, 0); 1419 mCurrentSoftApInfoMap.clear(); 1420 mConnectedClientWithApInfoMap.clear(); 1421 if (mSoftApCallback != null) { 1422 mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap, 1423 mConnectedClientWithApInfoMap, isBridgeRequired()); 1424 } 1425 } else { 1426 // the interface was up, but goes down 1427 sendMessage(CMD_INTERFACE_DOWN); 1428 } 1429 mWifiMetrics.addSoftApUpChangedEvent(isUp, 1430 mOriginalModeConfiguration.getTargetMode(), 1431 mDefaultShutdownTimeoutMillis, isBridgeRequired()); 1432 if (isUp) { 1433 mWifiMetrics.updateSoftApConfiguration(mCurrentSoftApConfiguration, 1434 mOriginalModeConfiguration.getTargetMode(), isBridgeRequired()); 1435 mWifiMetrics.updateSoftApCapability(mCurrentSoftApCapability, 1436 mOriginalModeConfiguration.getTargetMode(), isBridgeRequired()); 1437 } 1438 } 1439 1440 @Override enter()1441 public void enter() { 1442 mIfaceIsUp = false; 1443 mIfaceIsDestroyed = false; 1444 onUpChanged(mWifiNative.isInterfaceUp(mApInterfaceName)); 1445 1446 Handler handler = mStateMachine.getHandler(); 1447 if (SdkLevel.isAtLeastS()) { 1448 mCoexManager.registerCoexListener(mCoexListener); 1449 } 1450 if (mIsDisableShutDownBridgedModeIdleInstanceTimerWhenCharging) { 1451 IntentFilter filter = new IntentFilter(); 1452 filter.addAction(Intent.ACTION_POWER_CONNECTED); 1453 filter.addAction(Intent.ACTION_POWER_DISCONNECTED); 1454 mContext.registerReceiver(mBatteryChargingReceiver, filter); 1455 mIsCharging = mBatteryManager.isCharging(); 1456 } 1457 mSarManager.setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED); 1458 Log.d(getTag(), "Resetting connected clients on start"); 1459 mConnectedClientWithApInfoMap.clear(); 1460 mPendingDisconnectClients.clear(); 1461 mEverReportMetricsForMaxClient = false; 1462 } 1463 1464 @Override exit()1465 public void exit() { 1466 if (!mIfaceIsDestroyed) { 1467 stopSoftAp(); 1468 } 1469 if (SdkLevel.isAtLeastS()) { 1470 mCoexManager.unregisterCoexListener(mCoexListener); 1471 } 1472 if (getConnectedClientList().size() != 0) { 1473 Log.d(getTag(), "Resetting num stations on stop"); 1474 for (List<WifiClient> it : mConnectedClientWithApInfoMap.values()) { 1475 if (it.size() != 0) { 1476 mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent( 1477 0, 0, mOriginalModeConfiguration.getTargetMode(), 1478 mCurrentSoftApInfoMap 1479 .get(it.get(0).getApInstanceIdentifier())); 1480 } 1481 } 1482 mConnectedClientWithApInfoMap.clear(); 1483 if (mSoftApCallback != null) { 1484 mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap, 1485 mConnectedClientWithApInfoMap, isBridgeRequired()); 1486 } 1487 } 1488 mPendingDisconnectClients.clear(); 1489 for (String key : mSoftApTimeoutMessageMap.keySet()) { 1490 cancelTimeoutMessage(key); 1491 } 1492 mSoftApTimeoutMessageMap.clear(); 1493 if (mIsDisableShutDownBridgedModeIdleInstanceTimerWhenCharging) { 1494 mContext.unregisterReceiver(mBatteryChargingReceiver); 1495 } 1496 // Need this here since we are exiting |Started| state and won't handle any 1497 // future CMD_INTERFACE_STATUS_CHANGED events after this point 1498 mWifiMetrics.addSoftApUpChangedEvent(false, 1499 mOriginalModeConfiguration.getTargetMode(), 1500 mDefaultShutdownTimeoutMillis, isBridgeRequired()); 1501 updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 1502 WifiManager.WIFI_AP_STATE_DISABLING, 0); 1503 1504 mSarManager.setSapWifiState(WifiManager.WIFI_AP_STATE_DISABLED); 1505 1506 mApInterfaceName = null; 1507 mIfaceIsUp = false; 1508 mIfaceIsDestroyed = false; 1509 mRole = null; 1510 updateSoftApInfo(null, false); 1511 } 1512 updateUserBandPreferenceViolationMetricsIfNeeded(SoftApInfo apInfo)1513 private void updateUserBandPreferenceViolationMetricsIfNeeded(SoftApInfo apInfo) { 1514 // The band preference violation only need to detect in single AP mode. 1515 if (isBridgeRequired()) return; 1516 int band = mCurrentSoftApConfiguration.getBand(); 1517 boolean bandPreferenceViolated = 1518 (ScanResult.is24GHz(apInfo.getFrequency()) 1519 && !ApConfigUtil.containsBand(band, 1520 SoftApConfiguration.BAND_2GHZ)) 1521 || (ScanResult.is5GHz(apInfo.getFrequency()) 1522 && !ApConfigUtil.containsBand(band, 1523 SoftApConfiguration.BAND_5GHZ)) 1524 || (ScanResult.is6GHz(apInfo.getFrequency()) 1525 && !ApConfigUtil.containsBand(band, 1526 SoftApConfiguration.BAND_6GHZ)); 1527 1528 if (bandPreferenceViolated) { 1529 Log.e(getTag(), "Channel does not satisfy user band preference: " 1530 + apInfo.getFrequency()); 1531 mWifiMetrics.incrementNumSoftApUserBandPreferenceUnsatisfied(); 1532 } 1533 } 1534 1535 @Override processMessage(Message message)1536 public boolean processMessage(Message message) { 1537 switch (message.what) { 1538 case CMD_ASSOCIATED_STATIONS_CHANGED: 1539 if (!(message.obj instanceof WifiClient)) { 1540 Log.e(getTag(), "Invalid type returned for" 1541 + " CMD_ASSOCIATED_STATIONS_CHANGED"); 1542 break; 1543 } 1544 boolean isConnected = (message.arg1 == 1); 1545 WifiClient client = (WifiClient) message.obj; 1546 Log.d(getTag(), "CMD_ASSOCIATED_STATIONS_CHANGED, Client: " 1547 + client.getMacAddress().toString() + " isConnected: " 1548 + isConnected); 1549 updateConnectedClients(client, isConnected); 1550 break; 1551 case CMD_AP_INFO_CHANGED: 1552 if (!(message.obj instanceof SoftApInfo)) { 1553 Log.e(getTag(), "Invalid type returned for" 1554 + " CMD_AP_INFO_CHANGED"); 1555 break; 1556 } 1557 SoftApInfo apInfo = (SoftApInfo) message.obj; 1558 if (apInfo.getFrequency() < 0) { 1559 Log.e(getTag(), "Invalid ap channel frequency: " 1560 + apInfo.getFrequency()); 1561 break; 1562 } 1563 // Update shutdown timeout 1564 apInfo.setAutoShutdownTimeoutMillis(mTimeoutEnabled 1565 ? getShutdownTimeoutMillis() : 0); 1566 updateSoftApInfo(apInfo, false); 1567 break; 1568 case CMD_INTERFACE_STATUS_CHANGED: 1569 boolean isUp = message.arg1 == 1; 1570 onUpChanged(isUp); 1571 break; 1572 case CMD_STOP: 1573 if (mIfaceIsUp) { 1574 updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 1575 WifiManager.WIFI_AP_STATE_ENABLED, 0); 1576 } else { 1577 updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 1578 WifiManager.WIFI_AP_STATE_ENABLING, 0); 1579 } 1580 quitNow(); 1581 break; 1582 case CMD_START: 1583 // Already started, ignore this command. 1584 break; 1585 case CMD_NO_ASSOCIATED_STATIONS_TIMEOUT: 1586 if (!mTimeoutEnabled) { 1587 Log.wtf(getTag(), "Timeout message received while timeout is disabled." 1588 + " Dropping."); 1589 break; 1590 } 1591 if (getConnectedClientList().size() != 0) { 1592 Log.wtf(getTag(), "Timeout message received but has clients. " 1593 + "Dropping."); 1594 break; 1595 } 1596 mSoftApNotifier.showSoftApShutdownTimeoutExpiredNotification(); 1597 Log.i(getTag(), "Timeout message received. Stopping soft AP."); 1598 updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 1599 WifiManager.WIFI_AP_STATE_ENABLED, 0); 1600 quitNow(); 1601 break; 1602 case CMD_NO_ASSOCIATED_STATIONS_TIMEOUT_ON_ONE_INSTANCE: 1603 String idleInstance = (String) message.obj; 1604 if (!isBridgedMode() || mCurrentSoftApInfoMap.size() != 2) { 1605 Log.wtf(getTag(), "Ignore Bridged Mode Timeout message received" 1606 + " in single AP state. Dropping it from " + idleInstance); 1607 break; 1608 } 1609 if (!mBridgedModeOpportunisticsShutdownTimeoutEnabled) { 1610 Log.wtf(getTag(), "Bridged Mode Timeout message received" 1611 + " while timeout is disabled. Dropping."); 1612 break; 1613 } 1614 Log.d(getTag(), "Instance idle timout on " + idleInstance); 1615 removeIfaceInstanceFromBridgedApIface(idleInstance); 1616 break; 1617 case CMD_INTERFACE_DESTROYED: 1618 Log.d(getTag(), "Interface was cleanly destroyed."); 1619 updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 1620 WifiManager.WIFI_AP_STATE_ENABLED, 0); 1621 mIfaceIsDestroyed = true; 1622 quitNow(); 1623 break; 1624 case CMD_FAILURE: 1625 String instance = (String) message.obj; 1626 if (instance != null && isBridgedMode() 1627 && mCurrentSoftApInfoMap.size() >= 1) { 1628 Log.i(getTag(), "receive instanceFailure on " + instance); 1629 removeIfaceInstanceFromBridgedApIface(instance); 1630 // there is a available instance, keep AP on. 1631 if (mCurrentSoftApInfoMap.size() == 1) { 1632 break; 1633 } 1634 } 1635 Log.w(getTag(), "hostapd failure, stop and report failure"); 1636 /* fall through */ 1637 case CMD_INTERFACE_DOWN: 1638 Log.w(getTag(), "interface error, stop and report failure"); 1639 updateApState(WifiManager.WIFI_AP_STATE_FAILED, 1640 WifiManager.WIFI_AP_STATE_ENABLED, 1641 WifiManager.SAP_START_FAILURE_GENERAL); 1642 updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 1643 WifiManager.WIFI_AP_STATE_FAILED, 0); 1644 quitNow(); 1645 break; 1646 case CMD_UPDATE_CAPABILITY: 1647 SoftApCapability capability = (SoftApCapability) message.obj; 1648 mCurrentSoftApCapability = new SoftApCapability(capability); 1649 mWifiMetrics.updateSoftApCapability(mCurrentSoftApCapability, 1650 mOriginalModeConfiguration.getTargetMode(), isBridgeRequired()); 1651 updateClientConnection(); 1652 updateSafeChannelFrequencyList(); 1653 break; 1654 case CMD_UPDATE_CONFIG: 1655 SoftApConfiguration newConfig = (SoftApConfiguration) message.obj; 1656 SoftApConfiguration originalConfig = 1657 mOriginalModeConfiguration.getSoftApConfiguration(); 1658 if (!ApConfigUtil.checkConfigurationChangeNeedToRestart( 1659 originalConfig, newConfig)) { 1660 Log.d(getTag(), "Configuration changed to " + newConfig); 1661 if (mCurrentSoftApConfiguration.getMaxNumberOfClients() 1662 != newConfig.getMaxNumberOfClients()) { 1663 Log.d(getTag(), "Max Client changed, reset to record the metrics"); 1664 mEverReportMetricsForMaxClient = false; 1665 } 1666 boolean needRescheduleTimeoutMessage = 1667 mCurrentSoftApConfiguration.getShutdownTimeoutMillis() 1668 != newConfig.getShutdownTimeoutMillis() 1669 || mTimeoutEnabled != newConfig.isAutoShutdownEnabled() 1670 || mBridgedModeOpportunisticsShutdownTimeoutEnabled 1671 != newConfig 1672 .isBridgedModeOpportunisticShutdownEnabledInternal(); 1673 updateChangeableConfiguration(newConfig); 1674 updateClientConnection(); 1675 if (needRescheduleTimeoutMessage) { 1676 for (String key : mSoftApTimeoutMessageMap.keySet()) { 1677 cancelTimeoutMessage(key); 1678 } 1679 rescheduleTimeoutMessages(mApInterfaceName); 1680 // Update SoftApInfo 1681 for (SoftApInfo info : mCurrentSoftApInfoMap.values()) { 1682 SoftApInfo newInfo = new SoftApInfo(info); 1683 newInfo.setAutoShutdownTimeoutMillis(mTimeoutEnabled 1684 ? getShutdownTimeoutMillis() : 0); 1685 updateSoftApInfo(newInfo, false); 1686 } 1687 } 1688 mWifiMetrics.updateSoftApConfiguration( 1689 mCurrentSoftApConfiguration, 1690 mOriginalModeConfiguration.getTargetMode(), 1691 isBridgeRequired()); 1692 } else { 1693 Log.d(getTag(), "Ignore the config: " + newConfig 1694 + " update since it requires restart"); 1695 } 1696 break; 1697 case CMD_UPDATE_COUNTRY_CODE: 1698 String countryCode = (String) message.obj; 1699 if (!TextUtils.isEmpty(countryCode) 1700 && !TextUtils.equals(mCountryCode, countryCode) 1701 && mWifiNative.setApCountryCode( 1702 mApInterfaceName, countryCode.toUpperCase(Locale.ROOT))) { 1703 Log.i(getTag(), "Update country code when Soft AP enabled from " 1704 + mCountryCode + " to " + countryCode); 1705 mCountryCode = countryCode; 1706 } 1707 break; 1708 case CMD_FORCE_DISCONNECT_PENDING_CLIENTS: 1709 if (mPendingDisconnectClients.size() != 0) { 1710 Log.d(getTag(), "Disconnect pending list is NOT empty"); 1711 mPendingDisconnectClients.forEach((pendingClient, reason)-> 1712 mWifiNative.forceClientDisconnect(mApInterfaceName, 1713 pendingClient.getMacAddress(), reason)); 1714 sendMessageDelayed( 1715 SoftApStateMachine.CMD_FORCE_DISCONNECT_PENDING_CLIENTS, 1716 SOFT_AP_PENDING_DISCONNECTION_CHECK_DELAY_MS); 1717 } 1718 break; 1719 case CMD_SAFE_CHANNEL_FREQUENCY_CHANGED: 1720 updateSafeChannelFrequencyList(); 1721 if (!isBridgedMode() || mCurrentSoftApInfoMap.size() != 2) { 1722 Log.d(getTag(), "Ignore safe channel changed in single AP state"); 1723 break; 1724 } 1725 Set<String> unavailableInstances = new HashSet<>(); 1726 for (SoftApInfo currentInfo : mCurrentSoftApInfoMap.values()) { 1727 int sapFreq = currentInfo.getFrequency(); 1728 if (!mSafeChannelFrequencyList.contains(sapFreq)) { 1729 int sapBand = ApConfigUtil.convertFrequencyToBand(sapFreq); 1730 if (sapBand != ApConfigUtil.removeUnavailableBands( 1731 mCurrentSoftApCapability, 1732 sapBand, mCoexManager)) { 1733 unavailableInstances.add(currentInfo.getApInstanceIdentifier()); 1734 } 1735 } 1736 } 1737 removeIfaceInstanceFromBridgedApIface( 1738 getHighestFrequencyInstance(unavailableInstances)); 1739 break; 1740 case CMD_HANDLE_WIFI_CONNECTED: 1741 if (!isBridgeRequired() || mCurrentSoftApInfoMap.size() != 2) { 1742 Log.d(getTag(), "Ignore wifi connected in single AP state"); 1743 break; 1744 } 1745 WifiInfo wifiInfo = (WifiInfo) message.obj; 1746 int wifiFreq = wifiInfo.getFrequency(); 1747 String targetShutDownInstance = ""; 1748 if (wifiFreq > 0 && !mSafeChannelFrequencyList.contains(wifiFreq)) { 1749 Log.i(getTag(), "Wifi connected to freq:" + wifiFreq 1750 + " which is unavailable for SAP"); 1751 for (SoftApInfo sapInfo : mCurrentSoftApInfoMap.values()) { 1752 if (ApConfigUtil.convertFrequencyToBand(sapInfo.getFrequency()) 1753 == ApConfigUtil.convertFrequencyToBand(wifiFreq)) { 1754 targetShutDownInstance = sapInfo.getApInstanceIdentifier(); 1755 Log.d(getTag(), "Remove the " + targetShutDownInstance 1756 + " instance which is running on the same band as " 1757 + "the wifi connection on an unsafe channel"); 1758 break; 1759 } 1760 } 1761 // Wifi may connect to different band as the SAP. For instances: 1762 // Wifi connect to 6Ghz but bridged AP is running on 2.4Ghz + 5Ghz. 1763 // In this case, targetShutDownInstance will be empty, shutdown the 1764 // highest frequency instance. 1765 removeIfaceInstanceFromBridgedApIface( 1766 TextUtils.isEmpty(targetShutDownInstance) 1767 ? getHighestFrequencyInstance(mCurrentSoftApInfoMap.keySet()) 1768 : targetShutDownInstance); 1769 } 1770 break; 1771 case CMD_CHARGING_STATE_CHANGED: 1772 boolean newIsCharging = (message.arg1 != 0); 1773 if (mIsCharging != newIsCharging) { 1774 mIsCharging = newIsCharging; 1775 if (mCurrentSoftApInfoMap.size() == 2) { 1776 for (String apInstance : mCurrentSoftApInfoMap.keySet()) { 1777 rescheduleTimeoutMessageIfNeeded(apInstance); 1778 } 1779 } 1780 } 1781 break; 1782 default: 1783 return NOT_HANDLED; 1784 } 1785 return HANDLED; 1786 } 1787 } 1788 } 1789 } 1790