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