1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.wifi; 18 19import android.annotation.NonNull; 20import android.content.Context; 21import android.content.Intent; 22import android.net.wifi.WifiManager; 23import android.os.Looper; 24import android.os.Message; 25import android.os.UserHandle; 26import android.text.TextUtils; 27import android.util.Log; 28 29import com.android.internal.util.IState; 30import com.android.internal.util.State; 31import com.android.internal.util.StateMachine; 32import com.android.server.wifi.WifiNative.InterfaceCallback; 33 34import java.io.FileDescriptor; 35import java.io.PrintWriter; 36 37/** 38 * Manager WiFi in Client Mode where we connect to configured networks. 39 */ 40public class ClientModeManager implements ActiveModeManager { 41 private static final String TAG = "WifiClientModeManager"; 42 43 private final ClientModeStateMachine mStateMachine; 44 45 private final Context mContext; 46 private final WifiNative mWifiNative; 47 48 private final WifiMetrics mWifiMetrics; 49 private final Listener mListener; 50 private final ScanRequestProxy mScanRequestProxy; 51 private final WifiStateMachine mWifiStateMachine; 52 53 private String mClientInterfaceName; 54 private boolean mIfaceIsUp = false; 55 56 private boolean mExpectedStop = false; 57 58 ClientModeManager(Context context, @NonNull Looper looper, WifiNative wifiNative, 59 Listener listener, WifiMetrics wifiMetrics, ScanRequestProxy scanRequestProxy, 60 WifiStateMachine wifiStateMachine) { 61 mContext = context; 62 mWifiNative = wifiNative; 63 mListener = listener; 64 mWifiMetrics = wifiMetrics; 65 mScanRequestProxy = scanRequestProxy; 66 mWifiStateMachine = wifiStateMachine; 67 mStateMachine = new ClientModeStateMachine(looper); 68 } 69 70 /** 71 * Start client mode. 72 */ 73 public void start() { 74 mStateMachine.sendMessage(ClientModeStateMachine.CMD_START); 75 } 76 77 /** 78 * Disconnect from any currently connected networks and stop client mode. 79 */ 80 public void stop() { 81 Log.d(TAG, " currentstate: " + getCurrentStateName()); 82 mExpectedStop = true; 83 if (mClientInterfaceName != null) { 84 if (mIfaceIsUp) { 85 updateWifiState(WifiManager.WIFI_STATE_DISABLING, 86 WifiManager.WIFI_STATE_ENABLED); 87 } else { 88 updateWifiState(WifiManager.WIFI_STATE_DISABLING, 89 WifiManager.WIFI_STATE_ENABLING); 90 } 91 } 92 mStateMachine.quitNow(); 93 } 94 95 /** 96 * Dump info about this ClientMode manager. 97 */ 98 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 99 pw.println("--Dump of ClientModeManager--"); 100 101 pw.println("current StateMachine mode: " + getCurrentStateName()); 102 pw.println("mClientInterfaceName: " + mClientInterfaceName); 103 pw.println("mIfaceIsUp: " + mIfaceIsUp); 104 } 105 106 /** 107 * Listener for ClientMode state changes. 108 */ 109 public interface Listener { 110 /** 111 * Invoke when wifi state changes. 112 * @param state new wifi state 113 */ 114 void onStateChanged(int state); 115 } 116 117 private String getCurrentStateName() { 118 IState currentState = mStateMachine.getCurrentState(); 119 120 if (currentState != null) { 121 return currentState.getName(); 122 } 123 124 return "StateMachine not active"; 125 } 126 127 /** 128 * Update Wifi state and send the broadcast. 129 * @param newState new Wifi state 130 * @param currentState current wifi state 131 */ 132 private void updateWifiState(int newState, int currentState) { 133 if (!mExpectedStop) { 134 mListener.onStateChanged(newState); 135 } else { 136 Log.d(TAG, "expected stop, not triggering callbacks: newState = " + newState); 137 } 138 139 // Once we report the mode has stopped/failed any other stop signals are redundant 140 // note: this can happen in failure modes where we get multiple callbacks as underlying 141 // components/interface stops or the underlying interface is destroyed in cleanup 142 if (newState == WifiManager.WIFI_STATE_UNKNOWN 143 || newState == WifiManager.WIFI_STATE_DISABLED) { 144 mExpectedStop = true; 145 } 146 147 if (newState == WifiManager.WIFI_STATE_UNKNOWN) { 148 // do not need to broadcast failure to system 149 return; 150 } 151 152 mWifiStateMachine.setWifiStateForApiCalls(newState); 153 154 final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION); 155 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 156 intent.putExtra(WifiManager.EXTRA_WIFI_STATE, newState); 157 intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, currentState); 158 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 159 } 160 161 private class ClientModeStateMachine extends StateMachine { 162 // Commands for the state machine. 163 public static final int CMD_START = 0; 164 public static final int CMD_INTERFACE_STATUS_CHANGED = 3; 165 public static final int CMD_INTERFACE_DESTROYED = 4; 166 public static final int CMD_INTERFACE_DOWN = 5; 167 private final State mIdleState = new IdleState(); 168 private final State mStartedState = new StartedState(); 169 170 private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() { 171 @Override 172 public void onDestroyed(String ifaceName) { 173 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) { 174 Log.d(TAG, "STA iface " + ifaceName + " was destroyed, stopping client mode"); 175 176 // we must immediately clean up state in WSM to unregister all client mode 177 // related objects 178 // Note: onDestroyed is only called from the WSM thread 179 mWifiStateMachine.handleIfaceDestroyed(); 180 181 sendMessage(CMD_INTERFACE_DESTROYED); 182 } 183 } 184 185 @Override 186 public void onUp(String ifaceName) { 187 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) { 188 sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1); 189 } 190 } 191 192 @Override 193 public void onDown(String ifaceName) { 194 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) { 195 sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0); 196 } 197 } 198 }; 199 200 ClientModeStateMachine(Looper looper) { 201 super(TAG, looper); 202 203 addState(mIdleState); 204 addState(mStartedState); 205 206 setInitialState(mIdleState); 207 start(); 208 } 209 210 private class IdleState extends State { 211 212 @Override 213 public void enter() { 214 Log.d(TAG, "entering IdleState"); 215 mClientInterfaceName = null; 216 mIfaceIsUp = false; 217 } 218 219 @Override 220 public boolean processMessage(Message message) { 221 switch (message.what) { 222 case CMD_START: 223 updateWifiState(WifiManager.WIFI_STATE_ENABLING, 224 WifiManager.WIFI_STATE_DISABLED); 225 226 mClientInterfaceName = mWifiNative.setupInterfaceForClientMode( 227 false /* not low priority */, mWifiNativeInterfaceCallback); 228 if (TextUtils.isEmpty(mClientInterfaceName)) { 229 Log.e(TAG, "Failed to create ClientInterface. Sit in Idle"); 230 updateWifiState(WifiManager.WIFI_STATE_UNKNOWN, 231 WifiManager.WIFI_STATE_ENABLING); 232 updateWifiState(WifiManager.WIFI_STATE_DISABLED, 233 WifiManager.WIFI_STATE_UNKNOWN); 234 break; 235 } 236 sendScanAvailableBroadcast(false); 237 mScanRequestProxy.enableScanningForHiddenNetworks(false); 238 mScanRequestProxy.clearScanResults(); 239 transitionTo(mStartedState); 240 break; 241 default: 242 Log.d(TAG, "received an invalid message: " + message); 243 return NOT_HANDLED; 244 } 245 return HANDLED; 246 } 247 } 248 249 private class StartedState extends State { 250 251 private void onUpChanged(boolean isUp) { 252 if (isUp == mIfaceIsUp) { 253 return; // no change 254 } 255 mIfaceIsUp = isUp; 256 if (isUp) { 257 Log.d(TAG, "Wifi is ready to use for client mode"); 258 sendScanAvailableBroadcast(true); 259 mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE, 260 mClientInterfaceName); 261 updateWifiState(WifiManager.WIFI_STATE_ENABLED, 262 WifiManager.WIFI_STATE_ENABLING); 263 } else { 264 if (mWifiStateMachine.isConnectedMacRandomizationEnabled()) { 265 // Handle the error case where our underlying interface went down if we 266 // do not have mac randomization enabled (b/72459123). 267 return; 268 } 269 // if the interface goes down we should exit and go back to idle state. 270 Log.d(TAG, "interface down!"); 271 updateWifiState(WifiManager.WIFI_STATE_UNKNOWN, 272 WifiManager.WIFI_STATE_ENABLED); 273 mStateMachine.sendMessage(CMD_INTERFACE_DOWN); 274 } 275 } 276 277 @Override 278 public void enter() { 279 Log.d(TAG, "entering StartedState"); 280 mIfaceIsUp = false; 281 onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName)); 282 mScanRequestProxy.enableScanningForHiddenNetworks(true); 283 } 284 285 @Override 286 public boolean processMessage(Message message) { 287 switch(message.what) { 288 case CMD_START: 289 // Already started, ignore this command. 290 break; 291 case CMD_INTERFACE_DOWN: 292 Log.e(TAG, "Detected an interface down, reporting failure to SelfRecovery"); 293 mWifiStateMachine.failureDetected(SelfRecovery.REASON_STA_IFACE_DOWN); 294 295 updateWifiState(WifiManager.WIFI_STATE_DISABLING, 296 WifiManager.WIFI_STATE_UNKNOWN); 297 transitionTo(mIdleState); 298 break; 299 case CMD_INTERFACE_STATUS_CHANGED: 300 boolean isUp = message.arg1 == 1; 301 onUpChanged(isUp); 302 break; 303 case CMD_INTERFACE_DESTROYED: 304 Log.d(TAG, "interface destroyed - client mode stopping"); 305 306 updateWifiState(WifiManager.WIFI_STATE_DISABLING, 307 WifiManager.WIFI_STATE_ENABLED); 308 mClientInterfaceName = null; 309 transitionTo(mIdleState); 310 break; 311 default: 312 return NOT_HANDLED; 313 } 314 return HANDLED; 315 } 316 317 /** 318 * Clean up state, unregister listeners and update wifi state. 319 */ 320 @Override 321 public void exit() { 322 mWifiStateMachine.setOperationalMode(WifiStateMachine.DISABLED_MODE, null); 323 324 if (mClientInterfaceName != null) { 325 mWifiNative.teardownInterface(mClientInterfaceName); 326 mClientInterfaceName = null; 327 mIfaceIsUp = false; 328 } 329 330 updateWifiState(WifiManager.WIFI_STATE_DISABLED, 331 WifiManager.WIFI_STATE_DISABLING); 332 333 // once we leave started, nothing else to do... stop the state machine 334 mStateMachine.quitNow(); 335 } 336 } 337 338 private void sendScanAvailableBroadcast(boolean available) { 339 Log.d(TAG, "sending scan available broadcast: " + available); 340 final Intent intent = new Intent(WifiManager.WIFI_SCAN_AVAILABLE); 341 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 342 if (available) { 343 intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_ENABLED); 344 } else { 345 intent.putExtra(WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED); 346 } 347 mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); 348 } 349 } 350} 351