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 public WifiMonitor(WifiStateTracker tracker) { 123 mWifiStateTracker = tracker; 124 } 125 126 public void startMonitoring() { 127 new MonitorThread().start(); 128 } 129 130 public NetworkStateTracker getNetworkStateTracker() { 131 return mWifiStateTracker; 132 } 133 134 class MonitorThread extends Thread { 135 public MonitorThread() { 136 super("WifiMonitor"); 137 } 138 139 public void run() { 140 141 if (connectToSupplicant()) { 142 // Send a message indicating that it is now possible to send commands 143 // to the supplicant 144 mWifiStateTracker.notifySupplicantConnection(); 145 } else { 146 mWifiStateTracker.notifySupplicantLost(); 147 return; 148 } 149 150 //noinspection InfiniteLoopStatement 151 for (;;) { 152 String eventStr = WifiNative.waitForEvent(); 153 154 if (eventStr == null) { 155 continue; 156 } 157 158 // Skip logging the common but mostly uninteresting scan-results event 159 if (Config.LOGD && eventStr.indexOf(scanResultsEvent) == -1) { 160 Log.v(TAG, "Event [" + eventStr + "]"); 161 } 162 if (!eventStr.startsWith(eventPrefix)) { 163 if (eventStr.startsWith(wpaEventPrefix) && 0 < eventStr.indexOf(passwordKeyMayBeIncorrectEvent)) { 164 handlePasswordKeyMayBeIncorrect(); 165 } 166 continue; 167 } 168 169 String eventName = eventStr.substring(eventPrefixLen); 170 int nameEnd = eventName.indexOf(' '); 171 if (nameEnd != -1) 172 eventName = eventName.substring(0, nameEnd); 173 if (eventName.length() == 0) { 174 if (Config.LOGD) Log.i(TAG, "Received wpa_supplicant event with empty event name"); 175 continue; 176 } 177 /* 178 * Map event name into event enum 179 */ 180 int event; 181 if (eventName.equals(connectedEvent)) 182 event = CONNECTED; 183 else if (eventName.equals(disconnectedEvent)) 184 event = DISCONNECTED; 185 else if (eventName.equals(stateChangeEvent)) 186 event = STATE_CHANGE; 187 else if (eventName.equals(scanResultsEvent)) 188 event = SCAN_RESULTS; 189 else if (eventName.equals(linkSpeedEvent)) 190 event = LINK_SPEED; 191 else if (eventName.equals(terminatingEvent)) 192 event = TERMINATING; 193 else if (eventName.equals(driverStateEvent)) { 194 event = DRIVER_STATE; 195 } 196 else 197 event = UNKNOWN; 198 199 String eventData = eventStr; 200 if (event == DRIVER_STATE || event == LINK_SPEED) 201 eventData = eventData.split(" ")[1]; 202 else if (event == STATE_CHANGE) { 203 int ind = eventStr.indexOf(" "); 204 if (ind != -1) { 205 eventData = eventStr.substring(ind + 1); 206 } 207 } else { 208 int ind = eventStr.indexOf(" - "); 209 if (ind != -1) { 210 eventData = eventStr.substring(ind + 3); 211 } 212 } 213 214 if (event == STATE_CHANGE) { 215 handleSupplicantStateChange(eventData); 216 } else if (event == DRIVER_STATE) { 217 handleDriverEvent(eventData); 218 } else if (event == TERMINATING) { 219 mWifiStateTracker.notifySupplicantLost(); 220 // If supplicant is gone, exit the thread 221 break; 222 } else { 223 handleEvent(event, eventData); 224 } 225 } 226 } 227 228 private boolean connectToSupplicant() { 229 int connectTries = 0; 230 231 while (true) { 232 synchronized (mWifiStateTracker) { 233 if (WifiNative.connectToSupplicant()) { 234 return true; 235 } 236 } 237 if (connectTries++ < 3) { 238 nap(5); 239 } else { 240 break; 241 } 242 } 243 return false; 244 } 245 246 private void handlePasswordKeyMayBeIncorrect() { 247 mWifiStateTracker.notifyPasswordKeyMayBeIncorrect(); 248 } 249 250 private void handleDriverEvent(String state) { 251 if (state == null) { 252 return; 253 } 254 if (state.equals("STOPPED")) { 255 mWifiStateTracker.notifyDriverStopped(); 256 } else if (state.equals("STARTED")) { 257 mWifiStateTracker.notifyDriverStarted(); 258 } 259 } 260 261 /** 262 * Handle all supplicant events except STATE-CHANGE 263 * @param event the event type 264 * @param remainder the rest of the string following the 265 * event name and " — " 266 */ 267 void handleEvent(int event, String remainder) { 268 switch (event) { 269 case DISCONNECTED: 270 handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder); 271 break; 272 273 case CONNECTED: 274 handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder); 275 break; 276 277 case SCAN_RESULTS: 278 mWifiStateTracker.notifyScanResultsAvailable(); 279 break; 280 281 case UNKNOWN: 282 break; 283 } 284 } 285 286 /** 287 * Handle the supplicant STATE-CHANGE event 288 * @param dataString New supplicant state string in the format: 289 * id=network-id state=new-state 290 */ 291 private void handleSupplicantStateChange(String dataString) { 292 String[] dataTokens = dataString.split(" "); 293 294 int networkId = -1; 295 int newState = -1; 296 for (String token : dataTokens) { 297 String[] nameValue = token.split("="); 298 if (nameValue.length != 2) { 299 continue; 300 } 301 302 int value; 303 try { 304 value = Integer.parseInt(nameValue[1]); 305 } catch (NumberFormatException e) { 306 Log.w(TAG, "STATE-CHANGE non-integer parameter: " + token); 307 continue; 308 } 309 310 if (nameValue[0].equals("id")) { 311 networkId = value; 312 } else if (nameValue[0].equals("state")) { 313 newState = value; 314 } 315 } 316 317 if (newState == -1) return; 318 319 SupplicantState newSupplicantState = SupplicantState.INVALID; 320 for (SupplicantState state : SupplicantState.values()) { 321 if (state.ordinal() == newState) { 322 newSupplicantState = state; 323 break; 324 } 325 } 326 if (newSupplicantState == SupplicantState.INVALID) { 327 Log.w(TAG, "Invalid supplicant state: " + newState); 328 } 329 mWifiStateTracker.notifyStateChange(networkId, newSupplicantState); 330 } 331 } 332 333 private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) { 334 String BSSID = null; 335 int networkId = -1; 336 if (newState == NetworkInfo.DetailedState.CONNECTED) { 337 Matcher match = mConnectedEventPattern.matcher(data); 338 if (!match.find()) { 339 if (Config.LOGD) Log.d(TAG, "Could not find BSSID in CONNECTED event string"); 340 } else { 341 BSSID = match.group(1); 342 try { 343 networkId = Integer.parseInt(match.group(2)); 344 } catch (NumberFormatException e) { 345 networkId = -1; 346 } 347 } 348 } 349 mWifiStateTracker.notifyStateChange(newState, BSSID, networkId); 350 } 351 352 /** 353 * Sleep for a period of time. 354 * @param secs the number of seconds to sleep 355 */ 356 private static void nap(int secs) { 357 try { 358 Thread.sleep(secs * 1000); 359 } catch (InterruptedException ignore) { 360 } 361 } 362} 363