/*
|
* 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.Context;
|
import android.net.wifi.WifiConfiguration;
|
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.app.IBatteryStats;
|
import com.android.internal.util.Protocol;
|
import com.android.internal.util.State;
|
import com.android.internal.util.StateMachine;
|
import com.android.server.wifi.WifiNative.StatusListener;
|
|
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 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 DefaultModeManager mDefaultModeManager;
|
|
private final WifiInjector mWifiInjector;
|
private final Context mContext;
|
private final Looper mLooper;
|
private final Handler mHandler;
|
private final WifiNative mWifiNative;
|
private final IBatteryStats mBatteryStats;
|
private final SelfRecovery mSelfRecovery;
|
private BaseWifiDiagnostics mWifiDiagnostics;
|
private final ScanRequestProxy mScanRequestProxy;
|
|
// 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 StatusListener mWifiNativeStatusListener;
|
|
private WifiManager.SoftApCallback mSoftApCallback;
|
private ScanOnlyModeManager.Listener mScanOnlyCallback;
|
private ClientModeManager.Listener mClientModeCallback;
|
|
/**
|
* Called from WifiServiceImpl to register a callback for notifications from SoftApManager
|
*/
|
public void registerSoftApCallback(@NonNull WifiManager.SoftApCallback callback) {
|
mSoftApCallback = callback;
|
}
|
|
/**
|
* Called from WifiController to register a callback for notifications from ScanOnlyModeManager
|
*/
|
public void registerScanOnlyCallback(@NonNull ScanOnlyModeManager.Listener callback) {
|
mScanOnlyCallback = callback;
|
}
|
|
/**
|
* Called from WifiController to register a callback for notifications from ClientModeManager
|
*/
|
public void registerClientModeCallback(@NonNull ClientModeManager.Listener callback) {
|
mClientModeCallback = callback;
|
}
|
|
ActiveModeWarden(WifiInjector wifiInjector,
|
Context context,
|
Looper looper,
|
WifiNative wifiNative,
|
DefaultModeManager defaultModeManager,
|
IBatteryStats batteryStats) {
|
mWifiInjector = wifiInjector;
|
mContext = context;
|
mLooper = looper;
|
mHandler = new Handler(looper);
|
mWifiNative = wifiNative;
|
mActiveModeManagers = new ArraySet();
|
mDefaultModeManager = defaultModeManager;
|
mBatteryStats = batteryStats;
|
mSelfRecovery = mWifiInjector.getSelfRecovery();
|
mWifiDiagnostics = mWifiInjector.getWifiDiagnostics();
|
mScanRequestProxy = mWifiInjector.getScanRequestProxy();
|
mModeStateMachine = new ModeStateMachine();
|
mWifiNativeStatusListener = new WifiNativeStatusListener();
|
mWifiNative.registerStatusListener(mWifiNativeStatusListener);
|
}
|
|
/**
|
* Method to switch wifi into client mode where connections to configured networks will be
|
* attempted.
|
*/
|
public void enterClientMode() {
|
changeMode(ModeStateMachine.CMD_START_CLIENT_MODE);
|
}
|
|
/**
|
* Method to switch wifi into scan only mode where network connection attempts will not be made.
|
*
|
* This mode is utilized by location scans. If wifi is disabled by a user, but they have
|
* previously configured their device to perform location scans, this mode allows wifi to
|
* fulfill the location scan requests but will not be used for connectivity.
|
*/
|
public void enterScanOnlyMode() {
|
changeMode(ModeStateMachine.CMD_START_SCAN_ONLY_MODE);
|
}
|
|
/**
|
* 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 wifiConfig SoftApModeConfiguration for the hostapd softap
|
*/
|
public void enterSoftAPMode(@NonNull SoftApModeConfiguration wifiConfig) {
|
mHandler.post(() -> {
|
startSoftAp(wifiConfig);
|
});
|
}
|
|
/**
|
* 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 disable wifi in sta/client mode scenarios.
|
*
|
* This mode will stop any client/scan modes and will not perform any network scans.
|
*/
|
public void disableWifi() {
|
changeMode(ModeStateMachine.CMD_DISABLE_WIFI);
|
}
|
|
/**
|
* 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 ClientModeImpl 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);
|
}
|
|
/**
|
* Helper class to wrap the ActiveModeManager callback objects.
|
*/
|
private class ModeCallback {
|
ActiveModeManager mActiveManager;
|
|
void setActiveModeManager(ActiveModeManager manager) {
|
mActiveManager = manager;
|
}
|
|
ActiveModeManager getActiveModeManager() {
|
return mActiveManager;
|
}
|
}
|
|
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() {
|
return getCurrentState().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 {
|
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) {
|
Log.d(TAG, "received a message in WifiDisabledState: " + message);
|
if (checkForAndHandleModeChange(message)) {
|
return HANDLED;
|
}
|
return NOT_HANDLED;
|
}
|
|
@Override
|
public void exit() {
|
// do not have an active mode manager... nothing to clean up
|
}
|
|
}
|
|
class ClientModeActiveState extends ModeActiveState {
|
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) {
|
if (checkForAndHandleModeChange(message)) {
|
return HANDLED;
|
}
|
|
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.");
|
// notify WifiController that ClientMode failed
|
mClientModeCallback.onStateChanged(WifiManager.WIFI_STATE_UNKNOWN);
|
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.");
|
// notify WifiController that ClientMode stopped
|
mClientModeCallback.onStateChanged(WifiManager.WIFI_STATE_DISABLED);
|
mModeStateMachine.transitionTo(mWifiDisabledState);
|
break;
|
default:
|
return NOT_HANDLED;
|
}
|
return NOT_HANDLED;
|
}
|
}
|
|
class ScanOnlyModeActiveState extends ModeActiveState {
|
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) {
|
if (checkForAndHandleModeChange(message)) {
|
return HANDLED;
|
}
|
|
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.");
|
// notify WifiController that ScanOnlyMode failed
|
mScanOnlyCallback.onStateChanged(WifiManager.WIFI_STATE_UNKNOWN);
|
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.");
|
// notify WifiController that ScanOnlyMode stopped
|
mScanOnlyCallback.onStateChanged(WifiManager.WIFI_STATE_DISABLED);
|
mModeStateMachine.transitionTo(mWifiDisabledState);
|
break;
|
default:
|
return NOT_HANDLED;
|
}
|
return HANDLED;
|
}
|
}
|
} // class ModeStateMachine
|
|
private class SoftApCallbackImpl extends ModeCallback implements WifiManager.SoftApCallback {
|
private int mMode;
|
|
private SoftApCallbackImpl(int mode) {
|
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);
|
}
|
|
if (mSoftApCallback != null && mMode == WifiManager.IFACE_IP_MODE_TETHERED) {
|
mSoftApCallback.onStateChanged(state, reason);
|
}
|
}
|
|
@Override
|
public void onNumClientsChanged(int numClients) {
|
if (mSoftApCallback == null) {
|
Log.d(TAG, "SoftApCallback is null. Dropping NumClientsChanged event.");
|
} else if (mMode == WifiManager.IFACE_IP_MODE_TETHERED) {
|
mSoftApCallback.onNumClientsChanged(numClients);
|
}
|
}
|
}
|
|
private void startSoftAp(SoftApModeConfiguration softapConfig) {
|
Log.d(TAG, "Starting SoftApModeManager");
|
|
WifiConfiguration config = softapConfig.getWifiConfiguration();
|
if (config != null && config.SSID != null) {
|
Log.d(TAG, "Passing config to SoftApManager! " + config);
|
} else {
|
config = null;
|
}
|
|
SoftApCallbackImpl callback = new SoftApCallbackImpl(softapConfig.getTargetMode());
|
ActiveModeManager manager = mWifiInjector.makeSoftApManager(callback, softapConfig);
|
callback.setActiveModeManager(manager);
|
manager.start();
|
mActiveModeManagers.add(manager);
|
updateBatteryStatsWifiState(true);
|
}
|
|
/**
|
* 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");
|
}
|
}
|
|
// callback used to receive callbacks about underlying native failures
|
private final class WifiNativeStatusListener implements StatusListener {
|
|
@Override
|
public void onStatusChanged(boolean isReady) {
|
if (!isReady) {
|
mHandler.post(() -> {
|
Log.e(TAG, "One of the native daemons died. Triggering recovery");
|
mWifiDiagnostics.captureBugReportData(
|
WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE);
|
|
// immediately trigger SelfRecovery if we receive a notice about an
|
// underlying daemon failure
|
mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE);
|
});
|
}
|
}
|
};
|
}
|