363 lines
12 KiB
Java
363 lines
12 KiB
Java
/*
|
|
* 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) {
|
|
}
|
|
}
|
|
}
|