| /* |
| * 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, * "<code>x</code>" |
| * designates a dynamic value that needs to be parsed out from the event |
| * string |
| */ |
| /** |
| * <pre> |
| * CTRL-EVENT-CONNECTED - Connection to xx:xx:xx:xx:xx:xx completed |
| * </pre> |
| * <code>xx:xx:xx:xx:xx:xx</code> is the BSSID of the associated access point |
| */ |
| private static final String connectedEvent = "CONNECTED"; |
| /** |
| * <pre> |
| * CTRL-EVENT-DISCONNECTED - Disconnect event - remove keys |
| * </pre> |
| */ |
| private static final String disconnectedEvent = "DISCONNECTED"; |
| /** |
| * <pre> |
| * CTRL-EVENT-STATE-CHANGE x |
| * </pre> |
| * <code>x</code> is the numerical value of the new state. |
| */ |
| private static final String stateChangeEvent = "STATE-CHANGE"; |
| /** |
| * <pre> |
| * CTRL-EVENT-SCAN-RESULTS ready |
| * </pre> |
| */ |
| private static final String scanResultsEvent = "SCAN-RESULTS"; |
| |
| /** |
| * <pre> |
| * CTRL-EVENT-LINK-SPEED x Mb/s |
| * </pre> |
| * {@code x} is the link speed in Mb/sec. |
| */ |
| private static final String linkSpeedEvent = "LINK-SPEED"; |
| /** |
| * <pre> |
| * CTRL-EVENT-TERMINATING - signal x |
| * </pre> |
| * <code>x</code> is the signal that caused termination. |
| */ |
| private static final String terminatingEvent = "TERMINATING"; |
| /** |
| * <pre> |
| * CTRL-EVENT-DRIVER-STATE state |
| * </pre> |
| * <code>state</code> 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:<pre> |
| * CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]</pre> |
| */ |
| private static Pattern mConnectedEventPattern = |
| Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) "); |
| |
| private final WifiStateTracker mWifiStateTracker; |
| |
| 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(); |
| |
| if (eventStr == null) { |
| continue; |
| } |
| |
| // 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) { |
| mWifiStateTracker.notifySupplicantLost(); |
| // If supplicant is gone, exit the thread |
| break; |
| } else { |
| handleEvent(event, eventData); |
| } |
| } |
| } |
| |
| private boolean connectToSupplicant() { |
| int connectTries = 0; |
| |
| while (true) { |
| synchronized (mWifiStateTracker) { |
| if (WifiNative.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(); |
| } |
| } |
| |
| /** |
| * 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(" "); |
| |
| int networkId = -1; |
| int newState = -1; |
| for (String token : dataTokens) { |
| String[] nameValue = token.split("="); |
| if (nameValue.length != 2) { |
| 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, 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) { |
| } |
| } |
| } |