blob: c3547be5de17b14a4954b9b3c0134fe0838eae54 [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.Context;
import android.net.wifi.WifiManager;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.server.wifi.WifiNative.InterfaceCallback;
import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* Manager WiFi in Scan Only Mode - no network connections.
*/
public class ScanOnlyModeManager implements ActiveModeManager {
private final ScanOnlyModeStateMachine mStateMachine;
private static final String TAG = "WifiScanOnlyModeManager";
private final Context mContext;
private final WifiNative mWifiNative;
private final WifiMetrics mWifiMetrics;
private final Listener mListener;
private final WakeupController mWakeupController;
private final SarManager mSarManager;
private String mClientInterfaceName;
private boolean mIfaceIsUp = false;
private boolean mExpectedStop = false;
ScanOnlyModeManager(@NonNull Context context, @NonNull Looper looper,
@NonNull WifiNative wifiNative, @NonNull Listener listener,
@NonNull WifiMetrics wifiMetrics,
@NonNull WakeupController wakeupController,
@NonNull SarManager sarManager) {
mContext = context;
mWifiNative = wifiNative;
mListener = listener;
mWifiMetrics = wifiMetrics;
mWakeupController = wakeupController;
mSarManager = sarManager;
mStateMachine = new ScanOnlyModeStateMachine(looper);
}
/**
* Start scan only mode.
*/
public void start() {
mStateMachine.sendMessage(ScanOnlyModeStateMachine.CMD_START);
}
/**
* Cancel any pending scans and stop scan mode.
*/
public void stop() {
Log.d(TAG, " currentstate: " + getCurrentStateName());
mExpectedStop = true;
mStateMachine.quitNow();
}
public @ScanMode int getScanMode() {
return SCAN_WITHOUT_HIDDEN_NETWORKS;
}
/**
* Dump info about this ScanOnlyMode manager.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("--Dump of ScanOnlyModeManager--");
pw.println("current StateMachine mode: " + getCurrentStateName());
pw.println("mClientInterfaceName: " + mClientInterfaceName);
pw.println("mIfaceIsUp: " + mIfaceIsUp);
}
/**
* Listener for ScanOnlyMode state changes.
*/
public interface Listener {
/**
* Invoke when wifi state changes.
* @param state new wifi state
*/
void onStateChanged(int state);
}
private String getCurrentStateName() {
IState currentState = mStateMachine.getCurrentState();
if (currentState != null) {
return currentState.getName();
}
return "StateMachine not active";
}
/**
* Update Wifi state.
* @param state new Wifi state
*/
private void updateWifiState(int state) {
if (mExpectedStop) {
Log.d(TAG, "expected stop, not triggering callbacks: state = " + state);
return;
}
// Once we report the mode has stopped/failed any other stop signals are redundant
// note: this can happen in failure modes where we get multiple callbacks as underlying
// components/interface stops or the underlying interface is destroyed in cleanup
if (state == WifiManager.WIFI_STATE_UNKNOWN || state == WifiManager.WIFI_STATE_DISABLED) {
mExpectedStop = true;
}
mListener.onStateChanged(state);
}
private class ScanOnlyModeStateMachine extends StateMachine {
// Commands for the state machine.
public static final int CMD_START = 0;
public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
public static final int CMD_INTERFACE_DESTROYED = 4;
public static final int CMD_INTERFACE_DOWN = 5;
private final State mIdleState = new IdleState();
private final State mStartedState = new StartedState();
private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() {
@Override
public void onDestroyed(String ifaceName) {
if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
sendMessage(CMD_INTERFACE_DESTROYED);
}
}
@Override
public void onUp(String ifaceName) {
if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1);
}
}
@Override
public void onDown(String ifaceName) {
if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0);
}
}
};
ScanOnlyModeStateMachine(Looper looper) {
super(TAG, looper);
addState(mIdleState);
addState(mStartedState);
setInitialState(mIdleState);
start();
}
private class IdleState extends State {
@Override
public void enter() {
Log.d(TAG, "entering IdleState");
mClientInterfaceName = null;
}
@Override
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_START:
mClientInterfaceName = mWifiNative.setupInterfaceForClientInScanMode(
mWifiNativeInterfaceCallback);
if (TextUtils.isEmpty(mClientInterfaceName)) {
Log.e(TAG, "Failed to create ClientInterface. Sit in Idle");
updateWifiState(WifiManager.WIFI_STATE_UNKNOWN);
break;
}
transitionTo(mStartedState);
break;
default:
Log.d(TAG, "received an invalid message: " + message);
return NOT_HANDLED;
}
return HANDLED;
}
}
private class StartedState extends State {
private void onUpChanged(boolean isUp) {
if (isUp == mIfaceIsUp) {
return; // no change
}
mIfaceIsUp = isUp;
if (isUp) {
Log.d(TAG, "Wifi is ready to use for scanning");
mWakeupController.start();
updateWifiState(WifiManager.WIFI_STATE_ENABLED);
} else {
// if the interface goes down we should exit and go back to idle state.
Log.d(TAG, "interface down - stop scan mode");
mStateMachine.sendMessage(CMD_INTERFACE_DOWN);
}
}
@Override
public void enter() {
Log.d(TAG, "entering StartedState");
mIfaceIsUp = false;
onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName));
mSarManager.setScanOnlyWifiState(WifiManager.WIFI_STATE_ENABLED);
}
@Override
public boolean processMessage(Message message) {
switch(message.what) {
case CMD_START:
// Already started, ignore this command.
break;
case CMD_INTERFACE_DESTROYED:
Log.d(TAG, "Interface cleanly destroyed, report scan mode stop.");
mClientInterfaceName = null;
transitionTo(mIdleState);
break;
case CMD_INTERFACE_STATUS_CHANGED:
boolean isUp = message.arg1 == 1;
onUpChanged(isUp);
break;
case CMD_INTERFACE_DOWN:
Log.d(TAG, "interface down! stop mode");
updateWifiState(WifiManager.WIFI_STATE_UNKNOWN);
transitionTo(mIdleState);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
/**
* Clean up state and unregister listeners.
*/
@Override
public void exit() {
mWakeupController.stop();
if (mClientInterfaceName != null) {
mWifiNative.teardownInterface(mClientInterfaceName);
mClientInterfaceName = null;
}
updateWifiState(WifiManager.WIFI_STATE_DISABLED);
mSarManager.setScanOnlyWifiState(WifiManager.WIFI_STATE_DISABLED);
// once we leave started, nothing else to do... stop the state machine
mStateMachine.quitNow();
}
}
}
}