WifiMonitor.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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, * &quot;<code>x</code>&quot;
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 &quot;&#8195;&#8212;&#8195;&quot;
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