blob: 3ac3cde586b8106ac37d10bda4afd9a77d1bfd87 [file] [log] [blame]
/*
* Copyright (C) 2013 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.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.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.android.internal.R;
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;
/**
* WifiController is the class used to manage wifi state for various operating
* modes (normal, airplane, wifi hotspot, etc.).
*/
public class WifiController extends StateMachine {
private static final String TAG = "WifiController";
private final Context mContext;
// 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;
/* References to values tracked in WifiService */
private final ClientModeImpl mClientModeImpl;
private final Looper mClientModeImplLooper;
private final ActiveModeWarden mActiveModeWarden;
private final WifiSettingsStore mSettingsStore;
private final FrameworkFacade mFacade;
private final WifiPermissionsUtil mWifiPermissionsUtil;
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(Context context, ClientModeImpl clientModeImpl, Looper clientModeImplLooper,
WifiSettingsStore wss, Looper wifiServiceLooper, FrameworkFacade f,
ActiveModeWarden amw, WifiPermissionsUtil wifiPermissionsUtil) {
super(TAG, wifiServiceLooper);
mFacade = f;
mContext = context;
mClientModeImpl = clientModeImpl;
mClientModeImplLooper = clientModeImplLooper;
mActiveModeWarden = amw;
mSettingsStore = wss;
mWifiPermissionsUtil = wifiPermissionsUtil;
addState(mDefaultState); {
addState(mStaDisabledState, mDefaultState);
addState(mStaEnabledState, mDefaultState);
addState(mStaDisabledWithScanState, mDefaultState);
addState(mEcmState, mDefaultState);
}
setLogRecSize(100);
setLogOnlyTransitions(false);
// register for state updates via callbacks (vs the intents registered below)
mActiveModeWarden.registerScanOnlyCallback(state -> {
if (state == WifiManager.WIFI_STATE_UNKNOWN) {
Log.d(TAG, "ScanOnlyMode unexpected failure: state unknown");
} else if (state == WifiManager.WIFI_STATE_DISABLED) {
Log.d(TAG, "ScanOnlyMode stopped");
sendMessage(CMD_SCANNING_STOPPED);
} else if (state == WifiManager.WIFI_STATE_ENABLED) {
// scan mode is ready to go
Log.d(TAG, "scan mode active");
} else {
Log.d(TAG, "unexpected state update: " + state);
}
});
mActiveModeWarden.registerClientModeCallback(state -> {
if (state == WifiManager.WIFI_STATE_UNKNOWN) {
logd("ClientMode unexpected failure: state unknown");
sendMessage(CMD_STA_START_FAILURE);
} else if (state == WifiManager.WIFI_STATE_DISABLED) {
logd("ClientMode stopped");
sendMessage(CMD_STA_STOPPED);
} else if (state == WifiManager.WIFI_STATE_ENABLED) {
// scan mode is ready to go
logd("client mode active");
} else {
logd("unexpected state update: " + state);
}
});
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");
mActiveModeWarden.shutdownWifi();
transitionTo(mStaDisabledState);
break;
case CMD_RECOVERY_RESTART_WIFI:
deferMessage(obtainMessage(CMD_DEFERRED_RECOVERY_RESTART_WIFI));
mActiveModeWarden.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) {
mActiveModeWarden.enterSoftAPMode((SoftApModeConfiguration) msg.obj);
} else {
mActiveModeWarden.stopSoftAPMode(msg.arg2);
}
break;
case CMD_AIRPLANE_TOGGLED:
if (mSettingsStore.isAirplaneModeOn()) {
log("Airplane mode toggled, shutdown all modes");
mActiveModeWarden.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() {
mActiveModeWarden.disableWifi();
}
@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()");
mActiveModeWarden.enterClientMode();
}
@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) {
new Handler(mClientModeImplLooper)
.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() {
// now trigger the actual mode switch in ActiveModeWarden
mActiveModeWarden.enterScanOnlyMode();
}
@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() {
mActiveModeWarden.stopSoftAPMode(WifiManager.IFACE_IP_MODE_UNSPECIFIED);
boolean configWiFiDisableInECBM =
mFacade.getConfigWiFiDisableInECBM(mContext);
log("WifiController msg getConfigWiFiDisableInECBM "
+ configWiFiDisableInECBM);
if (configWiFiDisableInECBM) {
mActiveModeWarden.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;
}
}
}
}