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 com.android.server.wifi.util.ApConfigUtil.ERROR_GENERIC; 20 import static com.android.server.wifi.util.ApConfigUtil.ERROR_NO_CHANNEL; 21 import static com.android.server.wifi.util.ApConfigUtil.SUCCESS; 22 23 import android.annotation.NonNull; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.database.ContentObserver; 27 import android.net.wifi.WifiConfiguration; 28 import android.net.wifi.WifiManager; 29 import android.net.wifi.WifiScanner; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.os.Message; 33 import android.os.SystemClock; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.text.TextUtils; 37 import android.util.Log; 38 39 import com.android.internal.R; 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.util.ArrayUtils; 42 import com.android.internal.util.IState; 43 import com.android.internal.util.State; 44 import com.android.internal.util.StateMachine; 45 import com.android.internal.util.WakeupMessage; 46 import com.android.server.wifi.WifiNative.InterfaceCallback; 47 import com.android.server.wifi.WifiNative.SoftApListener; 48 import com.android.server.wifi.util.ApConfigUtil; 49 50 import java.io.FileDescriptor; 51 import java.io.PrintWriter; 52 import java.util.Arrays; 53 import java.util.Locale; 54 import java.util.stream.Stream; 55 56 /** 57 * Manage WiFi in AP mode. 58 * The internal state machine runs under "WifiStateMachine" thread context. 59 */ 60 public class SoftApManager implements ActiveModeManager { 61 private static final String TAG = "SoftApManager"; 62 63 // Minimum limit to use for timeout delay if the value from overlay setting is too small. 64 private static final int MIN_SOFT_AP_TIMEOUT_DELAY_MS = 600_000; // 10 minutes 65 66 @VisibleForTesting 67 public static final String SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG = TAG 68 + " Soft AP Send Message Timeout"; 69 70 private final Context mContext; 71 private final FrameworkFacade mFrameworkFacade; 72 private final WifiNative mWifiNative; 73 74 private final String mCountryCode; 75 76 private final SoftApStateMachine mStateMachine; 77 78 private final WifiManager.SoftApCallback mCallback; 79 80 private String mApInterfaceName; 81 private boolean mIfaceIsUp; 82 83 private final WifiApConfigStore mWifiApConfigStore; 84 85 private final WifiMetrics mWifiMetrics; 86 87 private final int mMode; 88 private WifiConfiguration mApConfig; 89 90 private int mReportedFrequency = -1; 91 private int mReportedBandwidth = -1; 92 93 private int mNumAssociatedStations = 0; 94 private boolean mTimeoutEnabled = false; 95 96 /** 97 * Listener for soft AP events. 98 */ 99 private final SoftApListener mSoftApListener = new SoftApListener() { 100 @Override 101 public void onNumAssociatedStationsChanged(int numStations) { 102 mStateMachine.sendMessage( 103 SoftApStateMachine.CMD_NUM_ASSOCIATED_STATIONS_CHANGED, numStations); 104 } 105 106 @Override 107 public void onSoftApChannelSwitched(int frequency, int bandwidth) { 108 mStateMachine.sendMessage( 109 SoftApStateMachine.CMD_SOFT_AP_CHANNEL_SWITCHED, frequency, bandwidth); 110 } 111 }; 112 113 public SoftApManager(@NonNull Context context, 114 @NonNull Looper looper, 115 @NonNull FrameworkFacade framework, 116 @NonNull WifiNative wifiNative, 117 String countryCode, 118 @NonNull WifiManager.SoftApCallback callback, 119 @NonNull WifiApConfigStore wifiApConfigStore, 120 @NonNull SoftApModeConfiguration apConfig, 121 @NonNull WifiMetrics wifiMetrics) { 122 mContext = context; 123 mFrameworkFacade = framework; 124 mWifiNative = wifiNative; 125 mCountryCode = countryCode; 126 mCallback = callback; 127 mWifiApConfigStore = wifiApConfigStore; 128 mMode = apConfig.getTargetMode(); 129 WifiConfiguration config = apConfig.getWifiConfiguration(); 130 if (config == null) { 131 mApConfig = mWifiApConfigStore.getApConfiguration(); 132 } else { 133 mApConfig = config; 134 } 135 mWifiMetrics = wifiMetrics; 136 mStateMachine = new SoftApStateMachine(looper); 137 } 138 139 /** 140 * Start soft AP with the supplied config. 141 */ 142 public void start() { 143 mStateMachine.sendMessage(SoftApStateMachine.CMD_START, mApConfig); 144 } 145 146 /** 147 * Stop soft AP. 148 */ 149 public void stop() { 150 Log.d(TAG, " currentstate: " + getCurrentStateName()); 151 if (mApInterfaceName != null) { 152 if (mIfaceIsUp) { 153 updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 154 WifiManager.WIFI_AP_STATE_ENABLED, 0); 155 } else { 156 updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 157 WifiManager.WIFI_AP_STATE_ENABLING, 0); 158 } 159 } 160 mStateMachine.quitNow(); 161 } 162 163 /** 164 * Dump info about this softap manager. 165 */ 166 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 167 pw.println("--Dump of SoftApManager--"); 168 169 pw.println("current StateMachine mode: " + getCurrentStateName()); 170 pw.println("mApInterfaceName: " + mApInterfaceName); 171 pw.println("mIfaceIsUp: " + mIfaceIsUp); 172 pw.println("mMode: " + mMode); 173 pw.println("mCountryCode: " + mCountryCode); 174 if (mApConfig != null) { 175 pw.println("mApConfig.SSID: " + mApConfig.SSID); 176 pw.println("mApConfig.apBand: " + mApConfig.apBand); 177 pw.println("mApConfig.hiddenSSID: " + mApConfig.hiddenSSID); 178 } else { 179 pw.println("mApConfig: null"); 180 } 181 pw.println("mNumAssociatedStations: " + mNumAssociatedStations); 182 pw.println("mTimeoutEnabled: " + mTimeoutEnabled); 183 pw.println("mReportedFrequency: " + mReportedFrequency); 184 pw.println("mReportedBandwidth: " + mReportedBandwidth); 185 } 186 187 private String getCurrentStateName() { 188 IState currentState = mStateMachine.getCurrentState(); 189 190 if (currentState != null) { 191 return currentState.getName(); 192 } 193 194 return "StateMachine not active"; 195 } 196 197 /** 198 * Update AP state. 199 * @param newState new AP state 200 * @param currentState current AP state 201 * @param reason Failure reason if the new AP state is in failure state 202 */ 203 private void updateApState(int newState, int currentState, int reason) { 204 mCallback.onStateChanged(newState, reason); 205 206 //send the AP state change broadcast 207 final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); 208 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 209 intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, newState); 210 intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, currentState); 211 if (newState == WifiManager.WIFI_AP_STATE_FAILED) { 212 //only set reason number when softAP start failed 213 intent.putExtra(WifiManager.EXTRA_WIFI_AP_FAILURE_REASON, reason); 214 } 215 216 intent.putExtra(WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME, mApInterfaceName); 217 intent.putExtra(WifiManager.EXTRA_WIFI_AP_MODE, mMode); 218 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 219 } 220 221 /** 222 * Start a soft AP instance with the given configuration. 223 * @param config AP configuration 224 * @return integer result code 225 */ 226 private int startSoftAp(WifiConfiguration config) { 227 if (config == null || config.SSID == null) { 228 Log.e(TAG, "Unable to start soft AP without valid configuration"); 229 return ERROR_GENERIC; 230 } 231 232 // Make a copy of configuration for updating AP band and channel. 233 WifiConfiguration localConfig = new WifiConfiguration(config); 234 235 int result = ApConfigUtil.updateApChannelConfig( 236 mWifiNative, mCountryCode, 237 mWifiApConfigStore.getAllowed2GChannel(), localConfig); 238 239 if (result != SUCCESS) { 240 Log.e(TAG, "Failed to update AP band and channel"); 241 return result; 242 } 243 244 // Setup country code if it is provided. 245 if (mCountryCode != null) { 246 // Country code is mandatory for 5GHz band, return an error if failed to set 247 // country code when AP is configured for 5GHz band. 248 if (!mWifiNative.setCountryCodeHal( 249 mApInterfaceName, mCountryCode.toUpperCase(Locale.ROOT)) 250 && config.apBand == WifiConfiguration.AP_BAND_5GHZ) { 251 Log.e(TAG, "Failed to set country code, required for setting up " 252 + "soft ap in 5GHz"); 253 return ERROR_GENERIC; 254 } 255 } 256 if (localConfig.hiddenSSID) { 257 Log.d(TAG, "SoftAP is a hidden network"); 258 } 259 if (!mWifiNative.startSoftAp(mApInterfaceName, localConfig, mSoftApListener)) { 260 Log.e(TAG, "Soft AP start failed"); 261 return ERROR_GENERIC; 262 } 263 Log.d(TAG, "Soft AP is started"); 264 265 return SUCCESS; 266 } 267 268 /** 269 * Teardown soft AP and teardown the interface. 270 */ 271 private void stopSoftAp() { 272 mWifiNative.teardownInterface(mApInterfaceName); 273 Log.d(TAG, "Soft AP is stopped"); 274 } 275 276 private class SoftApStateMachine extends StateMachine { 277 // Commands for the state machine. 278 public static final int CMD_START = 0; 279 public static final int CMD_INTERFACE_STATUS_CHANGED = 3; 280 public static final int CMD_NUM_ASSOCIATED_STATIONS_CHANGED = 4; 281 public static final int CMD_NO_ASSOCIATED_STATIONS_TIMEOUT = 5; 282 public static final int CMD_TIMEOUT_TOGGLE_CHANGED = 6; 283 public static final int CMD_INTERFACE_DESTROYED = 7; 284 public static final int CMD_INTERFACE_DOWN = 8; 285 public static final int CMD_SOFT_AP_CHANNEL_SWITCHED = 9; 286 287 private final State mIdleState = new IdleState(); 288 private final State mStartedState = new StartedState(); 289 290 private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() { 291 @Override 292 public void onDestroyed(String ifaceName) { 293 if (mApInterfaceName != null && mApInterfaceName.equals(ifaceName)) { 294 sendMessage(CMD_INTERFACE_DESTROYED); 295 } 296 } 297 298 @Override 299 public void onUp(String ifaceName) { 300 if (mApInterfaceName != null && mApInterfaceName.equals(ifaceName)) { 301 sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1); 302 } 303 } 304 305 @Override 306 public void onDown(String ifaceName) { 307 if (mApInterfaceName != null && mApInterfaceName.equals(ifaceName)) { 308 sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0); 309 } 310 } 311 }; 312 313 SoftApStateMachine(Looper looper) { 314 super(TAG, looper); 315 316 addState(mIdleState); 317 addState(mStartedState); 318 319 setInitialState(mIdleState); 320 start(); 321 } 322 323 private class IdleState extends State { 324 @Override 325 public void enter() { 326 mApInterfaceName = null; 327 mIfaceIsUp = false; 328 } 329 330 @Override 331 public boolean processMessage(Message message) { 332 switch (message.what) { 333 case CMD_START: 334 mApInterfaceName = mWifiNative.setupInterfaceForSoftApMode( 335 mWifiNativeInterfaceCallback); 336 if (TextUtils.isEmpty(mApInterfaceName)) { 337 Log.e(TAG, "setup failure when creating ap interface."); 338 updateApState(WifiManager.WIFI_AP_STATE_FAILED, 339 WifiManager.WIFI_AP_STATE_DISABLED, 340 WifiManager.SAP_START_FAILURE_GENERAL); 341 mWifiMetrics.incrementSoftApStartResult( 342 false, WifiManager.SAP_START_FAILURE_GENERAL); 343 break; 344 } 345 updateApState(WifiManager.WIFI_AP_STATE_ENABLING, 346 WifiManager.WIFI_AP_STATE_DISABLED, 0); 347 int result = startSoftAp((WifiConfiguration) message.obj); 348 if (result != SUCCESS) { 349 int failureReason = WifiManager.SAP_START_FAILURE_GENERAL; 350 if (result == ERROR_NO_CHANNEL) { 351 failureReason = WifiManager.SAP_START_FAILURE_NO_CHANNEL; 352 } 353 updateApState(WifiManager.WIFI_AP_STATE_FAILED, 354 WifiManager.WIFI_AP_STATE_ENABLING, 355 failureReason); 356 stopSoftAp(); 357 mWifiMetrics.incrementSoftApStartResult(false, failureReason); 358 break; 359 } 360 transitionTo(mStartedState); 361 break; 362 default: 363 // Ignore all other commands. 364 break; 365 } 366 367 return HANDLED; 368 } 369 } 370 371 private class StartedState extends State { 372 private int mTimeoutDelay; 373 private WakeupMessage mSoftApTimeoutMessage; 374 private SoftApTimeoutEnabledSettingObserver mSettingObserver; 375 376 /** 377 * Observer for timeout settings changes. 378 */ 379 private class SoftApTimeoutEnabledSettingObserver extends ContentObserver { 380 SoftApTimeoutEnabledSettingObserver(Handler handler) { 381 super(handler); 382 } 383 384 public void register() { 385 mFrameworkFacade.registerContentObserver(mContext, 386 Settings.Global.getUriFor(Settings.Global.SOFT_AP_TIMEOUT_ENABLED), 387 true, this); 388 mTimeoutEnabled = getValue(); 389 } 390 391 public void unregister() { 392 mFrameworkFacade.unregisterContentObserver(mContext, this); 393 } 394 395 @Override 396 public void onChange(boolean selfChange) { 397 super.onChange(selfChange); 398 mStateMachine.sendMessage(SoftApStateMachine.CMD_TIMEOUT_TOGGLE_CHANGED, 399 getValue() ? 1 : 0); 400 } 401 402 private boolean getValue() { 403 boolean enabled = mFrameworkFacade.getIntegerSetting(mContext, 404 Settings.Global.SOFT_AP_TIMEOUT_ENABLED, 1) == 1; 405 return enabled; 406 } 407 } 408 409 private int getConfigSoftApTimeoutDelay() { 410 int delay = mContext.getResources().getInteger( 411 R.integer.config_wifi_framework_soft_ap_timeout_delay); 412 if (delay < MIN_SOFT_AP_TIMEOUT_DELAY_MS) { 413 delay = MIN_SOFT_AP_TIMEOUT_DELAY_MS; 414 Log.w(TAG, "Overriding timeout delay with minimum limit value"); 415 } 416 Log.d(TAG, "Timeout delay: " + delay); 417 return delay; 418 } 419 420 private void scheduleTimeoutMessage() { 421 if (!mTimeoutEnabled) { 422 return; 423 } 424 mSoftApTimeoutMessage.schedule(SystemClock.elapsedRealtime() + mTimeoutDelay); 425 Log.d(TAG, "Timeout message scheduled"); 426 } 427 428 private void cancelTimeoutMessage() { 429 mSoftApTimeoutMessage.cancel(); 430 Log.d(TAG, "Timeout message canceled"); 431 } 432 433 /** 434 * Set number of stations associated with this soft AP 435 * @param numStations Number of connected stations 436 */ 437 private void setNumAssociatedStations(int numStations) { 438 if (mNumAssociatedStations == numStations) { 439 return; 440 } 441 mNumAssociatedStations = numStations; 442 Log.d(TAG, "Number of associated stations changed: " + mNumAssociatedStations); 443 444 if (mCallback != null) { 445 mCallback.onNumClientsChanged(mNumAssociatedStations); 446 } else { 447 Log.e(TAG, "SoftApCallback is null. Dropping NumClientsChanged event."); 448 } 449 mWifiMetrics.addSoftApNumAssociatedStationsChangedEvent(mNumAssociatedStations, 450 mMode); 451 452 if (mNumAssociatedStations == 0) { 453 scheduleTimeoutMessage(); 454 } else { 455 cancelTimeoutMessage(); 456 } 457 } 458 459 private void onUpChanged(boolean isUp) { 460 if (isUp == mIfaceIsUp) { 461 return; // no change 462 } 463 mIfaceIsUp = isUp; 464 if (isUp) { 465 Log.d(TAG, "SoftAp is ready for use"); 466 updateApState(WifiManager.WIFI_AP_STATE_ENABLED, 467 WifiManager.WIFI_AP_STATE_ENABLING, 0); 468 mWifiMetrics.incrementSoftApStartResult(true, 0); 469 if (mCallback != null) { 470 mCallback.onNumClientsChanged(mNumAssociatedStations); 471 } 472 } else { 473 // the interface was up, but goes down 474 sendMessage(CMD_INTERFACE_DOWN); 475 } 476 mWifiMetrics.addSoftApUpChangedEvent(isUp, mMode); 477 } 478 479 @Override 480 public void enter() { 481 mIfaceIsUp = false; 482 onUpChanged(mWifiNative.isInterfaceUp(mApInterfaceName)); 483 484 mTimeoutDelay = getConfigSoftApTimeoutDelay(); 485 Handler handler = mStateMachine.getHandler(); 486 mSoftApTimeoutMessage = new WakeupMessage(mContext, handler, 487 SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG, 488 SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT); 489 mSettingObserver = new SoftApTimeoutEnabledSettingObserver(handler); 490 491 if (mSettingObserver != null) { 492 mSettingObserver.register(); 493 } 494 Log.d(TAG, "Resetting num stations on start"); 495 mNumAssociatedStations = 0; 496 scheduleTimeoutMessage(); 497 } 498 499 @Override 500 public void exit() { 501 if (mApInterfaceName != null) { 502 stopSoftAp(); 503 } 504 if (mSettingObserver != null) { 505 mSettingObserver.unregister(); 506 } 507 Log.d(TAG, "Resetting num stations on stop"); 508 mNumAssociatedStations = 0; 509 cancelTimeoutMessage(); 510 // Need this here since we are exiting |Started| state and won't handle any 511 // future CMD_INTERFACE_STATUS_CHANGED events after this point 512 mWifiMetrics.addSoftApUpChangedEvent(false, mMode); 513 updateApState(WifiManager.WIFI_AP_STATE_DISABLED, 514 WifiManager.WIFI_AP_STATE_DISABLING, 0); 515 mApInterfaceName = null; 516 mIfaceIsUp = false; 517 mStateMachine.quitNow(); 518 } 519 520 @Override 521 public boolean processMessage(Message message) { 522 switch (message.what) { 523 case CMD_NUM_ASSOCIATED_STATIONS_CHANGED: 524 if (message.arg1 < 0) { 525 Log.e(TAG, "Invalid number of associated stations: " + message.arg1); 526 break; 527 } 528 Log.d(TAG, "Setting num stations on CMD_NUM_ASSOCIATED_STATIONS_CHANGED"); 529 setNumAssociatedStations(message.arg1); 530 break; 531 case CMD_SOFT_AP_CHANNEL_SWITCHED: 532 mReportedFrequency = message.arg1; 533 mReportedBandwidth = message.arg2; 534 Log.d(TAG, "Channel switched. Frequency: " + mReportedFrequency 535 + " Bandwidth: " + mReportedBandwidth); 536 mWifiMetrics.addSoftApChannelSwitchedEvent(mReportedFrequency, 537 mReportedBandwidth, mMode); 538 int[] allowedChannels = new int[0]; 539 if (mApConfig.apBand == WifiConfiguration.AP_BAND_2GHZ) { 540 allowedChannels = 541 mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ); 542 } else if (mApConfig.apBand == WifiConfiguration.AP_BAND_5GHZ) { 543 allowedChannels = 544 mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ); 545 } else if (mApConfig.apBand == WifiConfiguration.AP_BAND_ANY) { 546 int[] allowed2GChannels = 547 mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_24_GHZ); 548 int[] allowed5GChannels = 549 mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ); 550 allowedChannels = Stream.concat( 551 Arrays.stream(allowed2GChannels).boxed(), 552 Arrays.stream(allowed5GChannels).boxed()) 553 .mapToInt(Integer::valueOf) 554 .toArray(); 555 } 556 if (!ArrayUtils.contains(allowedChannels, mReportedFrequency)) { 557 Log.e(TAG, "Channel does not satisfy user band preference: " 558 + mReportedFrequency); 559 mWifiMetrics.incrementNumSoftApUserBandPreferenceUnsatisfied(); 560 } 561 break; 562 case CMD_TIMEOUT_TOGGLE_CHANGED: 563 boolean isEnabled = (message.arg1 == 1); 564 if (mTimeoutEnabled == isEnabled) { 565 break; 566 } 567 mTimeoutEnabled = isEnabled; 568 if (!mTimeoutEnabled) { 569 cancelTimeoutMessage(); 570 } 571 if (mTimeoutEnabled && mNumAssociatedStations == 0) { 572 scheduleTimeoutMessage(); 573 } 574 break; 575 case CMD_INTERFACE_STATUS_CHANGED: 576 boolean isUp = message.arg1 == 1; 577 onUpChanged(isUp); 578 break; 579 case CMD_START: 580 // Already started, ignore this command. 581 break; 582 case CMD_NO_ASSOCIATED_STATIONS_TIMEOUT: 583 if (!mTimeoutEnabled) { 584 Log.wtf(TAG, "Timeout message received while timeout is disabled." 585 + " Dropping."); 586 break; 587 } 588 if (mNumAssociatedStations != 0) { 589 Log.wtf(TAG, "Timeout message received but has clients. Dropping."); 590 break; 591 } 592 Log.i(TAG, "Timeout message received. Stopping soft AP."); 593 updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 594 WifiManager.WIFI_AP_STATE_ENABLED, 0); 595 transitionTo(mIdleState); 596 break; 597 case CMD_INTERFACE_DESTROYED: 598 Log.d(TAG, "Interface was cleanly destroyed."); 599 updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 600 WifiManager.WIFI_AP_STATE_ENABLED, 0); 601 mApInterfaceName = null; 602 transitionTo(mIdleState); 603 break; 604 case CMD_INTERFACE_DOWN: 605 Log.w(TAG, "interface error, stop and report failure"); 606 updateApState(WifiManager.WIFI_AP_STATE_FAILED, 607 WifiManager.WIFI_AP_STATE_ENABLED, 608 WifiManager.SAP_START_FAILURE_GENERAL); 609 updateApState(WifiManager.WIFI_AP_STATE_DISABLING, 610 WifiManager.WIFI_AP_STATE_FAILED, 0); 611 transitionTo(mIdleState); 612 break; 613 default: 614 return NOT_HANDLED; 615 } 616 return HANDLED; 617 } 618 } 619 } 620 } 621