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 17package com.android.server.wifi; 18 19import android.annotation.NonNull; 20import android.content.Context; 21import android.net.wifi.WifiConfiguration; 22import android.net.wifi.WifiManager; 23import android.os.BatteryStats; 24import android.os.Handler; 25import android.os.Looper; 26import android.os.Message; 27import android.os.RemoteException; 28import android.util.ArraySet; 29import android.util.Log; 30 31import com.android.internal.app.IBatteryStats; 32import com.android.internal.util.Protocol; 33import com.android.internal.util.State; 34import com.android.internal.util.StateMachine; 35import com.android.server.wifi.WifiNative.StatusListener; 36 37import java.io.FileDescriptor; 38import java.io.PrintWriter; 39 40/** 41 * This class provides the implementation for different WiFi operating modes. 42 * 43 * NOTE: The class is a WIP and is in active development. It is intended to replace the existing 44 * WifiStateMachine.java class when the rearchitecture is complete. 45 */ 46public class WifiStateMachinePrime { 47 private static final String TAG = "WifiStateMachinePrime"; 48 49 private ModeStateMachine mModeStateMachine; 50 51 // Holder for active mode managers 52 private final ArraySet<ActiveModeManager> mActiveModeManagers; 53 // DefaultModeManager used to service API calls when there are not active mode managers. 54 private DefaultModeManager mDefaultModeManager; 55 56 private final WifiInjector mWifiInjector; 57 private final Context mContext; 58 private final Looper mLooper; 59 private final Handler mHandler; 60 private final WifiNative mWifiNative; 61 private final IBatteryStats mBatteryStats; 62 private final SelfRecovery mSelfRecovery; 63 private BaseWifiDiagnostics mWifiDiagnostics; 64 private final ScanRequestProxy mScanRequestProxy; 65 66 // The base for wifi message types 67 static final int BASE = Protocol.BASE_WIFI; 68 69 // The message identifiers below are mapped to those in WifiStateMachine when applicable. 70 // Start the soft access point 71 static final int CMD_START_AP = BASE + 21; 72 // Indicates soft ap start failed 73 static final int CMD_START_AP_FAILURE = BASE + 22; 74 // Stop the soft access point 75 static final int CMD_STOP_AP = BASE + 23; 76 // Soft access point teardown is completed 77 static final int CMD_AP_STOPPED = BASE + 24; 78 79 // Start Scan Only mode 80 static final int CMD_START_SCAN_ONLY_MODE = BASE + 200; 81 // Indicates that start Scan only mode failed 82 static final int CMD_START_SCAN_ONLY_MODE_FAILURE = BASE + 201; 83 // Indicates that scan only mode stopped 84 static final int CMD_STOP_SCAN_ONLY_MODE = BASE + 202; 85 // ScanOnly mode teardown is complete 86 static final int CMD_SCAN_ONLY_MODE_STOPPED = BASE + 203; 87 // ScanOnly mode failed 88 static final int CMD_SCAN_ONLY_MODE_FAILED = BASE + 204; 89 90 // Start Client mode 91 static final int CMD_START_CLIENT_MODE = BASE + 300; 92 // Indicates that start client mode failed 93 static final int CMD_START_CLIENT_MODE_FAILURE = BASE + 301; 94 // Indicates that client mode stopped 95 static final int CMD_STOP_CLIENT_MODE = BASE + 302; 96 // Client mode teardown is complete 97 static final int CMD_CLIENT_MODE_STOPPED = BASE + 303; 98 // Client mode failed 99 static final int CMD_CLIENT_MODE_FAILED = BASE + 304; 100 101 private StatusListener mWifiNativeStatusListener; 102 103 private WifiManager.SoftApCallback mSoftApCallback; 104 private ScanOnlyModeManager.Listener mScanOnlyCallback; 105 private ClientModeManager.Listener mClientModeCallback; 106 107 /** 108 * Called from WifiServiceImpl to register a callback for notifications from SoftApManager 109 */ 110 public void registerSoftApCallback(@NonNull WifiManager.SoftApCallback callback) { 111 mSoftApCallback = callback; 112 } 113 114 /** 115 * Called from WifiController to register a callback for notifications from ScanOnlyModeManager 116 */ 117 public void registerScanOnlyCallback(@NonNull ScanOnlyModeManager.Listener callback) { 118 mScanOnlyCallback = callback; 119 } 120 121 /** 122 * Called from WifiController to register a callback for notifications from ClientModeManager 123 */ 124 public void registerClientModeCallback(@NonNull ClientModeManager.Listener callback) { 125 mClientModeCallback = callback; 126 } 127 128 WifiStateMachinePrime(WifiInjector wifiInjector, 129 Context context, 130 Looper looper, 131 WifiNative wifiNative, 132 DefaultModeManager defaultModeManager, 133 IBatteryStats batteryStats) { 134 mWifiInjector = wifiInjector; 135 mContext = context; 136 mLooper = looper; 137 mHandler = new Handler(looper); 138 mWifiNative = wifiNative; 139 mActiveModeManagers = new ArraySet(); 140 mDefaultModeManager = defaultModeManager; 141 mBatteryStats = batteryStats; 142 mSelfRecovery = mWifiInjector.getSelfRecovery(); 143 mWifiDiagnostics = mWifiInjector.getWifiDiagnostics(); 144 mScanRequestProxy = mWifiInjector.getScanRequestProxy(); 145 mModeStateMachine = new ModeStateMachine(); 146 mWifiNativeStatusListener = new WifiNativeStatusListener(); 147 mWifiNative.registerStatusListener(mWifiNativeStatusListener); 148 } 149 150 /** 151 * Method to switch wifi into client mode where connections to configured networks will be 152 * attempted. 153 */ 154 public void enterClientMode() { 155 changeMode(ModeStateMachine.CMD_START_CLIENT_MODE); 156 } 157 158 /** 159 * Method to switch wifi into scan only mode where network connection attempts will not be made. 160 * 161 * This mode is utilized by location scans. If wifi is disabled by a user, but they have 162 * previously configured their device to perform location scans, this mode allows wifi to 163 * fulfill the location scan requests but will not be used for connectivity. 164 */ 165 public void enterScanOnlyMode() { 166 changeMode(ModeStateMachine.CMD_START_SCAN_ONLY_MODE); 167 } 168 169 /** 170 * Method to enable soft ap for wifi hotspot. 171 * 172 * The supplied SoftApModeConfiguration includes the target softap WifiConfiguration (or null if 173 * the persisted config is to be used) and the target operating mode (ex, 174 * {@link WifiManager.IFACE_IP_MODE_TETHERED} {@link WifiManager.IFACE_IP_MODE_LOCAL_ONLY}). 175 * 176 * @param wifiConfig SoftApModeConfiguration for the hostapd softap 177 */ 178 public void enterSoftAPMode(@NonNull SoftApModeConfiguration wifiConfig) { 179 mHandler.post(() -> { 180 startSoftAp(wifiConfig); 181 }); 182 } 183 184 /** 185 * Method to stop soft ap for wifi hotspot. 186 * 187 * This method will stop any active softAp mode managers. 188 */ 189 public void stopSoftAPMode() { 190 mHandler.post(() -> { 191 for (ActiveModeManager manager : mActiveModeManagers) { 192 if (manager instanceof SoftApManager) { 193 Log.d(TAG, "Stopping SoftApModeManager"); 194 manager.stop(); 195 } 196 } 197 updateBatteryStatsWifiState(false); 198 }); 199 } 200 201 /** 202 * Method to disable wifi in sta/client mode scenarios. 203 * 204 * This mode will stop any client/scan modes and will not perform any network scans. 205 */ 206 public void disableWifi() { 207 changeMode(ModeStateMachine.CMD_DISABLE_WIFI); 208 } 209 210 /** 211 * Method to stop all active modes, for example, when toggling airplane mode. 212 */ 213 public void shutdownWifi() { 214 mHandler.post(() -> { 215 for (ActiveModeManager manager : mActiveModeManagers) { 216 manager.stop(); 217 } 218 updateBatteryStatsWifiState(false); 219 }); 220 } 221 222 /** 223 * Dump current state for active mode managers. 224 * 225 * Must be called from WifiStateMachine thread. 226 */ 227 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 228 pw.println("Dump of " + TAG); 229 230 pw.println("Current wifi mode: " + getCurrentMode()); 231 pw.println("NumActiveModeManagers: " + mActiveModeManagers.size()); 232 for (ActiveModeManager manager : mActiveModeManagers) { 233 manager.dump(fd, pw, args); 234 } 235 } 236 237 protected String getCurrentMode() { 238 return mModeStateMachine.getCurrentMode(); 239 } 240 241 private void changeMode(int newMode) { 242 mModeStateMachine.sendMessage(newMode); 243 } 244 245 /** 246 * Helper class to wrap the ActiveModeManager callback objects. 247 */ 248 private class ModeCallback { 249 ActiveModeManager mActiveManager; 250 251 void setActiveModeManager(ActiveModeManager manager) { 252 mActiveManager = manager; 253 } 254 255 ActiveModeManager getActiveModeManager() { 256 return mActiveManager; 257 } 258 } 259 260 private class ModeStateMachine extends StateMachine { 261 // Commands for the state machine - these will be removed, 262 // along with the StateMachine itself 263 public static final int CMD_START_CLIENT_MODE = 0; 264 public static final int CMD_START_SCAN_ONLY_MODE = 1; 265 public static final int CMD_DISABLE_WIFI = 3; 266 267 private final State mWifiDisabledState = new WifiDisabledState(); 268 private final State mClientModeActiveState = new ClientModeActiveState(); 269 private final State mScanOnlyModeActiveState = new ScanOnlyModeActiveState(); 270 271 ModeStateMachine() { 272 super(TAG, mLooper); 273 274 addState(mClientModeActiveState); 275 addState(mScanOnlyModeActiveState); 276 addState(mWifiDisabledState); 277 278 Log.d(TAG, "Starting Wifi in WifiDisabledState"); 279 setInitialState(mWifiDisabledState); 280 start(); 281 } 282 283 private String getCurrentMode() { 284 return getCurrentState().getName(); 285 } 286 287 private boolean checkForAndHandleModeChange(Message message) { 288 switch(message.what) { 289 case ModeStateMachine.CMD_START_CLIENT_MODE: 290 Log.d(TAG, "Switching from " + getCurrentMode() + " to ClientMode"); 291 mModeStateMachine.transitionTo(mClientModeActiveState); 292 break; 293 case ModeStateMachine.CMD_START_SCAN_ONLY_MODE: 294 Log.d(TAG, "Switching from " + getCurrentMode() + " to ScanOnlyMode"); 295 mModeStateMachine.transitionTo(mScanOnlyModeActiveState); 296 break; 297 case ModeStateMachine.CMD_DISABLE_WIFI: 298 Log.d(TAG, "Switching from " + getCurrentMode() + " to WifiDisabled"); 299 mModeStateMachine.transitionTo(mWifiDisabledState); 300 break; 301 default: 302 return NOT_HANDLED; 303 } 304 return HANDLED; 305 } 306 307 class ModeActiveState extends State { 308 ActiveModeManager mManager; 309 @Override 310 public boolean processMessage(Message message) { 311 // handle messages for changing modes here 312 return NOT_HANDLED; 313 } 314 315 @Override 316 public void exit() { 317 // Active states must have a mode manager, so this should not be null, but it isn't 318 // obvious from the structure - add a null check here, just in case this is missed 319 // in the future 320 if (mManager != null) { 321 mManager.stop(); 322 mActiveModeManagers.remove(mManager); 323 } 324 updateBatteryStatsWifiState(false); 325 } 326 } 327 328 class WifiDisabledState extends ModeActiveState { 329 @Override 330 public void enter() { 331 Log.d(TAG, "Entering WifiDisabledState"); 332 mDefaultModeManager.sendScanAvailableBroadcast(mContext, false); 333 mScanRequestProxy.enableScanningForHiddenNetworks(false); 334 mScanRequestProxy.clearScanResults(); 335 } 336 337 @Override 338 public boolean processMessage(Message message) { 339 Log.d(TAG, "received a message in WifiDisabledState: " + message); 340 if (checkForAndHandleModeChange(message)) { 341 return HANDLED; 342 } 343 return NOT_HANDLED; 344 } 345 346 @Override 347 public void exit() { 348 // do not have an active mode manager... nothing to clean up 349 } 350 351 } 352 353 class ClientModeActiveState extends ModeActiveState { 354 ClientListener mListener; 355 private class ClientListener implements ClientModeManager.Listener { 356 @Override 357 public void onStateChanged(int state) { 358 // make sure this listener is still active 359 if (this != mListener) { 360 Log.d(TAG, "Client mode state change from previous manager"); 361 return; 362 } 363 364 Log.d(TAG, "State changed from client mode. state = " + state); 365 366 if (state == WifiManager.WIFI_STATE_UNKNOWN) { 367 // error while setting up client mode or an unexpected failure. 368 mModeStateMachine.sendMessage(CMD_CLIENT_MODE_FAILED, this); 369 } else if (state == WifiManager.WIFI_STATE_DISABLED) { 370 // client mode stopped 371 mModeStateMachine.sendMessage(CMD_CLIENT_MODE_STOPPED, this); 372 } else if (state == WifiManager.WIFI_STATE_ENABLED) { 373 // client mode is ready to go 374 Log.d(TAG, "client mode active"); 375 } else { 376 // only care if client mode stopped or started, dropping 377 } 378 } 379 } 380 381 @Override 382 public void enter() { 383 Log.d(TAG, "Entering ClientModeActiveState"); 384 385 mListener = new ClientListener(); 386 mManager = mWifiInjector.makeClientModeManager(mListener); 387 mManager.start(); 388 mActiveModeManagers.add(mManager); 389 390 updateBatteryStatsWifiState(true); 391 } 392 393 @Override 394 public void exit() { 395 super.exit(); 396 mListener = null; 397 } 398 399 @Override 400 public boolean processMessage(Message message) { 401 if (checkForAndHandleModeChange(message)) { 402 return HANDLED; 403 } 404 405 switch(message.what) { 406 case CMD_START_CLIENT_MODE: 407 Log.d(TAG, "Received CMD_START_CLIENT_MODE when active - drop"); 408 break; 409 case CMD_CLIENT_MODE_FAILED: 410 if (mListener != message.obj) { 411 Log.d(TAG, "Client mode state change from previous manager"); 412 return HANDLED; 413 } 414 Log.d(TAG, "ClientMode failed, return to WifiDisabledState."); 415 // notify WifiController that ClientMode failed 416 mClientModeCallback.onStateChanged(WifiManager.WIFI_STATE_UNKNOWN); 417 mModeStateMachine.transitionTo(mWifiDisabledState); 418 break; 419 case CMD_CLIENT_MODE_STOPPED: 420 if (mListener != message.obj) { 421 Log.d(TAG, "Client mode state change from previous manager"); 422 return HANDLED; 423 } 424 425 Log.d(TAG, "ClientMode stopped, return to WifiDisabledState."); 426 // notify WifiController that ClientMode stopped 427 mClientModeCallback.onStateChanged(WifiManager.WIFI_STATE_DISABLED); 428 mModeStateMachine.transitionTo(mWifiDisabledState); 429 break; 430 default: 431 return NOT_HANDLED; 432 } 433 return NOT_HANDLED; 434 } 435 } 436 437 class ScanOnlyModeActiveState extends ModeActiveState { 438 ScanOnlyListener mListener; 439 private class ScanOnlyListener implements ScanOnlyModeManager.Listener { 440 @Override 441 public void onStateChanged(int state) { 442 if (this != mListener) { 443 Log.d(TAG, "ScanOnly mode state change from previous manager"); 444 return; 445 } 446 447 if (state == WifiManager.WIFI_STATE_UNKNOWN) { 448 Log.d(TAG, "ScanOnlyMode mode failed"); 449 // error while setting up scan mode or an unexpected failure. 450 mModeStateMachine.sendMessage(CMD_SCAN_ONLY_MODE_FAILED, this); 451 } else if (state == WifiManager.WIFI_STATE_DISABLED) { 452 Log.d(TAG, "ScanOnlyMode stopped"); 453 //scan only mode stopped 454 mModeStateMachine.sendMessage(CMD_SCAN_ONLY_MODE_STOPPED, this); 455 } else if (state == WifiManager.WIFI_STATE_ENABLED) { 456 // scan mode is ready to go 457 Log.d(TAG, "scan mode active"); 458 } else { 459 Log.d(TAG, "unexpected state update: " + state); 460 } 461 } 462 } 463 464 @Override 465 public void enter() { 466 Log.d(TAG, "Entering ScanOnlyModeActiveState"); 467 468 mListener = new ScanOnlyListener(); 469 mManager = mWifiInjector.makeScanOnlyModeManager(mListener); 470 mManager.start(); 471 mActiveModeManagers.add(mManager); 472 473 updateBatteryStatsWifiState(true); 474 updateBatteryStatsScanModeActive(); 475 } 476 477 @Override 478 public void exit() { 479 super.exit(); 480 mListener = null; 481 } 482 483 @Override 484 public boolean processMessage(Message message) { 485 if (checkForAndHandleModeChange(message)) { 486 return HANDLED; 487 } 488 489 switch(message.what) { 490 case CMD_START_SCAN_ONLY_MODE: 491 Log.d(TAG, "Received CMD_START_SCAN_ONLY_MODE when active - drop"); 492 break; 493 case CMD_SCAN_ONLY_MODE_FAILED: 494 if (mListener != message.obj) { 495 Log.d(TAG, "ScanOnly mode state change from previous manager"); 496 return HANDLED; 497 } 498 499 Log.d(TAG, "ScanOnlyMode failed, return to WifiDisabledState."); 500 // notify WifiController that ScanOnlyMode failed 501 mScanOnlyCallback.onStateChanged(WifiManager.WIFI_STATE_UNKNOWN); 502 mModeStateMachine.transitionTo(mWifiDisabledState); 503 break; 504 case CMD_SCAN_ONLY_MODE_STOPPED: 505 if (mListener != message.obj) { 506 Log.d(TAG, "ScanOnly mode state change from previous manager"); 507 return HANDLED; 508 } 509 510 Log.d(TAG, "ScanOnlyMode stopped, return to WifiDisabledState."); 511 // notify WifiController that ScanOnlyMode stopped 512 mScanOnlyCallback.onStateChanged(WifiManager.WIFI_STATE_DISABLED); 513 mModeStateMachine.transitionTo(mWifiDisabledState); 514 break; 515 default: 516 return NOT_HANDLED; 517 } 518 return HANDLED; 519 } 520 } 521 } // class ModeStateMachine 522 523 private class SoftApCallbackImpl extends ModeCallback implements WifiManager.SoftApCallback { 524 @Override 525 public void onStateChanged(int state, int reason) { 526 if (state == WifiManager.WIFI_AP_STATE_DISABLED) { 527 mActiveModeManagers.remove(getActiveModeManager()); 528 updateBatteryStatsWifiState(false); 529 } else if (state == WifiManager.WIFI_AP_STATE_FAILED) { 530 mActiveModeManagers.remove(getActiveModeManager()); 531 updateBatteryStatsWifiState(false); 532 } 533 534 if (mSoftApCallback != null) { 535 mSoftApCallback.onStateChanged(state, reason); 536 } 537 } 538 539 @Override 540 public void onNumClientsChanged(int numClients) { 541 if (mSoftApCallback != null) { 542 mSoftApCallback.onNumClientsChanged(numClients); 543 } else { 544 Log.d(TAG, "SoftApCallback is null. Dropping NumClientsChanged event."); 545 } 546 } 547 } 548 549 private void startSoftAp(SoftApModeConfiguration softapConfig) { 550 Log.d(TAG, "Starting SoftApModeManager"); 551 552 WifiConfiguration config = softapConfig.getWifiConfiguration(); 553 if (config != null && config.SSID != null) { 554 Log.d(TAG, "Passing config to SoftApManager! " + config); 555 } else { 556 config = null; 557 } 558 559 SoftApCallbackImpl callback = new SoftApCallbackImpl(); 560 ActiveModeManager manager = mWifiInjector.makeSoftApManager(callback, softapConfig); 561 callback.setActiveModeManager(manager); 562 manager.start(); 563 mActiveModeManagers.add(manager); 564 updateBatteryStatsWifiState(true); 565 } 566 567 /** 568 * Helper method to report wifi state as on/off (doesn't matter which mode). 569 * 570 * @param enabled boolean indicating that some mode has been turned on or off 571 */ 572 private void updateBatteryStatsWifiState(boolean enabled) { 573 try { 574 if (enabled) { 575 if (mActiveModeManagers.size() == 1) { 576 // only report wifi on if we haven't already 577 mBatteryStats.noteWifiOn(); 578 } 579 } else { 580 if (mActiveModeManagers.size() == 0) { 581 // only report if we don't have any active modes 582 mBatteryStats.noteWifiOff(); 583 } 584 } 585 } catch (RemoteException e) { 586 Log.e(TAG, "Failed to note battery stats in wifi"); 587 } 588 } 589 590 private void updateBatteryStatsScanModeActive() { 591 try { 592 mBatteryStats.noteWifiState(BatteryStats.WIFI_STATE_OFF_SCANNING, null); 593 } catch (RemoteException e) { 594 Log.e(TAG, "Failed to note battery stats in wifi"); 595 } 596 } 597 598 // callback used to receive callbacks about underlying native failures 599 private final class WifiNativeStatusListener implements StatusListener { 600 601 @Override 602 public void onStatusChanged(boolean isReady) { 603 if (!isReady) { 604 mHandler.post(() -> { 605 Log.e(TAG, "One of the native daemons died. Triggering recovery"); 606 mWifiDiagnostics.captureBugReportData( 607 WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE); 608 609 // immediately trigger SelfRecovery if we receive a notice about an 610 // underlying daemon failure 611 mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE); 612 }); 613 } 614 } 615 }; 616} 617