/*
|
* 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.content.Intent;
|
import android.net.wifi.WifiManager;
|
import android.os.Looper;
|
import android.os.Message;
|
import android.os.UserHandle;
|
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 Client Mode where we connect to configured networks.
|
*/
|
public class ClientModeManager implements ActiveModeManager {
|
private static final String TAG = "WifiClientModeManager";
|
|
private final ClientModeStateMachine mStateMachine;
|
|
private final Context mContext;
|
private final WifiNative mWifiNative;
|
|
private final WifiMetrics mWifiMetrics;
|
private final Listener mListener;
|
private final ClientModeImpl mClientModeImpl;
|
|
private String mClientInterfaceName;
|
private boolean mIfaceIsUp = false;
|
|
private boolean mExpectedStop = false;
|
|
ClientModeManager(Context context, @NonNull Looper looper, WifiNative wifiNative,
|
Listener listener, WifiMetrics wifiMetrics, ClientModeImpl clientModeImpl) {
|
mContext = context;
|
mWifiNative = wifiNative;
|
mListener = listener;
|
mWifiMetrics = wifiMetrics;
|
mClientModeImpl = clientModeImpl;
|
mStateMachine = new ClientModeStateMachine(looper);
|
}
|
|
/**
|
* Start client mode.
|
*/
|
public void start() {
|
mStateMachine.sendMessage(ClientModeStateMachine.CMD_START);
|
}
|
|
/**
|
* Disconnect from any currently connected networks and stop client mode.
|
*/
|
public void stop() {
|
Log.d(TAG, " currentstate: " + getCurrentStateName());
|
mExpectedStop = true;
|
if (mClientInterfaceName != null) {
|
if (mIfaceIsUp) {
|
updateWifiState(WifiManager.WIFI_STATE_DISABLING,
|
WifiManager.WIFI_STATE_ENABLED);
|
} else {
|
updateWifiState(WifiManager.WIFI_STATE_DISABLING,
|
WifiManager.WIFI_STATE_ENABLING);
|
}
|
}
|
mStateMachine.quitNow();
|
}
|
|
public @ScanMode int getScanMode() {
|
return SCAN_WITH_HIDDEN_NETWORKS;
|
}
|
|
/**
|
* Dump info about this ClientMode manager.
|
*/
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
pw.println("--Dump of ClientModeManager--");
|
|
pw.println("current StateMachine mode: " + getCurrentStateName());
|
pw.println("mClientInterfaceName: " + mClientInterfaceName);
|
pw.println("mIfaceIsUp: " + mIfaceIsUp);
|
}
|
|
/**
|
* Listener for ClientMode 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 and send the broadcast.
|
* @param newState new Wifi state
|
* @param currentState current wifi state
|
*/
|
private void updateWifiState(int newState, int currentState) {
|
if (!mExpectedStop) {
|
mListener.onStateChanged(newState);
|
} else {
|
Log.d(TAG, "expected stop, not triggering callbacks: newState = " + newState);
|
}
|
|
// 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 (newState == WifiManager.WIFI_STATE_UNKNOWN
|
|| newState == WifiManager.WIFI_STATE_DISABLED) {
|
mExpectedStop = true;
|
}
|
|
if (newState == WifiManager.WIFI_STATE_UNKNOWN) {
|
// do not need to broadcast failure to system
|
return;
|
}
|
|
mClientModeImpl.setWifiStateForApiCalls(newState);
|
|
final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
|
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
|
intent.putExtra(WifiManager.EXTRA_WIFI_STATE, newState);
|
intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, currentState);
|
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
|
}
|
|
private class ClientModeStateMachine 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)) {
|
Log.d(TAG, "STA iface " + ifaceName + " was destroyed, stopping client mode");
|
|
// we must immediately clean up state in ClientModeImpl to unregister
|
// all client mode related objects
|
// Note: onDestroyed is only called from the ClientModeImpl thread
|
mClientModeImpl.handleIfaceDestroyed();
|
|
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);
|
}
|
}
|
};
|
|
ClientModeStateMachine(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;
|
mIfaceIsUp = false;
|
}
|
|
@Override
|
public boolean processMessage(Message message) {
|
switch (message.what) {
|
case CMD_START:
|
updateWifiState(WifiManager.WIFI_STATE_ENABLING,
|
WifiManager.WIFI_STATE_DISABLED);
|
|
mClientInterfaceName =
|
mWifiNative.setupInterfaceForClientInConnectivityMode(
|
mWifiNativeInterfaceCallback);
|
if (TextUtils.isEmpty(mClientInterfaceName)) {
|
Log.e(TAG, "Failed to create ClientInterface. Sit in Idle");
|
updateWifiState(WifiManager.WIFI_STATE_UNKNOWN,
|
WifiManager.WIFI_STATE_ENABLING);
|
updateWifiState(WifiManager.WIFI_STATE_DISABLED,
|
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 client mode");
|
mClientModeImpl.setOperationalMode(ClientModeImpl.CONNECT_MODE,
|
mClientInterfaceName);
|
updateWifiState(WifiManager.WIFI_STATE_ENABLED,
|
WifiManager.WIFI_STATE_ENABLING);
|
} else {
|
if (mClientModeImpl.isConnectedMacRandomizationEnabled()) {
|
// Handle the error case where our underlying interface went down if we
|
// do not have mac randomization enabled (b/72459123).
|
return;
|
}
|
// if the interface goes down we should exit and go back to idle state.
|
Log.d(TAG, "interface down!");
|
updateWifiState(WifiManager.WIFI_STATE_UNKNOWN,
|
WifiManager.WIFI_STATE_ENABLED);
|
mStateMachine.sendMessage(CMD_INTERFACE_DOWN);
|
}
|
}
|
|
@Override
|
public void enter() {
|
Log.d(TAG, "entering StartedState");
|
mIfaceIsUp = false;
|
onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName));
|
}
|
|
@Override
|
public boolean processMessage(Message message) {
|
switch(message.what) {
|
case CMD_START:
|
// Already started, ignore this command.
|
break;
|
case CMD_INTERFACE_DOWN:
|
Log.e(TAG, "Detected an interface down, reporting failure to SelfRecovery");
|
mClientModeImpl.failureDetected(SelfRecovery.REASON_STA_IFACE_DOWN);
|
|
updateWifiState(WifiManager.WIFI_STATE_DISABLING,
|
WifiManager.WIFI_STATE_UNKNOWN);
|
transitionTo(mIdleState);
|
break;
|
case CMD_INTERFACE_STATUS_CHANGED:
|
boolean isUp = message.arg1 == 1;
|
onUpChanged(isUp);
|
break;
|
case CMD_INTERFACE_DESTROYED:
|
Log.d(TAG, "interface destroyed - client mode stopping");
|
|
updateWifiState(WifiManager.WIFI_STATE_DISABLING,
|
WifiManager.WIFI_STATE_ENABLED);
|
mClientInterfaceName = null;
|
transitionTo(mIdleState);
|
break;
|
default:
|
return NOT_HANDLED;
|
}
|
return HANDLED;
|
}
|
|
/**
|
* Clean up state, unregister listeners and update wifi state.
|
*/
|
@Override
|
public void exit() {
|
mClientModeImpl.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
|
|
if (mClientInterfaceName != null) {
|
mWifiNative.teardownInterface(mClientInterfaceName);
|
mClientInterfaceName = null;
|
mIfaceIsUp = false;
|
}
|
|
updateWifiState(WifiManager.WIFI_STATE_DISABLED,
|
WifiManager.WIFI_STATE_DISABLING);
|
|
// once we leave started, nothing else to do... stop the state machine
|
mStateMachine.quitNow();
|
}
|
}
|
}
|
}
|