WifiMonitor.java revision 0d25534fed91f636def5776ddc4605005bd7471c
1/* 2 * Copyright (C) 2008 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 android.net.wifi; 18 19import android.util.Log; 20import android.util.Config; 21import android.net.NetworkInfo; 22 23import java.util.regex.Pattern; 24import java.util.regex.Matcher; 25 26/** 27 * Listens for events from the wpa_supplicant server, and passes them on 28 * to the {@link WifiStateMachine} for handling. Runs in its own thread. 29 * 30 * @hide 31 */ 32public class WifiMonitor { 33 34 private static final String TAG = "WifiMonitor"; 35 36 /** Events we receive from the supplicant daemon */ 37 38 private static final int CONNECTED = 1; 39 private static final int DISCONNECTED = 2; 40 private static final int STATE_CHANGE = 3; 41 private static final int SCAN_RESULTS = 4; 42 private static final int LINK_SPEED = 5; 43 private static final int TERMINATING = 6; 44 private static final int DRIVER_STATE = 7; 45 private static final int UNKNOWN = 8; 46 47 /** All events coming from the supplicant start with this prefix */ 48 private static final String eventPrefix = "CTRL-EVENT-"; 49 private static final int eventPrefixLen = eventPrefix.length(); 50 51 /** All WPA events coming from the supplicant start with this prefix */ 52 private static final String wpaEventPrefix = "WPA:"; 53 private static final String passwordKeyMayBeIncorrectEvent = 54 "pre-shared key may be incorrect"; 55 56 /** 57 * Names of events from wpa_supplicant (minus the prefix). In the 58 * format descriptions, * "<code>x</code>" 59 * designates a dynamic value that needs to be parsed out from the event 60 * string 61 */ 62 /** 63 * <pre> 64 * CTRL-EVENT-CONNECTED - Connection to xx:xx:xx:xx:xx:xx completed 65 * </pre> 66 * <code>xx:xx:xx:xx:xx:xx</code> is the BSSID of the associated access point 67 */ 68 private static final String connectedEvent = "CONNECTED"; 69 /** 70 * <pre> 71 * CTRL-EVENT-DISCONNECTED - Disconnect event - remove keys 72 * </pre> 73 */ 74 private static final String disconnectedEvent = "DISCONNECTED"; 75 /** 76 * <pre> 77 * CTRL-EVENT-STATE-CHANGE x 78 * </pre> 79 * <code>x</code> is the numerical value of the new state. 80 */ 81 private static final String stateChangeEvent = "STATE-CHANGE"; 82 /** 83 * <pre> 84 * CTRL-EVENT-SCAN-RESULTS ready 85 * </pre> 86 */ 87 private static final String scanResultsEvent = "SCAN-RESULTS"; 88 89 /** 90 * <pre> 91 * CTRL-EVENT-LINK-SPEED x Mb/s 92 * </pre> 93 * {@code x} is the link speed in Mb/sec. 94 */ 95 private static final String linkSpeedEvent = "LINK-SPEED"; 96 /** 97 * <pre> 98 * CTRL-EVENT-TERMINATING - signal x 99 * </pre> 100 * <code>x</code> is the signal that caused termination. 101 */ 102 private static final String terminatingEvent = "TERMINATING"; 103 /** 104 * <pre> 105 * CTRL-EVENT-DRIVER-STATE state 106 * </pre> 107 * <code>state</code> is either STARTED or STOPPED 108 */ 109 private static final String driverStateEvent = "DRIVER-STATE"; 110 111 /** 112 * Regex pattern for extracting an Ethernet-style MAC address from a string. 113 * Matches a strings like the following:<pre> 114 * CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]</pre> 115 */ 116 private static Pattern mConnectedEventPattern = 117 Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) "); 118 119 private final WifiStateMachine mWifiStateMachine; 120 121 /** 122 * This indicates the supplicant connection for the monitor is closed 123 */ 124 private static final String monitorSocketClosed = "connection closed"; 125 126 /** 127 * This indicates a read error on the monitor socket conenction 128 */ 129 private static final String wpaRecvError = "recv error"; 130 131 /** 132 * Tracks consecutive receive errors 133 */ 134 private int mRecvErrors = 0; 135 136 /** 137 * Max errors before we close supplicant connection 138 */ 139 private static final int MAX_RECV_ERRORS = 10; 140 141 public WifiMonitor(WifiStateMachine wifiStateMachine) { 142 mWifiStateMachine = wifiStateMachine; 143 } 144 145 public void startMonitoring() { 146 new MonitorThread().start(); 147 } 148 149 class MonitorThread extends Thread { 150 public MonitorThread() { 151 super("WifiMonitor"); 152 } 153 154 public void run() { 155 156 if (connectToSupplicant()) { 157 // Send a message indicating that it is now possible to send commands 158 // to the supplicant 159 mWifiStateMachine.notifySupplicantConnection(); 160 } else { 161 mWifiStateMachine.notifySupplicantLost(); 162 return; 163 } 164 165 //noinspection InfiniteLoopStatement 166 for (;;) { 167 String eventStr = WifiNative.waitForEvent(); 168 169 // Skip logging the common but mostly uninteresting scan-results event 170 if (Config.LOGD && eventStr.indexOf(scanResultsEvent) == -1) { 171 Log.v(TAG, "Event [" + eventStr + "]"); 172 } 173 if (!eventStr.startsWith(eventPrefix)) { 174 if (eventStr.startsWith(wpaEventPrefix) && 175 0 < eventStr.indexOf(passwordKeyMayBeIncorrectEvent)) { 176 handlePasswordKeyMayBeIncorrect(); 177 } 178 continue; 179 } 180 181 String eventName = eventStr.substring(eventPrefixLen); 182 int nameEnd = eventName.indexOf(' '); 183 if (nameEnd != -1) 184 eventName = eventName.substring(0, nameEnd); 185 if (eventName.length() == 0) { 186 if (Config.LOGD) Log.i(TAG, "Received wpa_supplicant event with empty event name"); 187 continue; 188 } 189 /* 190 * Map event name into event enum 191 */ 192 int event; 193 if (eventName.equals(connectedEvent)) 194 event = CONNECTED; 195 else if (eventName.equals(disconnectedEvent)) 196 event = DISCONNECTED; 197 else if (eventName.equals(stateChangeEvent)) 198 event = STATE_CHANGE; 199 else if (eventName.equals(scanResultsEvent)) 200 event = SCAN_RESULTS; 201 else if (eventName.equals(linkSpeedEvent)) 202 event = LINK_SPEED; 203 else if (eventName.equals(terminatingEvent)) 204 event = TERMINATING; 205 else if (eventName.equals(driverStateEvent)) { 206 event = DRIVER_STATE; 207 } 208 else 209 event = UNKNOWN; 210 211 String eventData = eventStr; 212 if (event == DRIVER_STATE || event == LINK_SPEED) 213 eventData = eventData.split(" ")[1]; 214 else if (event == STATE_CHANGE) { 215 int ind = eventStr.indexOf(" "); 216 if (ind != -1) { 217 eventData = eventStr.substring(ind + 1); 218 } 219 } else { 220 int ind = eventStr.indexOf(" - "); 221 if (ind != -1) { 222 eventData = eventStr.substring(ind + 3); 223 } 224 } 225 226 if (event == STATE_CHANGE) { 227 handleSupplicantStateChange(eventData); 228 } else if (event == DRIVER_STATE) { 229 handleDriverEvent(eventData); 230 } else if (event == TERMINATING) { 231 /** 232 * If monitor socket is closed, we have already 233 * stopped the supplicant, simply exit the monitor thread 234 */ 235 if (eventData.startsWith(monitorSocketClosed)) { 236 if (Config.LOGD) { 237 Log.d(TAG, "Monitor socket is closed, exiting thread"); 238 } 239 break; 240 } 241 242 /** 243 * Close the supplicant connection if we see 244 * too many recv errors 245 */ 246 if (eventData.startsWith(wpaRecvError)) { 247 if (++mRecvErrors > MAX_RECV_ERRORS) { 248 if (Config.LOGD) { 249 Log.d(TAG, "too many recv errors, closing connection"); 250 } 251 } else { 252 continue; 253 } 254 } 255 256 // notify and exit 257 mWifiStateMachine.notifySupplicantLost(); 258 break; 259 } else { 260 handleEvent(event, eventData); 261 } 262 mRecvErrors = 0; 263 } 264 } 265 266 private boolean connectToSupplicant() { 267 int connectTries = 0; 268 269 while (true) { 270 if (WifiNative.connectToSupplicant()) { 271 return true; 272 } 273 if (connectTries++ < 3) { 274 nap(5); 275 } else { 276 break; 277 } 278 } 279 return false; 280 } 281 282 private void handlePasswordKeyMayBeIncorrect() { 283 mWifiStateMachine.notifyPasswordKeyMayBeIncorrect(); 284 } 285 286 private void handleDriverEvent(String state) { 287 if (state == null) { 288 return; 289 } 290 if (state.equals("STOPPED")) { 291 mWifiStateMachine.notifyDriverStopped(); 292 } else if (state.equals("STARTED")) { 293 mWifiStateMachine.notifyDriverStarted(); 294 } else if (state.equals("HANGED")) { 295 mWifiStateMachine.notifyDriverHung(); 296 } 297 } 298 299 /** 300 * Handle all supplicant events except STATE-CHANGE 301 * @param event the event type 302 * @param remainder the rest of the string following the 303 * event name and " — " 304 */ 305 void handleEvent(int event, String remainder) { 306 switch (event) { 307 case DISCONNECTED: 308 handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder); 309 break; 310 311 case CONNECTED: 312 handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder); 313 break; 314 315 case SCAN_RESULTS: 316 mWifiStateMachine.notifyScanResultsAvailable(); 317 break; 318 319 case UNKNOWN: 320 break; 321 } 322 } 323 324 /** 325 * Handle the supplicant STATE-CHANGE event 326 * @param dataString New supplicant state string in the format: 327 * id=network-id state=new-state 328 */ 329 private void handleSupplicantStateChange(String dataString) { 330 String[] dataTokens = dataString.split(" "); 331 332 String BSSID = null; 333 int networkId = -1; 334 int newState = -1; 335 for (String token : dataTokens) { 336 String[] nameValue = token.split("="); 337 if (nameValue.length != 2) { 338 continue; 339 } 340 341 if (nameValue[0].equals("BSSID")) { 342 BSSID = nameValue[1]; 343 continue; 344 } 345 346 int value; 347 try { 348 value = Integer.parseInt(nameValue[1]); 349 } catch (NumberFormatException e) { 350 Log.w(TAG, "STATE-CHANGE non-integer parameter: " + token); 351 continue; 352 } 353 354 if (nameValue[0].equals("id")) { 355 networkId = value; 356 } else if (nameValue[0].equals("state")) { 357 newState = value; 358 } 359 } 360 361 if (newState == -1) return; 362 363 SupplicantState newSupplicantState = SupplicantState.INVALID; 364 for (SupplicantState state : SupplicantState.values()) { 365 if (state.ordinal() == newState) { 366 newSupplicantState = state; 367 break; 368 } 369 } 370 if (newSupplicantState == SupplicantState.INVALID) { 371 Log.w(TAG, "Invalid supplicant state: " + newState); 372 } 373 mWifiStateMachine.notifySupplicantStateChange(networkId, BSSID, newSupplicantState); 374 } 375 } 376 377 private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) { 378 String BSSID = null; 379 int networkId = -1; 380 if (newState == NetworkInfo.DetailedState.CONNECTED) { 381 Matcher match = mConnectedEventPattern.matcher(data); 382 if (!match.find()) { 383 if (Config.LOGD) Log.d(TAG, "Could not find BSSID in CONNECTED event string"); 384 } else { 385 BSSID = match.group(1); 386 try { 387 networkId = Integer.parseInt(match.group(2)); 388 } catch (NumberFormatException e) { 389 networkId = -1; 390 } 391 } 392 } 393 mWifiStateMachine.notifyNetworkStateChange(newState, BSSID, networkId); 394 } 395 396 /** 397 * Sleep for a period of time. 398 * @param secs the number of seconds to sleep 399 */ 400 private static void nap(int secs) { 401 try { 402 Thread.sleep(secs * 1000); 403 } catch (InterruptedException ignore) { 404 } 405 } 406} 407