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 17 package com.android.server.wifi; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.net.wifi.WifiManager; 22 import android.os.Looper; 23 import android.os.Message; 24 import android.text.TextUtils; 25 import android.util.Log; 26 27 import com.android.internal.util.IState; 28 import com.android.internal.util.State; 29 import com.android.internal.util.StateMachine; 30 import com.android.server.wifi.WifiNative.InterfaceCallback; 31 32 import java.io.FileDescriptor; 33 import java.io.PrintWriter; 34 35 /** 36 * Manager WiFi in Scan Only Mode - no network connections. 37 */ 38 public class ScanOnlyModeManager implements ActiveModeManager { 39 40 private final ScanOnlyModeStateMachine mStateMachine; 41 42 private static final String TAG = "WifiScanOnlyModeManager"; 43 44 private final Context mContext; 45 private final WifiNative mWifiNative; 46 47 private final WifiMetrics mWifiMetrics; 48 private final Listener mListener; 49 private final ScanRequestProxy mScanRequestProxy; 50 private final WakeupController mWakeupController; 51 52 private String mClientInterfaceName; 53 private boolean mIfaceIsUp = false; 54 55 private boolean mExpectedStop = false; 56 57 ScanOnlyModeManager(@NonNull Context context, @NonNull Looper looper, 58 @NonNull WifiNative wifiNative, @NonNull Listener listener, 59 @NonNull WifiMetrics wifiMetrics, 60 @NonNull ScanRequestProxy scanRequestProxy, 61 @NonNull WakeupController wakeupController) { 62 mContext = context; 63 mWifiNative = wifiNative; 64 mListener = listener; 65 mWifiMetrics = wifiMetrics; 66 mScanRequestProxy = scanRequestProxy; 67 mWakeupController = wakeupController; 68 mStateMachine = new ScanOnlyModeStateMachine(looper); 69 } 70 71 /** 72 * Start scan only mode. 73 */ 74 public void start() { 75 mStateMachine.sendMessage(ScanOnlyModeStateMachine.CMD_START); 76 } 77 78 /** 79 * Cancel any pending scans and stop scan mode. 80 */ 81 public void stop() { 82 Log.d(TAG, " currentstate: " + getCurrentStateName()); 83 mExpectedStop = true; 84 mStateMachine.quitNow(); 85 } 86 87 /** 88 * Dump info about this ScanOnlyMode manager. 89 */ 90 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 91 pw.println("--Dump of ScanOnlyModeManager--"); 92 93 pw.println("current StateMachine mode: " + getCurrentStateName()); 94 pw.println("mClientInterfaceName: " + mClientInterfaceName); 95 pw.println("mIfaceIsUp: " + mIfaceIsUp); 96 } 97 98 /** 99 * Listener for ScanOnlyMode state changes. 100 */ 101 public interface Listener { 102 /** 103 * Invoke when wifi state changes. 104 * @param state new wifi state 105 */ 106 void onStateChanged(int state); 107 } 108 109 private String getCurrentStateName() { 110 IState currentState = mStateMachine.getCurrentState(); 111 112 if (currentState != null) { 113 return currentState.getName(); 114 } 115 116 return "StateMachine not active"; 117 } 118 119 /** 120 * Update Wifi state. 121 * @param state new Wifi state 122 */ 123 private void updateWifiState(int state) { 124 if (mExpectedStop) { 125 Log.d(TAG, "expected stop, not triggering callbacks: state = " + state); 126 return; 127 } 128 129 // Once we report the mode has stopped/failed any other stop signals are redundant 130 // note: this can happen in failure modes where we get multiple callbacks as underlying 131 // components/interface stops or the underlying interface is destroyed in cleanup 132 if (state == WifiManager.WIFI_STATE_UNKNOWN || state == WifiManager.WIFI_STATE_DISABLED) { 133 mExpectedStop = true; 134 } 135 136 mListener.onStateChanged(state); 137 } 138 139 private class ScanOnlyModeStateMachine extends StateMachine { 140 // Commands for the state machine. 141 public static final int CMD_START = 0; 142 public static final int CMD_INTERFACE_STATUS_CHANGED = 3; 143 public static final int CMD_INTERFACE_DESTROYED = 4; 144 public static final int CMD_INTERFACE_DOWN = 5; 145 146 private final State mIdleState = new IdleState(); 147 private final State mStartedState = new StartedState(); 148 149 private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() { 150 @Override 151 public void onDestroyed(String ifaceName) { 152 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) { 153 sendMessage(CMD_INTERFACE_DESTROYED); 154 } 155 } 156 157 @Override 158 public void onUp(String ifaceName) { 159 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) { 160 sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1); 161 } 162 } 163 164 @Override 165 public void onDown(String ifaceName) { 166 if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) { 167 sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0); 168 } 169 } 170 }; 171 172 ScanOnlyModeStateMachine(Looper looper) { 173 super(TAG, looper); 174 175 addState(mIdleState); 176 addState(mStartedState); 177 178 setInitialState(mIdleState); 179 start(); 180 } 181 182 private class IdleState extends State { 183 184 @Override 185 public void enter() { 186 Log.d(TAG, "entering IdleState"); 187 mClientInterfaceName = null; 188 } 189 190 @Override 191 public boolean processMessage(Message message) { 192 switch (message.what) { 193 case CMD_START: 194 mClientInterfaceName = mWifiNative.setupInterfaceForClientMode(true, 195 mWifiNativeInterfaceCallback); 196 if (TextUtils.isEmpty(mClientInterfaceName)) { 197 Log.e(TAG, "Failed to create ClientInterface. Sit in Idle"); 198 updateWifiState(WifiManager.WIFI_STATE_UNKNOWN); 199 break; 200 } 201 // we have a new scanning interface, make sure scanner knows we aren't 202 // ready yet and clear out the ScanRequestProxy 203 sendScanAvailableBroadcast(false); 204 // explicitly disable scanning for hidden networks in case we were 205 // previously in client mode 206 mScanRequestProxy.enableScanningForHiddenNetworks(false); 207 mScanRequestProxy.clearScanResults(); 208 209 transitionTo(mStartedState); 210 break; 211 default: 212 Log.d(TAG, "received an invalid message: " + message); 213 return NOT_HANDLED; 214 } 215 return HANDLED; 216 } 217 } 218 219 private class StartedState extends State { 220 221 private void onUpChanged(boolean isUp) { 222 if (isUp == mIfaceIsUp) { 223 return; // no change 224 } 225 mIfaceIsUp = isUp; 226 if (isUp) { 227 Log.d(TAG, "Wifi is ready to use for scanning"); 228 mWakeupController.start(); 229 sendScanAvailableBroadcast(true); 230 updateWifiState(WifiManager.WIFI_STATE_ENABLED); 231 } else { 232 // if the interface goes down we should exit and go back to idle state. 233 Log.d(TAG, "interface down - stop scan mode"); 234 mStateMachine.sendMessage(CMD_INTERFACE_DOWN); 235 } 236 } 237 238 @Override 239 public void enter() { 240 Log.d(TAG, "entering StartedState"); 241 mScanRequestProxy.enableScanningForHiddenNetworks(false); 242 243 mIfaceIsUp = false; 244 onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName)); 245 } 246 247 @Override 248 public boolean processMessage(Message message) { 249 switch(message.what) { 250 case CMD_START: 251 // Already started, ignore this command. 252 break; 253 case CMD_INTERFACE_DESTROYED: 254 Log.d(TAG, "Interface cleanly destroyed, report scan mode stop."); 255 mClientInterfaceName = null; 256 transitionTo(mIdleState); 257 break; 258 case CMD_INTERFACE_STATUS_CHANGED: 259 boolean isUp = message.arg1 == 1; 260 onUpChanged(isUp); 261 break; 262 case CMD_INTERFACE_DOWN: 263 Log.d(TAG, "interface down! stop mode"); 264 updateWifiState(WifiManager.WIFI_STATE_UNKNOWN); 265 transitionTo(mIdleState); 266 break; 267 default: 268 return NOT_HANDLED; 269 } 270 return HANDLED; 271 } 272 273 /** 274 * Clean up state and unregister listeners. 275 */ 276 @Override 277 public void exit() { 278 mWakeupController.stop(); 279 if (mClientInterfaceName != null) { 280 mWifiNative.teardownInterface(mClientInterfaceName); 281 mClientInterfaceName = null; 282 } 283 updateWifiState(WifiManager.WIFI_STATE_DISABLED); 284 285 // once we leave started, nothing else to do... stop the state machine 286 mStateMachine.quitNow(); 287 } 288 } 289 } 290 291 private void sendScanAvailableBroadcast(boolean available) { 292 sendScanAvailableBroadcast(mContext, available); 293 } 294 } 295