blob: fd3a65099a402f51a7e5b67f56a39c64d7663659 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wifi;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.LocationManager;
import android.net.wifi.WifiManager;
import android.os.BatteryStats;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.R;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.IState;
import com.android.internal.util.Preconditions;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.server.wifi.util.WifiPermissionsUtil;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* This class provides the implementation for different WiFi operating modes.
*/
public class ActiveModeWarden {
private static final String TAG = "WifiActiveModeWarden";
private static final String STATE_MACHINE_EXITED_STATE_NAME = "STATE_MACHINE_EXITED";
private final ModeStateMachine mModeStateMachine;
// Holder for active mode managers
private final ArraySet<ActiveModeManager> mActiveModeManagers;
// DefaultModeManager used to service API calls when there are not active mode managers.
private final DefaultModeManager mDefaultModeManager;
private final WifiInjector mWifiInjector;
private final Looper mLooper;
private final Handler mHandler;
private final Context mContext;
private final ClientModeImpl mClientModeImpl;
private final WifiSettingsStore mSettingsStore;
private final FrameworkFacade mFacade;
private final WifiPermissionsUtil mWifiPermissionsUtil;
private final IBatteryStats mBatteryStats;
private final ScanRequestProxy mScanRequestProxy;
private final WifiController mWifiController;
// The base for wifi message types
static final int BASE = Protocol.BASE_WIFI;
// The message identifiers below are mapped to those in ClientModeImpl when applicable.
// Start the soft access point
static final int CMD_START_AP = BASE + 21;
// Indicates soft ap start failed
static final int CMD_START_AP_FAILURE = BASE + 22;
// Stop the soft access point
static final int CMD_STOP_AP = BASE + 23;
// Soft access point teardown is completed
static final int CMD_AP_STOPPED = BASE + 24;
// Start Scan Only mode
static final int CMD_START_SCAN_ONLY_MODE = BASE + 200;
// Indicates that start Scan only mode failed
static final int CMD_START_SCAN_ONLY_MODE_FAILURE = BASE + 201;
// Indicates that scan only mode stopped
static final int CMD_STOP_SCAN_ONLY_MODE = BASE + 202;
// ScanOnly mode teardown is complete
static final int CMD_SCAN_ONLY_MODE_STOPPED = BASE + 203;
// ScanOnly mode failed
static final int CMD_SCAN_ONLY_MODE_FAILED = BASE + 204;
// Start Client mode
static final int CMD_START_CLIENT_MODE = BASE + 300;
// Indicates that start client mode failed
static final int CMD_START_CLIENT_MODE_FAILURE = BASE + 301;
// Indicates that client mode stopped
static final int CMD_STOP_CLIENT_MODE = BASE + 302;
// Client mode teardown is complete
static final int CMD_CLIENT_MODE_STOPPED = BASE + 303;
// Client mode failed
static final int CMD_CLIENT_MODE_FAILED = BASE + 304;
private WifiManager.SoftApCallback mSoftApCallback;
private WifiManager.SoftApCallback mLohsCallback;
/**
* Called from WifiServiceImpl to register a callback for notifications from SoftApManager
*/
public void registerSoftApCallback(@NonNull WifiManager.SoftApCallback callback) {
mSoftApCallback = callback;
}
/**
* Called from WifiServiceImpl to register a callback for notifications from SoftApManager
* for local-only hotspot.
*/
public void registerLohsCallback(@NonNull WifiManager.SoftApCallback callback) {
mLohsCallback = callback;
}
ActiveModeWarden(WifiInjector wifiInjector,
Looper looper,
WifiNative wifiNative,
DefaultModeManager defaultModeManager,
IBatteryStats batteryStats,
BaseWifiDiagnostics wifiDiagnostics,
Context context,
ClientModeImpl clientModeImpl,
WifiSettingsStore settingsStore,
FrameworkFacade facade,
WifiPermissionsUtil wifiPermissionsUtil) {
mWifiInjector = wifiInjector;
mLooper = looper;
mHandler = new Handler(looper);
mContext = context;
mClientModeImpl = clientModeImpl;
mSettingsStore = settingsStore;
mFacade = facade;
mWifiPermissionsUtil = wifiPermissionsUtil;
mActiveModeManagers = new ArraySet<>();
mDefaultModeManager = defaultModeManager;
mBatteryStats = batteryStats;
mScanRequestProxy = wifiInjector.getScanRequestProxy();
mModeStateMachine = new ModeStateMachine();
mWifiController = new WifiController();
wifiNative.registerStatusListener(isReady -> {
if (!isReady) {
mHandler.post(() -> {
Log.e(TAG, "One of the native daemons died. Triggering recovery");
wifiDiagnostics.captureBugReportData(
WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE);
// immediately trigger SelfRecovery if we receive a notice about an
// underlying daemon failure
// Note: SelfRecovery has a circular dependency with ActiveModeWarden and is
// instantiated after ActiveModeWarden, so use WifiInjector to get the instance
// instead of directly passing in SelfRecovery in the constructor.
mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE);
});
}
});
}
/**
* Method to enable soft ap for wifi hotspot.
*
* The supplied SoftApModeConfiguration includes the target softap WifiConfiguration (or null if
* the persisted config is to be used) and the target operating mode (ex,
* {@link WifiManager#IFACE_IP_MODE_TETHERED} {@link WifiManager#IFACE_IP_MODE_LOCAL_ONLY}).
*
* @param softApConfig SoftApModeConfiguration for the hostapd softap
*/
public void enterSoftAPMode(@NonNull SoftApModeConfiguration softApConfig) {
mHandler.post(() -> {
Log.d(TAG, "Starting SoftApModeManager config = "
+ softApConfig.getWifiConfiguration());
SoftApCallbackImpl callback = new SoftApCallbackImpl(softApConfig.getTargetMode());
ActiveModeManager manager = mWifiInjector.makeSoftApManager(callback, softApConfig);
callback.setActiveModeManager(manager);
manager.start();
mActiveModeManagers.add(manager);
updateBatteryStatsWifiState(true);
});
}
/**
* Method to stop soft ap for wifi hotspot.
*
* This method will stop any active softAp mode managers.
*
* @param mode the operating mode of APs to bring down (ex,
* {@link WifiManager#IFACE_IP_MODE_TETHERED} or
* {@link WifiManager#IFACE_IP_MODE_LOCAL_ONLY}).
* Use {@link WifiManager#IFACE_IP_MODE_UNSPECIFIED} to stop all APs.
*/
public void stopSoftAPMode(int mode) {
mHandler.post(() -> {
for (ActiveModeManager manager : mActiveModeManagers) {
if (!(manager instanceof SoftApManager)) continue;
SoftApManager softApManager = (SoftApManager) manager;
if (mode != WifiManager.IFACE_IP_MODE_UNSPECIFIED
&& mode != softApManager.getIpMode()) {
continue;
}
softApManager.stop();
}
updateBatteryStatsWifiState(false);
});
}
/**
* Method to stop all active modes, for example, when toggling airplane mode.
*/
public void shutdownWifi() {
mHandler.post(() -> {
for (ActiveModeManager manager : mActiveModeManagers) {
manager.stop();
}
updateBatteryStatsWifiState(false);
});
}
/**
* Dump current state for active mode managers.
*
* Must be called from the main Wifi thread.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Dump of " + TAG);
pw.println("Current wifi mode: " + getCurrentMode());
pw.println("NumActiveModeManagers: " + mActiveModeManagers.size());
for (ActiveModeManager manager : mActiveModeManagers) {
manager.dump(fd, pw, args);
}
}
protected String getCurrentMode() {
return mModeStateMachine.getCurrentMode();
}
private void changeMode(int newMode) {
mModeStateMachine.sendMessage(newMode);
}
private class ModeStateMachine extends StateMachine {
// Commands for the state machine - these will be removed,
// along with the StateMachine itself
public static final int CMD_START_CLIENT_MODE = 0;
public static final int CMD_START_SCAN_ONLY_MODE = 1;
public static final int CMD_DISABLE_WIFI = 3;
private final State mWifiDisabledState = new WifiDisabledState();
private final State mClientModeActiveState = new ClientModeActiveState();
private final State mScanOnlyModeActiveState = new ScanOnlyModeActiveState();
ModeStateMachine() {
super(TAG, mLooper);
addState(mClientModeActiveState);
addState(mScanOnlyModeActiveState);
addState(mWifiDisabledState);
Log.d(TAG, "Starting Wifi in WifiDisabledState");
setInitialState(mWifiDisabledState);
start();
}
private String getCurrentMode() {
IState state = getCurrentState();
if (state == null) {
return STATE_MACHINE_EXITED_STATE_NAME;
} else {
return state.getName();
}
}
private boolean checkForAndHandleModeChange(Message message) {
switch(message.what) {
case ModeStateMachine.CMD_START_CLIENT_MODE:
Log.d(TAG, "Switching from " + getCurrentMode() + " to ClientMode");
mModeStateMachine.transitionTo(mClientModeActiveState);
break;
case ModeStateMachine.CMD_START_SCAN_ONLY_MODE:
Log.d(TAG, "Switching from " + getCurrentMode() + " to ScanOnlyMode");
mModeStateMachine.transitionTo(mScanOnlyModeActiveState);
break;
case ModeStateMachine.CMD_DISABLE_WIFI:
Log.d(TAG, "Switching from " + getCurrentMode() + " to WifiDisabled");
mModeStateMachine.transitionTo(mWifiDisabledState);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
class ModeActiveState extends State {
protected ActiveModeManager mManager;
@Override
public boolean processMessage(Message message) {
// handle messages for changing modes here
return NOT_HANDLED;
}
@Override
public void exit() {
// Active states must have a mode manager, so this should not be null, but it isn't
// obvious from the structure - add a null check here, just in case this is missed
// in the future
if (mManager != null) {
mManager.stop();
mActiveModeManagers.remove(mManager);
updateScanMode();
}
updateBatteryStatsWifiState(false);
}
// Hook to be used by sub-classes of ModeActiveState to indicate the completion of
// bringup of the corresponding mode.
public void onModeActivationComplete() {
updateScanMode();
}
// Update the scan state based on all active mode managers.
// Note: This is an overkill currently because there is only 1 of scan-only or client
// mode present today.
private void updateScanMode() {
boolean scanEnabled = false;
boolean scanningForHiddenNetworksEnabled = false;
for (ActiveModeManager modeManager : mActiveModeManagers) {
@ActiveModeManager.ScanMode int scanState = modeManager.getScanMode();
switch (scanState) {
case ActiveModeManager.SCAN_NONE:
break;
case ActiveModeManager.SCAN_WITHOUT_HIDDEN_NETWORKS:
scanEnabled = true;
break;
case ActiveModeManager.SCAN_WITH_HIDDEN_NETWORKS:
scanEnabled = true;
scanningForHiddenNetworksEnabled = true;
break;
}
}
mScanRequestProxy.enableScanning(scanEnabled, scanningForHiddenNetworksEnabled);
}
}
class WifiDisabledState extends ModeActiveState {
@Override
public void enter() {
Log.d(TAG, "Entering WifiDisabledState");
}
@Override
public boolean processMessage(Message message) {
if (checkForAndHandleModeChange(message)) {
return HANDLED;
}
Log.d(TAG, "Unhandled message in WifiDisabledState: " + message);
return NOT_HANDLED;
}
@Override
public void exit() {
// do not have an active mode manager... nothing to clean up
}
}
class ClientModeActiveState extends ModeActiveState {
private ClientListener mListener;
private class ClientListener implements ClientModeManager.Listener {
@Override
public void onStateChanged(int state) {
// make sure this listener is still active
if (this != mListener) {
Log.d(TAG, "Client mode state change from previous manager");
return;
}
Log.d(TAG, "State changed from client mode. state = " + state);
if (state == WifiManager.WIFI_STATE_UNKNOWN) {
// error while setting up client mode or an unexpected failure.
mModeStateMachine.sendMessage(CMD_CLIENT_MODE_FAILED, this);
} else if (state == WifiManager.WIFI_STATE_DISABLED) {
// client mode stopped
mModeStateMachine.sendMessage(CMD_CLIENT_MODE_STOPPED, this);
} else if (state == WifiManager.WIFI_STATE_ENABLED) {
// client mode is ready to go
Log.d(TAG, "client mode active");
onModeActivationComplete();
} else {
// only care if client mode stopped or started, dropping
}
}
}
@Override
public void enter() {
Log.d(TAG, "Entering ClientModeActiveState");
mListener = new ClientListener();
mManager = mWifiInjector.makeClientModeManager(mListener);
mManager.start();
mActiveModeManagers.add(mManager);
updateBatteryStatsWifiState(true);
}
@Override
public void exit() {
super.exit();
mListener = null;
}
@Override
public boolean processMessage(Message message) {
switch(message.what) {
case CMD_START_CLIENT_MODE:
Log.d(TAG, "Received CMD_START_CLIENT_MODE when active - drop");
break;
case CMD_CLIENT_MODE_FAILED:
if (mListener != message.obj) {
Log.d(TAG, "Client mode state change from previous manager");
return HANDLED;
}
Log.d(TAG, "ClientMode failed, return to WifiDisabledState.");
mWifiController.sendMessage(WifiController.CMD_STA_START_FAILURE);
mModeStateMachine.transitionTo(mWifiDisabledState);
break;
case CMD_CLIENT_MODE_STOPPED:
if (mListener != message.obj) {
Log.d(TAG, "Client mode state change from previous manager");
return HANDLED;
}
Log.d(TAG, "ClientMode stopped, return to WifiDisabledState.");
mWifiController.sendMessage(WifiController.CMD_STA_STOPPED);
mModeStateMachine.transitionTo(mWifiDisabledState);
break;
default:
return checkForAndHandleModeChange(message);
}
return NOT_HANDLED;
}
}
class ScanOnlyModeActiveState extends ModeActiveState {
private ScanOnlyListener mListener;
private class ScanOnlyListener implements ScanOnlyModeManager.Listener {
@Override
public void onStateChanged(int state) {
if (this != mListener) {
Log.d(TAG, "ScanOnly mode state change from previous manager");
return;
}
if (state == WifiManager.WIFI_STATE_UNKNOWN) {
Log.d(TAG, "ScanOnlyMode mode failed");
// error while setting up scan mode or an unexpected failure.
mModeStateMachine.sendMessage(CMD_SCAN_ONLY_MODE_FAILED, this);
} else if (state == WifiManager.WIFI_STATE_DISABLED) {
Log.d(TAG, "ScanOnlyMode stopped");
//scan only mode stopped
mModeStateMachine.sendMessage(CMD_SCAN_ONLY_MODE_STOPPED, this);
} else if (state == WifiManager.WIFI_STATE_ENABLED) {
// scan mode is ready to go
Log.d(TAG, "scan mode active");
onModeActivationComplete();
} else {
Log.d(TAG, "unexpected state update: " + state);
}
}
}
@Override
public void enter() {
Log.d(TAG, "Entering ScanOnlyModeActiveState");
mListener = new ScanOnlyListener();
mManager = mWifiInjector.makeScanOnlyModeManager(mListener);
mManager.start();
mActiveModeManagers.add(mManager);
updateBatteryStatsWifiState(true);
updateBatteryStatsScanModeActive();
}
@Override
public void exit() {
super.exit();
mListener = null;
}
@Override
public boolean processMessage(Message message) {
switch(message.what) {
case CMD_START_SCAN_ONLY_MODE:
Log.d(TAG, "Received CMD_START_SCAN_ONLY_MODE when active - drop");
break;
case CMD_SCAN_ONLY_MODE_FAILED:
if (mListener != message.obj) {
Log.d(TAG, "ScanOnly mode state change from previous manager");
return HANDLED;
}
Log.d(TAG, "ScanOnlyMode failed, return to WifiDisabledState.");
mModeStateMachine.transitionTo(mWifiDisabledState);
break;
case CMD_SCAN_ONLY_MODE_STOPPED:
if (mListener != message.obj) {
Log.d(TAG, "ScanOnly mode state change from previous manager");
return HANDLED;
}
Log.d(TAG, "ScanOnlyMode stopped, return to WifiDisabledState.");
mWifiController.sendMessage(WifiController.CMD_SCANNING_STOPPED);
mModeStateMachine.transitionTo(mWifiDisabledState);
break;
default:
return checkForAndHandleModeChange(message);
}
return HANDLED;
}
}
} // class ModeStateMachine
/**
* Helper class to wrap the ActiveModeManager callback objects.
*/
private static class ModeCallback {
private ActiveModeManager mActiveManager;
void setActiveModeManager(ActiveModeManager manager) {
mActiveManager = manager;
}
ActiveModeManager getActiveModeManager() {
return mActiveManager;
}
}
private class SoftApCallbackImpl extends ModeCallback implements WifiManager.SoftApCallback {
private final int mMode;
SoftApCallbackImpl(int mode) {
Preconditions.checkArgument(mode == WifiManager.IFACE_IP_MODE_TETHERED
|| mode == WifiManager.IFACE_IP_MODE_LOCAL_ONLY);
mMode = mode;
}
@Override
public void onStateChanged(int state, int reason) {
if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
mActiveModeManagers.remove(getActiveModeManager());
updateBatteryStatsWifiState(false);
} else if (state == WifiManager.WIFI_AP_STATE_FAILED) {
mActiveModeManagers.remove(getActiveModeManager());
updateBatteryStatsWifiState(false);
}
switch (mMode) {
case WifiManager.IFACE_IP_MODE_TETHERED:
if (mSoftApCallback != null) mSoftApCallback.onStateChanged(state, reason);
break;
case WifiManager.IFACE_IP_MODE_LOCAL_ONLY:
if (mLohsCallback != null) mLohsCallback.onStateChanged(state, reason);
break;
}
}
@Override
public void onNumClientsChanged(int numClients) {
switch (mMode) {
case WifiManager.IFACE_IP_MODE_TETHERED:
if (mSoftApCallback != null) mSoftApCallback.onNumClientsChanged(numClients);
break;
case WifiManager.IFACE_IP_MODE_LOCAL_ONLY:
if (mLohsCallback != null) mLohsCallback.onNumClientsChanged(numClients);
break;
}
}
}
/**
* Helper method to report wifi state as on/off (doesn't matter which mode).
*
* @param enabled boolean indicating that some mode has been turned on or off
*/
private void updateBatteryStatsWifiState(boolean enabled) {
try {
if (enabled) {
if (mActiveModeManagers.size() == 1) {
// only report wifi on if we haven't already
mBatteryStats.noteWifiOn();
}
} else {
if (mActiveModeManagers.size() == 0) {
// only report if we don't have any active modes
mBatteryStats.noteWifiOff();
}
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to note battery stats in wifi");
}
}
private void updateBatteryStatsScanModeActive() {
try {
mBatteryStats.noteWifiState(BatteryStats.WIFI_STATE_OFF_SCANNING, null);
} catch (RemoteException e) {
Log.e(TAG, "Failed to note battery stats in wifi");
}
}
/**
* WifiController is the class used to manage wifi state for various operating
* modes (normal, airplane, wifi hotspot, etc.).
*/
private class WifiController extends StateMachine {
private static final String TAG = "WifiController";
// Maximum limit to use for timeout delay if the value from overlay setting is too large.
private static final int MAX_RECOVERY_TIMEOUT_DELAY_MS = 4000;
private final int mRecoveryDelayMillis;
private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
static final int CMD_EMERGENCY_MODE_CHANGED = BASE + 1;
static final int CMD_SCAN_ALWAYS_MODE_CHANGED = BASE + 7;
static final int CMD_WIFI_TOGGLED = BASE + 8;
static final int CMD_AIRPLANE_TOGGLED = BASE + 9;
static final int CMD_SET_AP = BASE + 10;
static final int CMD_EMERGENCY_CALL_STATE_CHANGED = BASE + 14;
static final int CMD_AP_STOPPED = BASE + 15;
static final int CMD_STA_START_FAILURE = BASE + 16;
// Command used to trigger a wifi stack restart when in active mode
static final int CMD_RECOVERY_RESTART_WIFI = BASE + 17;
// Internal command used to complete wifi stack restart
private static final int CMD_RECOVERY_RESTART_WIFI_CONTINUE = BASE + 18;
// Command to disable wifi when SelfRecovery is throttled or otherwise not doing full recovery
static final int CMD_RECOVERY_DISABLE_WIFI = BASE + 19;
static final int CMD_STA_STOPPED = BASE + 20;
static final int CMD_SCANNING_STOPPED = BASE + 21;
static final int CMD_DEFERRED_RECOVERY_RESTART_WIFI = BASE + 22;
private final DefaultState mDefaultState = new DefaultState();
private final StaEnabledState mStaEnabledState = new StaEnabledState();
private final StaDisabledState mStaDisabledState = new StaDisabledState();
private final StaDisabledWithScanState mStaDisabledWithScanState =
new StaDisabledWithScanState();
private final EcmState mEcmState = new EcmState();
WifiController() {
super(TAG, mLooper);
addState(mDefaultState); {
addState(mStaDisabledState, mDefaultState);
addState(mStaEnabledState, mDefaultState);
addState(mStaDisabledWithScanState, mDefaultState);
addState(mEcmState, mDefaultState);
}
setLogRecSize(100);
setLogOnlyTransitions(false);
mRecoveryDelayMillis = readWifiRecoveryDelay();
}
@Override
public void start() {
boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();
boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();
boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable();
boolean isLocationModeActive = mWifiPermissionsUtil.isLocationModeEnabled();
log("isAirplaneModeOn = " + isAirplaneModeOn
+ ", isWifiEnabled = " + isWifiEnabled
+ ", isScanningAvailable = " + isScanningAlwaysAvailable
+ ", isLocationModeActive = " + isLocationModeActive);
if (checkScanOnlyModeAvailable()) {
setInitialState(mStaDisabledWithScanState);
} else {
setInitialState(mStaDisabledState);
}
IntentFilter filter = new IntentFilter();
filter.addAction(LocationManager.MODE_CHANGED_ACTION);
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
// Location mode has been toggled... trigger with the scan change
// update to make sure we are in the correct mode
sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
}
}
},
new IntentFilter(filter));
super.start();
}
private boolean checkScanOnlyModeAvailable() {
return mWifiPermissionsUtil.isLocationModeEnabled()
&& mSettingsStore.isScanAlwaysAvailable();
}
private int readWifiRecoveryDelay() {
int recoveryDelayMillis = mContext.getResources().getInteger(
R.integer.config_wifi_framework_recovery_timeout_delay);
if (recoveryDelayMillis > MAX_RECOVERY_TIMEOUT_DELAY_MS) {
recoveryDelayMillis = MAX_RECOVERY_TIMEOUT_DELAY_MS;
Log.w(TAG, "Overriding timeout delay with maximum limit value");
}
return recoveryDelayMillis;
}
class DefaultState extends State {
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_SCAN_ALWAYS_MODE_CHANGED:
case CMD_WIFI_TOGGLED:
case CMD_SCANNING_STOPPED:
case CMD_STA_STOPPED:
case CMD_STA_START_FAILURE:
case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
case CMD_DEFERRED_RECOVERY_RESTART_WIFI:
break;
case CMD_RECOVERY_DISABLE_WIFI:
log("Recovery has been throttled, disable wifi");
shutdownWifi();
transitionTo(mStaDisabledState);
break;
case CMD_RECOVERY_RESTART_WIFI:
deferMessage(obtainMessage(CMD_DEFERRED_RECOVERY_RESTART_WIFI));
shutdownWifi();
transitionTo(mStaDisabledState);
break;
case CMD_SET_AP:
// note: CMD_SET_AP is handled/dropped in ECM mode - will not start here
if (msg.arg1 == 1) {
enterSoftAPMode((SoftApModeConfiguration) msg.obj);
} else {
stopSoftAPMode(msg.arg2);
}
break;
case CMD_AIRPLANE_TOGGLED:
if (mSettingsStore.isAirplaneModeOn()) {
log("Airplane mode toggled, shutdown all modes");
shutdownWifi();
transitionTo(mStaDisabledState);
} else {
log("Airplane mode disabled, determine next state");
if (mSettingsStore.isWifiToggleEnabled()) {
transitionTo(mStaEnabledState);
} else if (checkScanOnlyModeAvailable()) {
transitionTo(mStaDisabledWithScanState);
}
// wifi should remain disabled, do not need to transition
}
break;
case CMD_EMERGENCY_CALL_STATE_CHANGED:
case CMD_EMERGENCY_MODE_CHANGED:
if (msg.arg1 == 1) {
transitionTo(mEcmState);
}
break;
case CMD_AP_STOPPED:
log("SoftAp mode disabled, determine next state");
if (mSettingsStore.isWifiToggleEnabled()) {
transitionTo(mStaEnabledState);
} else if (checkScanOnlyModeAvailable()) {
transitionTo(mStaDisabledWithScanState);
}
// wifi should remain disabled, do not need to transition
break;
default:
throw new RuntimeException("WifiController.handleMessage " + msg.what);
}
return HANDLED;
}
}
class StaDisabledState extends State {
@Override
public void enter() {
changeMode(ModeStateMachine.CMD_DISABLE_WIFI);
}
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_WIFI_TOGGLED:
if (mSettingsStore.isWifiToggleEnabled()) {
transitionTo(mStaEnabledState);
} else if (checkScanOnlyModeAvailable()) {
// only go to scan mode if we aren't in airplane mode
if (mSettingsStore.isAirplaneModeOn()) {
transitionTo(mStaDisabledWithScanState);
}
}
break;
case CMD_SCAN_ALWAYS_MODE_CHANGED:
if (checkScanOnlyModeAvailable()) {
transitionTo(mStaDisabledWithScanState);
}
break;
case CMD_SET_AP:
if (msg.arg1 == 1) {
// remember that we were disabled, but pass the command up to start softap
mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
}
return NOT_HANDLED;
case CMD_DEFERRED_RECOVERY_RESTART_WIFI:
// wait mRecoveryDelayMillis for letting driver clean reset.
sendMessageDelayed(CMD_RECOVERY_RESTART_WIFI_CONTINUE, mRecoveryDelayMillis);
break;
case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
if (mSettingsStore.isWifiToggleEnabled()) {
// wifi is currently disabled but the toggle is on, must have had an
// interface down before the recovery triggered
transitionTo(mStaEnabledState);
break;
} else if (checkScanOnlyModeAvailable()) {
transitionTo(mStaDisabledWithScanState);
break;
}
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class StaEnabledState extends State {
@Override
public void enter() {
log("StaEnabledState.enter()");
changeMode(ModeStateMachine.CMD_START_CLIENT_MODE);
}
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_WIFI_TOGGLED:
if (! mSettingsStore.isWifiToggleEnabled()) {
if (checkScanOnlyModeAvailable()) {
transitionTo(mStaDisabledWithScanState);
} else {
transitionTo(mStaDisabledState);
}
}
break;
case CMD_AIRPLANE_TOGGLED:
// airplane mode toggled on is handled in the default state
if (mSettingsStore.isAirplaneModeOn()) {
return NOT_HANDLED;
} else {
// when airplane mode is toggled off, but wifi is on, we can keep it on
log("airplane mode toggled - and airplane mode is off. return handled");
return HANDLED;
}
case CMD_STA_START_FAILURE:
if (!checkScanOnlyModeAvailable()) {
transitionTo(mStaDisabledState);
} else {
transitionTo(mStaDisabledWithScanState);
}
break;
case CMD_SET_AP:
if (msg.arg1 == 1) {
// remember that we were enabled, but pass the command up to start softap
mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_ENABLED);
}
return NOT_HANDLED;
case CMD_AP_STOPPED:
// already in a wifi mode, no need to check where we should go with softap
// stopped
break;
case CMD_STA_STOPPED:
// Client mode stopped. head to Disabled to wait for next command
transitionTo(mStaDisabledState);
break;
case CMD_RECOVERY_RESTART_WIFI:
final String bugTitle;
final String bugDetail;
if (msg.arg1 < SelfRecovery.REASON_STRINGS.length && msg.arg1 >= 0) {
bugDetail = SelfRecovery.REASON_STRINGS[msg.arg1];
bugTitle = "Wi-Fi BugReport: " + bugDetail;
} else {
bugDetail = "";
bugTitle = "Wi-Fi BugReport";
}
if (msg.arg1 != SelfRecovery.REASON_LAST_RESORT_WATCHDOG) {
mHandler.post(() -> mClientModeImpl.takeBugReport(bugTitle, bugDetail));
}
// after the bug report trigger, more handling needs to be done
return NOT_HANDLED;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class StaDisabledWithScanState extends State {
@Override
public void enter() {
changeMode(ModeStateMachine.CMD_START_SCAN_ONLY_MODE);
}
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_WIFI_TOGGLED:
if (mSettingsStore.isWifiToggleEnabled()) {
transitionTo(mStaEnabledState);
}
break;
case CMD_SCAN_ALWAYS_MODE_CHANGED:
if (!checkScanOnlyModeAvailable()) {
log("StaDisabledWithScanState: scan no longer available");
transitionTo(mStaDisabledState);
}
break;
case CMD_SET_AP:
if (msg.arg1 == 1) {
// remember that we were disabled, but pass the command up to start softap
mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
}
return NOT_HANDLED;
case CMD_AP_STOPPED:
// already in a wifi mode, no need to check where we should go with softap
// stopped
break;
case CMD_SCANNING_STOPPED:
// stopped due to interface destruction - return to disabled and wait
log("WifiController: SCANNING_STOPPED when in scan mode -> StaDisabled");
transitionTo(mStaDisabledState);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class EcmState extends State {
/**
* we can enter EcmState either because an emergency call started or because
* emergency callback mode started. This count keeps track of how many such
* events happened; so we can exit after all are undone
*/
private int mEcmEntryCount;
@Override
public void enter() {
stopSoftAPMode(WifiManager.IFACE_IP_MODE_UNSPECIFIED);
boolean configWiFiDisableInECBM =
mFacade.getConfigWiFiDisableInECBM(mContext);
log("WifiController msg getConfigWiFiDisableInECBM "
+ configWiFiDisableInECBM);
if (configWiFiDisableInECBM) {
shutdownWifi();
}
mEcmEntryCount = 1;
}
/**
* Handles messages received while in EcmMode.
*/
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_EMERGENCY_CALL_STATE_CHANGED:
case CMD_EMERGENCY_MODE_CHANGED:
if (msg.arg1 == 1) {
mEcmEntryCount++;
} else {
mEcmEntryCount--;
}
if (mEcmEntryCount <= 0) {
if (mSettingsStore.isWifiToggleEnabled()) {
transitionTo(mStaEnabledState);
} else if (checkScanOnlyModeAvailable()) {
transitionTo(mStaDisabledWithScanState);
} else {
transitionTo(mStaDisabledState);
}
}
return HANDLED;
case CMD_RECOVERY_RESTART_WIFI:
case CMD_RECOVERY_DISABLE_WIFI:
// do not want to restart wifi if we are in emergency mode
return HANDLED;
case CMD_AP_STOPPED:
case CMD_SCANNING_STOPPED:
case CMD_STA_STOPPED:
// do not want to trigger a mode switch if we are in emergency mode
return HANDLED;
case CMD_SET_AP:
// do not want to start softap if we are in emergency mode
return HANDLED;
default:
return NOT_HANDLED;
}
}
}
}
}