/*
|
* 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();
|
}
|
}
|
}
|
}
|