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