/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net.wifi; import android.util.Log; import android.util.Config; import android.net.NetworkInfo; import android.net.NetworkStateTracker; import java.util.regex.Pattern; import java.util.regex.Matcher; /** * Listens for events from the wpa_supplicant server, and passes them on * to the {@link WifiStateTracker} for handling. Runs in its own thread. * * @hide */ public class WifiMonitor { private static final String TAG = "WifiMonitor"; /** Events we receive from the supplicant daemon */ private static final int CONNECTED = 1; private static final int DISCONNECTED = 2; private static final int STATE_CHANGE = 3; private static final int SCAN_RESULTS = 4; private static final int LINK_SPEED = 5; private static final int TERMINATING = 6; private static final int DRIVER_STATE = 7; private static final int UNKNOWN = 8; /** All events coming from the supplicant start with this prefix */ private static final String eventPrefix = "CTRL-EVENT-"; private static final int eventPrefixLen = eventPrefix.length(); /** All WPA events coming from the supplicant start with this prefix */ private static final String wpaEventPrefix = "WPA:"; private static final String passwordKeyMayBeIncorrectEvent = "pre-shared key may be incorrect"; /** * Names of events from wpa_supplicant (minus the prefix). In the * format descriptions, * "x" * designates a dynamic value that needs to be parsed out from the event * string */ /** *
     * CTRL-EVENT-CONNECTED - Connection to xx:xx:xx:xx:xx:xx completed
     * 
* xx:xx:xx:xx:xx:xx is the BSSID of the associated access point */ private static final String connectedEvent = "CONNECTED"; /** *
     * CTRL-EVENT-DISCONNECTED - Disconnect event - remove keys
     * 
*/ private static final String disconnectedEvent = "DISCONNECTED"; /** *
     * CTRL-EVENT-STATE-CHANGE x
     * 
* x is the numerical value of the new state. */ private static final String stateChangeEvent = "STATE-CHANGE"; /** *
     * CTRL-EVENT-SCAN-RESULTS ready
     * 
*/ private static final String scanResultsEvent = "SCAN-RESULTS"; /** *
     * CTRL-EVENT-LINK-SPEED x Mb/s
     * 
* {@code x} is the link speed in Mb/sec. */ private static final String linkSpeedEvent = "LINK-SPEED"; /** *
     * CTRL-EVENT-TERMINATING - signal x
     * 
* x is the signal that caused termination. */ private static final String terminatingEvent = "TERMINATING"; /** *
     * CTRL-EVENT-DRIVER-STATE state
     * 
* state is either STARTED or STOPPED */ private static final String driverStateEvent = "DRIVER-STATE"; /** * Regex pattern for extracting an Ethernet-style MAC address from a string. * Matches a strings like the following:
     * CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]
*/ private static Pattern mConnectedEventPattern = Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) "); private final WifiStateTracker mWifiStateTracker; /** * This indicates the supplicant connection for the monitor is closed */ private static final String monitorSocketClosed = "connection closed"; /** * This indicates a read error on the monitor socket conenction */ private static final String wpaRecvError = "recv error"; /** * Tracks consecutive receive errors */ private int mRecvErrors = 0; /** * Max errors before we close supplicant connection */ private static final int MAX_RECV_ERRORS = 10; public WifiMonitor(WifiStateTracker tracker) { mWifiStateTracker = tracker; } public void startMonitoring() { new MonitorThread().start(); } public NetworkStateTracker getNetworkStateTracker() { return mWifiStateTracker; } class MonitorThread extends Thread { public MonitorThread() { super("WifiMonitor"); } public void run() { if (connectToSupplicant()) { // Send a message indicating that it is now possible to send commands // to the supplicant mWifiStateTracker.notifySupplicantConnection(); } else { mWifiStateTracker.notifySupplicantLost(); return; } //noinspection InfiniteLoopStatement for (;;) { String eventStr = WifiNative.waitForEvent(); // Skip logging the common but mostly uninteresting scan-results event if (Config.LOGD && eventStr.indexOf(scanResultsEvent) == -1) { Log.v(TAG, "Event [" + eventStr + "]"); } if (!eventStr.startsWith(eventPrefix)) { if (eventStr.startsWith(wpaEventPrefix) && 0 < eventStr.indexOf(passwordKeyMayBeIncorrectEvent)) { handlePasswordKeyMayBeIncorrect(); } continue; } String eventName = eventStr.substring(eventPrefixLen); int nameEnd = eventName.indexOf(' '); if (nameEnd != -1) eventName = eventName.substring(0, nameEnd); if (eventName.length() == 0) { if (Config.LOGD) Log.i(TAG, "Received wpa_supplicant event with empty event name"); continue; } /* * Map event name into event enum */ int event; if (eventName.equals(connectedEvent)) event = CONNECTED; else if (eventName.equals(disconnectedEvent)) event = DISCONNECTED; else if (eventName.equals(stateChangeEvent)) event = STATE_CHANGE; else if (eventName.equals(scanResultsEvent)) event = SCAN_RESULTS; else if (eventName.equals(linkSpeedEvent)) event = LINK_SPEED; else if (eventName.equals(terminatingEvent)) event = TERMINATING; else if (eventName.equals(driverStateEvent)) { event = DRIVER_STATE; } else event = UNKNOWN; String eventData = eventStr; if (event == DRIVER_STATE || event == LINK_SPEED) eventData = eventData.split(" ")[1]; else if (event == STATE_CHANGE) { int ind = eventStr.indexOf(" "); if (ind != -1) { eventData = eventStr.substring(ind + 1); } } else { int ind = eventStr.indexOf(" - "); if (ind != -1) { eventData = eventStr.substring(ind + 3); } } if (event == STATE_CHANGE) { handleSupplicantStateChange(eventData); } else if (event == DRIVER_STATE) { handleDriverEvent(eventData); } else if (event == TERMINATING) { /** * If monitor socket is closed, we have already * stopped the supplicant, simply exit the monitor thread */ if (eventData.startsWith(monitorSocketClosed)) { if (Config.LOGD) { Log.d(TAG, "Monitor socket is closed, exiting thread"); } break; } /** * Close the supplicant connection if we see * too many recv errors */ if (eventData.startsWith(wpaRecvError)) { if (++mRecvErrors > MAX_RECV_ERRORS) { if (Config.LOGD) { Log.d(TAG, "too many recv errors, closing connection"); } } else { continue; } } // notify and exit mWifiStateTracker.notifySupplicantLost(); break; } else { handleEvent(event, eventData); } mRecvErrors = 0; } } private boolean connectToSupplicant() { int connectTries = 0; while (true) { if (mWifiStateTracker.connectToSupplicant()) { return true; } if (connectTries++ < 3) { nap(5); } else { break; } } return false; } private void handlePasswordKeyMayBeIncorrect() { mWifiStateTracker.notifyPasswordKeyMayBeIncorrect(); } private void handleDriverEvent(String state) { if (state == null) { return; } if (state.equals("STOPPED")) { mWifiStateTracker.notifyDriverStopped(); } else if (state.equals("STARTED")) { mWifiStateTracker.notifyDriverStarted(); } else if (state.equals("HANGED")) { mWifiStateTracker.notifyDriverHung(); } } /** * Handle all supplicant events except STATE-CHANGE * @param event the event type * @param remainder the rest of the string following the * event name and " — " */ void handleEvent(int event, String remainder) { switch (event) { case DISCONNECTED: handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder); break; case CONNECTED: handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder); break; case SCAN_RESULTS: mWifiStateTracker.notifyScanResultsAvailable(); break; case UNKNOWN: break; } } /** * Handle the supplicant STATE-CHANGE event * @param dataString New supplicant state string in the format: * id=network-id state=new-state */ private void handleSupplicantStateChange(String dataString) { String[] dataTokens = dataString.split(" "); String BSSID = null; int networkId = -1; int newState = -1; for (String token : dataTokens) { String[] nameValue = token.split("="); if (nameValue.length != 2) { continue; } if (nameValue[0].equals("BSSID")) { BSSID = nameValue[1]; continue; } int value; try { value = Integer.parseInt(nameValue[1]); } catch (NumberFormatException e) { Log.w(TAG, "STATE-CHANGE non-integer parameter: " + token); continue; } if (nameValue[0].equals("id")) { networkId = value; } else if (nameValue[0].equals("state")) { newState = value; } } if (newState == -1) return; SupplicantState newSupplicantState = SupplicantState.INVALID; for (SupplicantState state : SupplicantState.values()) { if (state.ordinal() == newState) { newSupplicantState = state; break; } } if (newSupplicantState == SupplicantState.INVALID) { Log.w(TAG, "Invalid supplicant state: " + newState); } mWifiStateTracker.notifyStateChange(networkId, BSSID, newSupplicantState); } } private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) { String BSSID = null; int networkId = -1; if (newState == NetworkInfo.DetailedState.CONNECTED) { Matcher match = mConnectedEventPattern.matcher(data); if (!match.find()) { if (Config.LOGD) Log.d(TAG, "Could not find BSSID in CONNECTED event string"); } else { BSSID = match.group(1); try { networkId = Integer.parseInt(match.group(2)); } catch (NumberFormatException e) { networkId = -1; } } } mWifiStateTracker.notifyStateChange(newState, BSSID, networkId); } /** * Sleep for a period of time. * @param secs the number of seconds to sleep */ private static void nap(int secs) { try { Thread.sleep(secs * 1000); } catch (InterruptedException ignore) { } } }