WifiStateTracker.java revision ea8bd1d1676dd96f8f9726bb8f67fa18dec7b9c1
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.app.ActivityManagerNative;
20import android.net.NetworkInfo;
21import android.net.NetworkStateTracker;
22import android.net.DhcpInfo;
23import android.net.NetworkUtils;
24import android.net.ConnectivityManager;
25import android.net.NetworkInfo.DetailedState;
26import android.net.NetworkInfo.State;
27import android.os.Message;
28import android.os.Parcelable;
29import android.os.Handler;
30import android.os.HandlerThread;
31import android.os.SystemProperties;
32import android.os.Looper;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.provider.Settings;
36import android.text.TextUtils;
37import android.util.EventLog;
38import android.util.Log;
39import android.util.Config;
40import android.app.Notification;
41import android.app.PendingIntent;
42import android.bluetooth.BluetoothDevice;
43import android.bluetooth.BluetoothHeadset;
44import android.bluetooth.BluetoothA2dp;
45import android.content.ContentResolver;
46import android.content.Intent;
47import android.content.Context;
48import android.database.ContentObserver;
49import com.android.internal.app.IBatteryStats;
50
51import java.util.List;
52import java.util.ArrayList;
53import java.util.Set;
54import java.net.UnknownHostException;
55
56/**
57 * Track the state of Wifi connectivity. All event handling is done here,
58 * and all changes in connectivity state are initiated here.
59 *
60 * @hide
61 */
62public class WifiStateTracker extends NetworkStateTracker {
63
64    private static final boolean LOCAL_LOGD = Config.LOGD || false;
65
66    private static final String TAG = "WifiStateTracker";
67
68    // Event log tags (must be in sync with event-log-tags)
69    private static final int EVENTLOG_NETWORK_STATE_CHANGED = 50021;
70    private static final int EVENTLOG_SUPPLICANT_STATE_CHANGED = 50022;
71    private static final int EVENTLOG_DRIVER_STATE_CHANGED = 50023;
72    private static final int EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED = 50024;
73    private static final int EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED = 50025;
74
75    // Event codes
76    private static final int EVENT_SUPPLICANT_CONNECTION             = 1;
77    private static final int EVENT_SUPPLICANT_DISCONNECT             = 2;
78    private static final int EVENT_SUPPLICANT_STATE_CHANGED          = 3;
79    private static final int EVENT_NETWORK_STATE_CHANGED             = 4;
80    private static final int EVENT_SCAN_RESULTS_AVAILABLE            = 5;
81    private static final int EVENT_INTERFACE_CONFIGURATION_SUCCEEDED = 6;
82    private static final int EVENT_INTERFACE_CONFIGURATION_FAILED    = 7;
83    private static final int EVENT_POLL_INTERVAL                     = 8;
84    private static final int EVENT_DHCP_START                        = 9;
85    private static final int EVENT_DEFERRED_DISCONNECT               = 10;
86    private static final int EVENT_DEFERRED_RECONNECT                = 11;
87    /**
88     * The driver is started or stopped. The object will be the state: true for
89     * started, false for stopped.
90     */
91    private static final int EVENT_DRIVER_STATE_CHANGED              = 12;
92    private static final int EVENT_PASSWORD_KEY_MAY_BE_INCORRECT     = 13;
93    private static final int EVENT_MAYBE_START_SCAN_POST_DISCONNECT  = 14;
94
95    /**
96     * The driver state indication.
97     */
98    private static final int DRIVER_STARTED                          = 0;
99    private static final int DRIVER_STOPPED                          = 1;
100    private static final int DRIVER_HUNG                             = 2;
101
102    /**
103     * Interval in milliseconds between polling for connection
104     * status items that are not sent via asynchronous events.
105     * An example is RSSI (signal strength).
106     */
107    private static final int POLL_STATUS_INTERVAL_MSECS = 3000;
108
109    /**
110     * The max number of the WPA supplicant loop iterations before we
111     * decide that the loop should be terminated:
112     */
113    private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
114
115    /**
116     * When a DISCONNECT event is received, we defer handling it to
117     * allow for the possibility that the DISCONNECT is about to
118     * be followed shortly by a CONNECT to the same network we were
119     * just connected to. In such a case, we don't want to report
120     * the network as down, nor do we want to reconfigure the network
121     * interface, etc. If we get a CONNECT event for another network
122     * within the delay window, we immediately handle the pending
123     * disconnect before processing the CONNECT.<p/>
124     * The five second delay is chosen somewhat arbitrarily, but is
125     * meant to cover most of the cases where a DISCONNECT/CONNECT
126     * happens to a network.
127     */
128    private static final int DISCONNECT_DELAY_MSECS = 5000;
129    /**
130     * When the supplicant goes idle after we do an explicit disconnect
131     * following a DHCP failure, we need to kick the supplicant into
132     * trying to associate with access points.
133     */
134    private static final int RECONNECT_DELAY_MSECS = 2000;
135
136    /**
137     * When the supplicant disconnects from an AP it sometimes forgets
138     * to restart scanning.  Wait this delay before asking it to start
139     * scanning (in case it forgot).  15 sec is the standard delay between
140     * scans.
141     */
142    private static final int KICKSTART_SCANNING_DELAY_MSECS = 15000;
143
144    /**
145     * The maximum number of times we will retry a connection to an access point
146     * for which we have failed in acquiring an IP address from DHCP. A value of
147     * N means that we will make N+1 connection attempts in all.
148     * <p>
149     * See {@link Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default
150     * value if a Settings value is not present.
151     */
152    private static final int DEFAULT_MAX_DHCP_RETRIES = 2;
153
154    private static final int DRIVER_POWER_MODE_AUTO = 0;
155    private static final int DRIVER_POWER_MODE_ACTIVE = 1;
156
157    /**
158     * The current WPA supplicant loop state (used to detect looping behavior):
159     */
160    private SupplicantState mSupplicantLoopState = SupplicantState.DISCONNECTED;
161
162    /**
163     * The current number of WPA supplicant loop iterations:
164     */
165    private int mNumSupplicantLoopIterations = 0;
166
167    /**
168     * The current number of supplicant state changes.  This is used to determine
169     * if we've received any new info since we found out it was DISCONNECTED or
170     * INACTIVE.  If we haven't for X ms, we then request a scan - it should have
171     * done that automatically, but sometimes some firmware does not.
172     */
173    private int mNumSupplicantStateChanges = 0;
174
175    /**
176     * True if we received an event that that a password-key may be incorrect.
177     * If the next incoming supplicant state change event is DISCONNECT,
178     * broadcast a message that we have a possible password error and disable
179     * the network.
180     */
181    private boolean mPasswordKeyMayBeIncorrect = false;
182
183    public static final int SUPPL_SCAN_HANDLING_NORMAL = 1;
184    public static final int SUPPL_SCAN_HANDLING_LIST_ONLY = 2;
185
186    private WifiMonitor mWifiMonitor;
187    private WifiInfo mWifiInfo;
188    private List<ScanResult> mScanResults;
189    private WifiManager mWM;
190    private boolean mHaveIpAddress;
191    private boolean mObtainingIpAddress;
192    private boolean mTornDownByConnMgr;
193    /**
194     * A DISCONNECT event has been received, but processing it
195     * is being deferred.
196     */
197    private boolean mDisconnectPending;
198    /**
199     * An operation has been performed as a result of which we expect the next event
200     * will be a DISCONNECT.
201     */
202    private boolean mDisconnectExpected;
203    private DhcpHandler mDhcpTarget;
204    private DhcpInfo mDhcpInfo;
205    private int mLastSignalLevel = -1;
206    private String mLastBssid;
207    private String mLastSsid;
208    private int mLastNetworkId = -1;
209    private boolean mUseStaticIp = false;
210    private int mReconnectCount;
211
212    // used to store the (non-persisted) num determined during device boot
213    // (from mcc or other phone info) before the driver is started.
214    private int mNumAllowedChannels = 0;
215
216    // Variables relating to the 'available networks' notification
217
218    /**
219     * The icon to show in the 'available networks' notification. This will also
220     * be the ID of the Notification given to the NotificationManager.
221     */
222    private static final int ICON_NETWORKS_AVAILABLE =
223            com.android.internal.R.drawable.stat_notify_wifi_in_range;
224    /**
225     * When a notification is shown, we wait this amount before possibly showing it again.
226     */
227    private final long NOTIFICATION_REPEAT_DELAY_MS;
228    /**
229     * Whether the user has set the setting to show the 'available networks' notification.
230     */
231    private boolean mNotificationEnabled;
232    /**
233     * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
234     */
235    private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
236    /**
237     * The {@link System#currentTimeMillis()} must be at least this value for us
238     * to show the notification again.
239     */
240    private long mNotificationRepeatTime;
241    /**
242     * The Notification object given to the NotificationManager.
243     */
244    private Notification mNotification;
245    /**
246     * Whether the notification is being shown, as set by us. That is, if the
247     * user cancels the notification, we will not receive the callback so this
248     * will still be true. We only guarantee if this is false, then the
249     * notification is not showing.
250     */
251    private boolean mNotificationShown;
252    /**
253     * The number of continuous scans that must occur before consider the
254     * supplicant in a scanning state. This allows supplicant to associate with
255     * remembered networks that are in the scan results.
256     */
257    private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
258    /**
259     * The number of scans since the last network state change. When this
260     * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
261     * supplicant to actually be scanning. When the network state changes to
262     * something other than scanning, we reset this to 0.
263     */
264    private int mNumScansSinceNetworkStateChange;
265    /**
266     * Observes the static IP address settings.
267     */
268    private SettingsObserver mSettingsObserver;
269
270    private boolean mIsScanModeActive;
271    private boolean mEnableRssiPolling;
272
273    // Wi-Fi run states:
274    private static final int RUN_STATE_STARTING = 1;
275    private static final int RUN_STATE_RUNNING  = 2;
276    private static final int RUN_STATE_STOPPING = 3;
277    private static final int RUN_STATE_STOPPED  = 4;
278
279    private static final String mRunStateNames[] = {
280            "Starting",
281            "Running",
282            "Stopping",
283            "Stopped"
284    };
285    private int mRunState;
286
287    private final IBatteryStats mBatteryStats;
288
289    private boolean mIsScanOnly;
290
291    private BluetoothA2dp mBluetoothA2dp;
292
293    private String mInterfaceName;
294    private static String LS = System.getProperty("line.separator");
295
296    private Runnable mReleaseWakeLockCallback;
297
298    private static String[] sDnsPropNames;
299
300    /**
301     * A structure for supplying information about a supplicant state
302     * change in the STATE_CHANGE event message that comes from the
303     * WifiMonitor
304     * thread.
305     */
306    private static class SupplicantStateChangeResult {
307        SupplicantStateChangeResult(int networkId, SupplicantState state) {
308            this.state = state;
309            this.networkId = networkId;
310        }
311        int networkId;
312        SupplicantState state;
313    }
314
315    /**
316     * A structure for supplying information about a connection in
317     * the CONNECTED event message that comes from the WifiMonitor
318     * thread.
319     */
320    private static class NetworkStateChangeResult {
321        NetworkStateChangeResult(DetailedState state, String BSSID, int networkId) {
322            this.state = state;
323            this.BSSID = BSSID;
324            this.networkId = networkId;
325        }
326        DetailedState state;
327        String BSSID;
328        int networkId;
329    }
330
331    public WifiStateTracker(Context context, Handler target) {
332        super(context, target, ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
333
334        mWifiInfo = new WifiInfo();
335        mWifiMonitor = new WifiMonitor(this);
336        mHaveIpAddress = false;
337        mObtainingIpAddress = false;
338        setTornDownByConnMgr(false);
339        mDisconnectPending = false;
340        mScanResults = new ArrayList<ScanResult>();
341        // Allocate DHCP info object once, and fill it in on each request
342        mDhcpInfo = new DhcpInfo();
343        mRunState = RUN_STATE_STARTING;
344
345        // Setting is in seconds
346        NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(),
347                Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
348        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
349        mNotificationEnabledSettingObserver.register();
350
351        mSettingsObserver = new SettingsObserver(new Handler());
352
353        mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0");
354        sDnsPropNames = new String[] {
355            "dhcp." + mInterfaceName + ".dns1",
356            "dhcp." + mInterfaceName + ".dns2"
357        };
358        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
359
360    }
361
362    /**
363     * Helper method: sets the supplicant state and keeps the network
364     * info updated.
365     * @param state the new state
366     */
367    private void setSupplicantState(SupplicantState state) {
368        mWifiInfo.setSupplicantState(state);
369        updateNetworkInfo();
370        checkPollTimer();
371    }
372
373    public SupplicantState getSupplicantState() {
374        return mWifiInfo.getSupplicantState();
375    }
376
377    /**
378     * Helper method: sets the supplicant state and keeps the network
379     * info updated (string version).
380     * @param stateName the string name of the new state
381     */
382    private void setSupplicantState(String stateName) {
383        mWifiInfo.setSupplicantState(stateName);
384        updateNetworkInfo();
385        checkPollTimer();
386    }
387
388    /**
389     * Helper method: sets the boolean indicating that the connection
390     * manager asked the network to be torn down (and so only the connection
391     * manager can set it up again).
392     * network info updated.
393     * @param flag {@code true} if explicitly disabled.
394     */
395    private void setTornDownByConnMgr(boolean flag) {
396        mTornDownByConnMgr = flag;
397        updateNetworkInfo();
398    }
399
400    /**
401     * Return the IP addresses of the DNS servers available for the WLAN
402     * network interface.
403     * @return a list of DNS addresses, with no holes.
404     */
405    public String[] getNameServers() {
406        return getNameServerList(sDnsPropNames);
407    }
408
409    /**
410     * Return the name of our WLAN network interface.
411     * @return the name of our interface.
412     */
413    public String getInterfaceName() {
414        return mInterfaceName;
415    }
416
417    /**
418     * Return the system properties name associated with the tcp buffer sizes
419     * for this network.
420     */
421    public String getTcpBufferSizesPropName() {
422        return "net.tcp.buffersize.wifi";
423    }
424
425    public void startMonitoring() {
426        /*
427         * Get a handle on the WifiManager. This cannot be done in our
428         * constructor, because the Wifi service is not yet registered.
429         */
430        mWM = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
431    }
432
433    public void startEventLoop() {
434        mWifiMonitor.startMonitoring();
435    }
436
437    /**
438     * Wi-Fi is considered available as long as we have a connection to the
439     * supplicant daemon and there is at least one enabled network. If a teardown
440     * was explicitly requested, then Wi-Fi can be restarted with a reconnect
441     * request, so it is considered available. If the driver has been stopped
442     * for any reason other than a teardown request, Wi-Fi is considered
443     * unavailable.
444     * @return {@code true} if Wi-Fi connections are possible
445     */
446    public synchronized boolean isAvailable() {
447        /*
448         * TODO: Need to also look at scan results to see whether we're
449         * in range of any access points. If we have scan results that
450         * are no more than N seconds old, use those, otherwise, initiate
451         * a scan and wait for the results. This only matters if we
452         * allow mobile to be the preferred network.
453         */
454        SupplicantState suppState = mWifiInfo.getSupplicantState();
455        return suppState != SupplicantState.UNINITIALIZED &&
456                suppState != SupplicantState.INACTIVE &&
457                (mTornDownByConnMgr || !isDriverStopped());
458    }
459
460    /**
461     * {@inheritDoc}
462     * There are currently no defined Wi-Fi subtypes.
463     */
464    public int getNetworkSubtype() {
465        return 0;
466    }
467
468    /**
469     * Helper method: updates the network info object to keep it in sync with
470     * the Wi-Fi state tracker.
471     */
472    private void updateNetworkInfo() {
473        mNetworkInfo.setIsAvailable(isAvailable());
474    }
475
476    /**
477     * Report whether the Wi-Fi connection is fully configured for data.
478     * @return {@code true} if the {@link SupplicantState} is
479     * {@link android.net.wifi.SupplicantState#COMPLETED COMPLETED}.
480     */
481    public boolean isConnectionCompleted() {
482        return mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED;
483    }
484
485    /**
486     * Report whether the Wi-Fi connection has successfully acquired an IP address.
487     * @return {@code true} if the Wi-Fi connection has been assigned an IP address.
488     */
489    public boolean hasIpAddress() {
490        return mHaveIpAddress;
491    }
492
493    /**
494     * Send the tracker a notification that a user-entered password key
495     * may be incorrect (i.e., caused authentication to fail).
496     */
497    void notifyPasswordKeyMayBeIncorrect() {
498        sendEmptyMessage(EVENT_PASSWORD_KEY_MAY_BE_INCORRECT);
499    }
500
501    /**
502     * Send the tracker a notification that a connection to the supplicant
503     * daemon has been established.
504     */
505    void notifySupplicantConnection() {
506        sendEmptyMessage(EVENT_SUPPLICANT_CONNECTION);
507    }
508
509    /**
510     * Send the tracker a notification that the state of the supplicant
511     * has changed.
512     * @param networkId the configured network on which the state change occurred
513     * @param newState the new {@code SupplicantState}
514     */
515    void notifyStateChange(int networkId, SupplicantState newState) {
516        Message msg = Message.obtain(
517            this, EVENT_SUPPLICANT_STATE_CHANGED,
518            new SupplicantStateChangeResult(networkId, newState));
519        msg.sendToTarget();
520    }
521
522    /**
523     * Send the tracker a notification that the state of Wifi connectivity
524     * has changed.
525     * @param networkId the configured network on which the state change occurred
526     * @param newState the new network state
527     * @param BSSID when the new state is {@link DetailedState#CONNECTED
528     * NetworkInfo.DetailedState.CONNECTED},
529     * this is the MAC address of the access point. Otherwise, it
530     * is {@code null}.
531     */
532    void notifyStateChange(DetailedState newState, String BSSID, int networkId) {
533        Message msg = Message.obtain(
534            this, EVENT_NETWORK_STATE_CHANGED,
535            new NetworkStateChangeResult(newState, BSSID, networkId));
536        msg.sendToTarget();
537    }
538
539    /**
540     * Send the tracker a notification that a scan has completed, and results
541     * are available.
542     */
543    void notifyScanResultsAvailable() {
544        // reset the supplicant's handling of scan results to "normal" mode
545        synchronized (this) {
546            WifiNative.setScanResultHandlingCommand(SUPPL_SCAN_HANDLING_NORMAL);
547        }
548        sendEmptyMessage(EVENT_SCAN_RESULTS_AVAILABLE);
549    }
550
551    /**
552     * Send the tracker a notification that we can no longer communicate with
553     * the supplicant daemon.
554     */
555    void notifySupplicantLost() {
556        sendEmptyMessage(EVENT_SUPPLICANT_DISCONNECT);
557    }
558
559    /**
560     * Send the tracker a notification that the Wi-Fi driver has been stopped.
561     */
562    void notifyDriverStopped() {
563        mRunState = RUN_STATE_STOPPED;
564
565        // Send a driver stopped message to our handler
566        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STOPPED, 0).sendToTarget();
567    }
568
569    /**
570     * Send the tracker a notification that the Wi-Fi driver has been restarted after
571     * having been stopped.
572     */
573    void notifyDriverStarted() {
574        // Send a driver started message to our handler
575        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STARTED, 0).sendToTarget();
576    }
577
578    /**
579     * Send the tracker a notification that the Wi-Fi driver has hung and needs restarting.
580     */
581    void notifyDriverHung() {
582        // Send a driver hanged message to our handler
583        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_HUNG, 0).sendToTarget();
584    }
585
586    /**
587     * Set the interval timer for polling connection information
588     * that is not delivered asynchronously.
589     */
590    private synchronized void checkPollTimer() {
591        if (mEnableRssiPolling &&
592                mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED &&
593                !hasMessages(EVENT_POLL_INTERVAL)) {
594            sendEmptyMessageDelayed(EVENT_POLL_INTERVAL, POLL_STATUS_INTERVAL_MSECS);
595        }
596    }
597
598    private synchronized boolean isDriverStopped() {
599        return mRunState == RUN_STATE_STOPPED || mRunState == RUN_STATE_STOPPING;
600    }
601
602    private void noteRunState() {
603        try {
604            if (mRunState == RUN_STATE_RUNNING) {
605                mBatteryStats.noteWifiRunning();
606            } else if (mRunState == RUN_STATE_STOPPED) {
607                mBatteryStats.noteWifiStopped();
608            }
609        } catch (RemoteException ignore) {
610        }
611    }
612
613    /**
614     * Set the number of allowed radio frequency channels from the system
615     * setting value, if any.
616     * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
617     * the number of channels is invalid.
618     */
619    public boolean setNumAllowedChannels() {
620        try {
621            return setNumAllowedChannels(
622                    Settings.Secure.getInt(mContext.getContentResolver(),
623                    Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS));
624        } catch (Settings.SettingNotFoundException e) {
625            if (mNumAllowedChannels != 0) {
626                WifiNative.setNumAllowedChannelsCommand(mNumAllowedChannels);
627            }
628            // otherwise, use the driver default
629        }
630        return true;
631    }
632
633    /**
634     * Set the number of radio frequency channels that are allowed to be used
635     * in the current regulatory domain.
636     * @param numChannels the number of allowed channels. Must be greater than 0
637     * and less than or equal to 16.
638     * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
639     * {@code numChannels} is outside the valid range.
640     */
641    public synchronized boolean setNumAllowedChannels(int numChannels) {
642        mNumAllowedChannels = numChannels;
643        return WifiNative.setNumAllowedChannelsCommand(numChannels);
644    }
645
646    /**
647     * Set the run state to either "normal" or "scan-only".
648     * @param scanOnlyMode true if the new mode should be scan-only.
649     */
650    public synchronized void setScanOnlyMode(boolean scanOnlyMode) {
651        // do nothing unless scan-only mode is changing
652        if (mIsScanOnly != scanOnlyMode) {
653            int scanType = (scanOnlyMode ?
654                    SUPPL_SCAN_HANDLING_LIST_ONLY : SUPPL_SCAN_HANDLING_NORMAL);
655            if (LOCAL_LOGD) Log.v(TAG, "Scan-only mode changing to " + scanOnlyMode + " scanType=" + scanType);
656            if (WifiNative.setScanResultHandlingCommand(scanType)) {
657                mIsScanOnly = scanOnlyMode;
658                if (!isDriverStopped()) {
659                    if (scanOnlyMode) {
660                        WifiNative.disconnectCommand();
661                    } else {
662                        WifiNative.reconnectCommand();
663                    }
664                }
665            }
666        }
667    }
668
669    /**
670     * Enable or disable Bluetooth coexistence scan mode. When this mode is on,
671     * some of the low-level scan parameters used by the driver are changed to
672     * reduce interference with A2DP streaming.
673     *
674     * @param isBluetoothPlaying whether to enable or disable this mode
675     */
676    public synchronized void setBluetoothScanMode(boolean isBluetoothPlaying) {
677        WifiNative.setBluetoothCoexistenceScanModeCommand(isBluetoothPlaying);
678    }
679
680    private void checkIsBluetoothPlaying() {
681        boolean isBluetoothPlaying = false;
682        Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedSinks();
683
684        for (BluetoothDevice device : connected) {
685            if (mBluetoothA2dp.getSinkState(device) == BluetoothA2dp.STATE_PLAYING) {
686                isBluetoothPlaying = true;
687                break;
688            }
689        }
690        setBluetoothScanMode(isBluetoothPlaying);
691    }
692
693    public void enableRssiPolling(boolean enable) {
694        if (mEnableRssiPolling != enable) {
695            mEnableRssiPolling = enable;
696            checkPollTimer();
697        }
698    }
699
700    @Override
701    public void releaseWakeLock() {
702        if (mReleaseWakeLockCallback != null) {
703            mReleaseWakeLockCallback.run();
704        }
705    }
706
707    public void setReleaseWakeLockCallback(Runnable callback) {
708        mReleaseWakeLockCallback = callback;
709    }
710
711    /**
712     * Tracks the WPA supplicant states to detect "loop" situations.
713     * @param newSupplicantState The new WPA supplicant state.
714     * @return {@code true} if the supplicant loop should be stopped
715     * and {@code false} if it should continue.
716     */
717    private boolean isSupplicantLooping(SupplicantState newSupplicantState) {
718        if (SupplicantState.ASSOCIATING.ordinal() <= newSupplicantState.ordinal()
719            && newSupplicantState.ordinal() < SupplicantState.COMPLETED.ordinal()) {
720            if (mSupplicantLoopState != newSupplicantState) {
721                if (newSupplicantState.ordinal() < mSupplicantLoopState.ordinal()) {
722                    ++mNumSupplicantLoopIterations;
723                }
724
725                mSupplicantLoopState = newSupplicantState;
726            }
727        } else if (newSupplicantState == SupplicantState.COMPLETED) {
728            resetSupplicantLoopState();
729        }
730
731        return mNumSupplicantLoopIterations >= MAX_SUPPLICANT_LOOP_ITERATIONS;
732    }
733
734    /**
735     * Resets the WPA supplicant loop state.
736     */
737    private void resetSupplicantLoopState() {
738        mNumSupplicantLoopIterations = 0;
739    }
740
741    @Override
742    public void handleMessage(Message msg) {
743        Intent intent;
744
745        switch (msg.what) {
746            case EVENT_SUPPLICANT_CONNECTION:
747                mRunState = RUN_STATE_RUNNING;
748                noteRunState();
749                checkUseStaticIp();
750                /*
751                 * DHCP requests are blocking, so run them in a separate thread.
752                 */
753                HandlerThread dhcpThread = new HandlerThread("DHCP Handler Thread");
754                dhcpThread.start();
755                mDhcpTarget = new DhcpHandler(dhcpThread.getLooper(), this);
756                mIsScanModeActive = true;
757                mTornDownByConnMgr = false;
758                mLastBssid = null;
759                mLastSsid = null;
760                requestConnectionInfo();
761                SupplicantState supplState = mWifiInfo.getSupplicantState();
762                /**
763                 * The MAC address isn't going to change, so just request it
764                 * once here.
765                 */
766                String macaddr;
767                synchronized (this) {
768                    macaddr = WifiNative.getMacAddressCommand();
769                }
770                if (macaddr != null) {
771                    mWifiInfo.setMacAddress(macaddr);
772                }
773                if (LOCAL_LOGD) Log.v(TAG, "Connection to supplicant established, state=" +
774                    supplState);
775                // Wi-Fi supplicant connection state changed:
776                // [31- 2] Reserved for future use
777                // [ 1- 0] Connected to supplicant (1), disconnected from supplicant (0) ,
778                //         or supplicant died (2)
779                EventLog.writeEvent(EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED, 1);
780                /*
781                 * The COMPLETED state change from the supplicant may have occurred
782                 * in between polling for supplicant availability, in which case
783                 * we didn't perform a DHCP request to get an IP address.
784                 */
785                if (supplState == SupplicantState.COMPLETED) {
786                    mLastBssid = mWifiInfo.getBSSID();
787                    mLastSsid = mWifiInfo.getSSID();
788                    configureInterface();
789                }
790                if (ActivityManagerNative.isSystemReady()) {
791                    intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
792                    intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, true);
793                    mContext.sendBroadcast(intent);
794                }
795                if (supplState == SupplicantState.COMPLETED && mHaveIpAddress) {
796                    setDetailedState(DetailedState.CONNECTED);
797                } else {
798                    setDetailedState(WifiInfo.getDetailedStateOf(supplState));
799                }
800                /*
801                 * Filter out multicast packets. This saves battery power, since
802                 * the CPU doesn't have to spend time processing packets that
803                 * are going to end up being thrown away.
804                 *
805                 * Note that rather than turn this off directly, we use the
806                 * public api - this keeps us all in sync - turn multicast on
807                 * first and then off.. if nobody else wants it on it'll be
808                 * off then and it's all synchronized within the API.
809                 */
810                WifiManager.MulticastLock l =
811                        mWM.createMulticastLock("WifiStateTracker");
812                l.acquire();
813                l.release();
814
815                if (mBluetoothA2dp == null) {
816                    mBluetoothA2dp = new BluetoothA2dp(mContext);
817                }
818                checkIsBluetoothPlaying();
819
820                // initialize this after the supplicant is alive
821                setNumAllowedChannels();
822                break;
823
824            case EVENT_SUPPLICANT_DISCONNECT:
825                mRunState = RUN_STATE_STOPPED;
826                noteRunState();
827                int wifiState = mWM.getWifiState();
828                boolean died = wifiState != WifiManager.WIFI_STATE_DISABLED &&
829                        wifiState != WifiManager.WIFI_STATE_DISABLING;
830                if (died) {
831                    if (LOCAL_LOGD) Log.v(TAG, "Supplicant died unexpectedly");
832                } else {
833                    if (LOCAL_LOGD) Log.v(TAG, "Connection to supplicant lost");
834                }
835                // Wi-Fi supplicant connection state changed:
836                // [31- 2] Reserved for future use
837                // [ 1- 0] Connected to supplicant (1), disconnected from supplicant (0) ,
838                //         or supplicant died (2)
839                EventLog.writeEvent(EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED, died ? 2 : 0);
840                synchronized (this) {
841                    WifiNative.closeSupplicantConnection();
842                }
843                if (died) {
844                    resetInterface(false);
845                }
846                // When supplicant dies, kill the DHCP thread
847                if (mDhcpTarget != null) {
848                    mDhcpTarget.getLooper().quit();
849                    mDhcpTarget = null;
850                }
851                mContext.removeStickyBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION));
852                if (ActivityManagerNative.isSystemReady()) {
853                    intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
854                    intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false);
855                    mContext.sendBroadcast(intent);
856                }
857                setDetailedState(DetailedState.DISCONNECTED);
858                setSupplicantState(SupplicantState.UNINITIALIZED);
859                mHaveIpAddress = false;
860                mObtainingIpAddress = false;
861                if (died) {
862                    mWM.setWifiEnabled(false);
863                }
864                break;
865
866            case EVENT_MAYBE_START_SCAN_POST_DISCONNECT:
867                // Only do this if we haven't gotten a new supplicant status since the timer
868                // started
869                if (mNumSupplicantStateChanges == msg.arg1) {
870                    WifiNative.scanCommand(false); // do a passive scan
871                }
872                break;
873
874            case EVENT_SUPPLICANT_STATE_CHANGED:
875                mNumSupplicantStateChanges++;
876                SupplicantStateChangeResult supplicantStateResult =
877                    (SupplicantStateChangeResult) msg.obj;
878                SupplicantState newState = supplicantStateResult.state;
879                SupplicantState currentState = mWifiInfo.getSupplicantState();
880
881                // Wi-Fi supplicant state changed:
882                // [31- 6] Reserved for future use
883                // [ 5- 0] Supplicant state ordinal (as defined by SupplicantState)
884                int eventLogParam = (newState.ordinal() & 0x3f);
885                EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, eventLogParam);
886
887                if (LOCAL_LOGD) Log.v(TAG, "Changing supplicant state: "
888                                      + currentState +
889                                      " ==> " + newState);
890
891                int networkId = supplicantStateResult.networkId;
892
893                /*
894                 * If we get disconnect or inactive we need to start our
895                 * watchdog timer to start a scan
896                 */
897                if (newState == SupplicantState.DISCONNECTED ||
898                        newState == SupplicantState.INACTIVE) {
899                    sendMessageDelayed(obtainMessage(EVENT_MAYBE_START_SCAN_POST_DISCONNECT,
900                            mNumSupplicantStateChanges, 0), KICKSTART_SCANNING_DELAY_MSECS);
901                }
902
903
904                /*
905                 * Did we get to DISCONNECTED state due to an
906                 * authentication (password) failure?
907                 */
908                boolean failedToAuthenticate = false;
909                if (newState == SupplicantState.DISCONNECTED) {
910                    failedToAuthenticate = mPasswordKeyMayBeIncorrect;
911                }
912                mPasswordKeyMayBeIncorrect = false;
913
914                /*
915                 * Keep track of the supplicant state and check if we should
916                 * disable the network
917                 */
918                boolean disabledNetwork = false;
919                if (isSupplicantLooping(newState)) {
920                    if (LOCAL_LOGD) {
921                        Log.v(TAG,
922                              "Stop WPA supplicant loop and disable network");
923                    }
924                    disabledNetwork = wifiManagerDisableNetwork(networkId);
925                }
926
927                if (disabledNetwork) {
928                    /*
929                     * Reset the loop state if we disabled the network
930                     */
931                    resetSupplicantLoopState();
932                } else if (newState != currentState ||
933                        (newState == SupplicantState.DISCONNECTED && isDriverStopped())) {
934                    setSupplicantState(newState);
935                    if (newState == SupplicantState.DORMANT) {
936                        DetailedState newDetailedState;
937                        if (mIsScanOnly || mRunState == RUN_STATE_STOPPING) {
938                            newDetailedState = DetailedState.IDLE;
939                        } else {
940                            newDetailedState = DetailedState.FAILED;
941                        }
942                        handleDisconnectedState(newDetailedState);
943                        /**
944                         * If we were associated with a network (networkId != -1),
945                         * assume we reached this state because of a failed attempt
946                         * to acquire an IP address, and attempt another connection
947                         * and IP address acquisition in RECONNECT_DELAY_MSECS
948                         * milliseconds.
949                         */
950                        if (mRunState == RUN_STATE_RUNNING && !mIsScanOnly && networkId != -1) {
951                            sendEmptyMessageDelayed(EVENT_DEFERRED_RECONNECT, RECONNECT_DELAY_MSECS);
952                        } else if (mRunState == RUN_STATE_STOPPING) {
953                            synchronized (this) {
954                                WifiNative.stopDriverCommand();
955                            }
956                        } else if (mRunState == RUN_STATE_STARTING && !mIsScanOnly) {
957                            synchronized (this) {
958                                WifiNative.reconnectCommand();
959                            }
960                        }
961                    } else if (newState == SupplicantState.DISCONNECTED) {
962                        mHaveIpAddress = false;
963                        if (isDriverStopped() || mDisconnectExpected) {
964                            handleDisconnectedState(DetailedState.DISCONNECTED);
965                        } else {
966                            scheduleDisconnect();
967                        }
968                    } else if (newState != SupplicantState.COMPLETED && !mDisconnectPending) {
969                        /**
970                         * Ignore events that don't change the connectivity state,
971                         * such as WPA rekeying operations.
972                         */
973                        if (!(currentState == SupplicantState.COMPLETED &&
974                               (newState == SupplicantState.ASSOCIATING ||
975                                newState == SupplicantState.ASSOCIATED ||
976                                newState == SupplicantState.FOUR_WAY_HANDSHAKE ||
977                                newState == SupplicantState.GROUP_HANDSHAKE))) {
978                            setDetailedState(WifiInfo.getDetailedStateOf(newState));
979                        }
980                    }
981
982                    mDisconnectExpected = false;
983                    intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
984                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
985                    intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)newState);
986                    if (failedToAuthenticate) {
987                        if (LOCAL_LOGD) Log.d(TAG, "Failed to authenticate, disabling network " + networkId);
988                        wifiManagerDisableNetwork(networkId);
989                        intent.putExtra(
990                            WifiManager.EXTRA_SUPPLICANT_ERROR,
991                            WifiManager.ERROR_AUTHENTICATING);
992                    }
993                    mContext.sendStickyBroadcast(intent);
994                }
995                break;
996
997            case EVENT_NETWORK_STATE_CHANGED:
998                /*
999                 * Each CONNECT or DISCONNECT generates a pair of events.
1000                 * One is a supplicant state change event, and the other
1001                 * is a network state change event. For connects, the
1002                 * supplicant event always arrives first, followed by
1003                 * the network state change event. Only the latter event
1004                 * has the BSSID, which we are interested in capturing.
1005                 * For disconnects, the order is the opposite -- the
1006                 * network state change event comes first, followed by
1007                 * the supplicant state change event.
1008                 */
1009                NetworkStateChangeResult result =
1010                    (NetworkStateChangeResult) msg.obj;
1011
1012                // Wi-Fi network state changed:
1013                // [31- 6] Reserved for future use
1014                // [ 5- 0] Detailed state ordinal (as defined by NetworkInfo.DetailedState)
1015                eventLogParam = (result.state.ordinal() & 0x3f);
1016                EventLog.writeEvent(EVENTLOG_NETWORK_STATE_CHANGED, eventLogParam);
1017
1018                if (LOCAL_LOGD) Log.v(TAG, "New network state is " + result.state);
1019                /*
1020                 * If we're in scan-only mode, don't advance the state machine, and
1021                 * don't report the state change to clients.
1022                 */
1023                if (mIsScanOnly) {
1024                    if (LOCAL_LOGD) Log.v(TAG, "Dropping event in scan-only mode");
1025                    break;
1026                }
1027                if (result.state != DetailedState.SCANNING) {
1028                    /*
1029                     * Reset the scan count since there was a network state
1030                     * change. This could be from supplicant trying to associate
1031                     * with a network.
1032                     */
1033                    mNumScansSinceNetworkStateChange = 0;
1034                }
1035                /*
1036                 * If the supplicant sent us a CONNECTED event, we don't
1037                 * want to send out an indication of overall network
1038                 * connectivity until we have our IP address. If the
1039                 * supplicant sent us a DISCONNECTED event, we delay
1040                 * sending a notification in case a reconnection to
1041                 * the same access point occurs within a short time.
1042                 */
1043                if (result.state == DetailedState.DISCONNECTED) {
1044                    if (mWifiInfo.getSupplicantState() != SupplicantState.DORMANT) {
1045                        scheduleDisconnect();
1046                    }
1047                    break;
1048                }
1049                requestConnectionStatus(mWifiInfo);
1050                if (!(result.state == DetailedState.CONNECTED &&
1051                        (!mHaveIpAddress || mDisconnectPending))) {
1052                    setDetailedState(result.state);
1053                }
1054
1055                if (result.state == DetailedState.CONNECTED) {
1056                    /*
1057                     * Remove the 'available networks' notification when we
1058                     * successfully connect to a network.
1059                     */
1060                    setNotificationVisible(false, 0, false, 0);
1061                    boolean wasDisconnectPending = mDisconnectPending;
1062                    cancelDisconnect();
1063                    /*
1064                     * The connection is fully configured as far as link-level
1065                     * connectivity is concerned, but we may still need to obtain
1066                     * an IP address.
1067                     */
1068                    if (wasDisconnectPending) {
1069                        DetailedState saveState = getNetworkInfo().getDetailedState();
1070                        handleDisconnectedState(DetailedState.DISCONNECTED);
1071                        setDetailedStateInternal(saveState);
1072                    }
1073                    configureInterface();
1074                    mLastBssid = result.BSSID;
1075                    mLastSsid = mWifiInfo.getSSID();
1076                    mLastNetworkId = result.networkId;
1077                    if (mHaveIpAddress) {
1078                        setDetailedState(DetailedState.CONNECTED);
1079                    } else {
1080                        setDetailedState(DetailedState.OBTAINING_IPADDR);
1081                    }
1082                }
1083                sendNetworkStateChangeBroadcast(mWifiInfo.getBSSID());
1084                break;
1085
1086            case EVENT_SCAN_RESULTS_AVAILABLE:
1087                if (ActivityManagerNative.isSystemReady()) {
1088                    mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
1089                }
1090                sendScanResultsAvailable();
1091                /**
1092                 * On receiving the first scan results after connecting to
1093                 * the supplicant, switch scan mode over to passive.
1094                 */
1095                setScanMode(false);
1096                break;
1097
1098            case EVENT_POLL_INTERVAL:
1099                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
1100                    requestPolledInfo(mWifiInfo, true);
1101                    checkPollTimer();
1102                }
1103                break;
1104
1105            case EVENT_DEFERRED_DISCONNECT:
1106                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
1107                    handleDisconnectedState(DetailedState.DISCONNECTED);
1108                }
1109                break;
1110
1111            case EVENT_DEFERRED_RECONNECT:
1112                /*
1113                 * If we've exceeded the maximum number of retries for reconnecting
1114                 * to a given network, disable the network so that the supplicant
1115                 * will try some other network, if any is available.
1116                 * TODO: network ID may have changed since we stored it.
1117                 */
1118                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
1119                    if (++mReconnectCount > getMaxDhcpRetries()) {
1120                        mWM.disableNetwork(mLastNetworkId);
1121                    }
1122                    synchronized(this) {
1123                        WifiNative.reconnectCommand();
1124                    }
1125                }
1126                break;
1127
1128            case EVENT_INTERFACE_CONFIGURATION_SUCCEEDED:
1129                /**
1130                 * Since this event is sent from another thread, it might have been
1131                 * sent after we closed our connection to the supplicant in the course
1132                 * of disabling Wi-Fi. In that case, we should just ignore the event.
1133                 */
1134                if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) {
1135                    break;
1136                }
1137                mReconnectCount = 0;
1138                mHaveIpAddress = true;
1139                mObtainingIpAddress = false;
1140                mWifiInfo.setIpAddress(mDhcpInfo.ipAddress);
1141                mLastSignalLevel = -1; // force update of signal strength
1142                if (mNetworkInfo.getDetailedState() != DetailedState.CONNECTED) {
1143                    setDetailedState(DetailedState.CONNECTED);
1144                    sendNetworkStateChangeBroadcast(mWifiInfo.getBSSID());
1145                } else {
1146                    mTarget.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED);
1147                }
1148                if (LOCAL_LOGD) Log.v(TAG, "IP configuration: " + mDhcpInfo);
1149                // Wi-Fi interface configuration state changed:
1150                // [31- 1] Reserved for future use
1151                // [ 0- 0] Interface configuration succeeded (1) or failed (0)
1152                EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 1);
1153
1154                // We've connected successfully, so allow the notification again in the future
1155                resetNotificationTimer();
1156                break;
1157
1158            case EVENT_INTERFACE_CONFIGURATION_FAILED:
1159                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
1160                    // Wi-Fi interface configuration state changed:
1161                    // [31- 1] Reserved for future use
1162                    // [ 0- 0] Interface configuration succeeded (1) or failed (0)
1163                    EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 0);
1164
1165                    mHaveIpAddress = false;
1166                    mWifiInfo.setIpAddress(0);
1167                    mObtainingIpAddress = false;
1168                    synchronized(this) {
1169                        WifiNative.disconnectCommand();
1170                    }
1171                }
1172                break;
1173
1174            case EVENT_DRIVER_STATE_CHANGED:
1175                // Wi-Fi driver state changed:
1176                // 0 STARTED
1177                // 1 STOPPED
1178                // 2 HUNG
1179                EventLog.writeEvent(EVENTLOG_DRIVER_STATE_CHANGED, msg.arg1);
1180
1181                switch (msg.arg1) {
1182                case DRIVER_STARTED:
1183                    /**
1184                     * Set the number of allowed radio channels according
1185                     * to the system setting, since it gets reset by the
1186                     * driver upon changing to the STARTED state.
1187                     */
1188                    setNumAllowedChannels();
1189                    synchronized (this) {
1190                        if (mRunState == RUN_STATE_STARTING) {
1191                            mRunState = RUN_STATE_RUNNING;
1192                            if (!mIsScanOnly) {
1193                                WifiNative.reconnectCommand();
1194                            } else {
1195                                // In some situations, supplicant needs to be kickstarted to
1196                                // start the background scanning
1197                                WifiNative.scanCommand(true);
1198                            }
1199                        }
1200                    }
1201                    break;
1202                case DRIVER_HUNG:
1203                    Log.e(TAG, "Wifi Driver reports HUNG - reloading.");
1204                    /**
1205                     * restart the driver - toggle off and on
1206                     */
1207                    mWM.setWifiEnabled(false);
1208                    mWM.setWifiEnabled(true);
1209                    break;
1210                }
1211                noteRunState();
1212                break;
1213
1214            case EVENT_PASSWORD_KEY_MAY_BE_INCORRECT:
1215                mPasswordKeyMayBeIncorrect = true;
1216                break;
1217        }
1218    }
1219
1220    private boolean wifiManagerDisableNetwork(int networkId) {
1221        boolean disabledNetwork = false;
1222        if (0 <= networkId) {
1223            disabledNetwork = mWM.disableNetwork(networkId);
1224            if (LOCAL_LOGD) {
1225                if (disabledNetwork) {
1226                    Log.v(TAG, "Disabled network: " + networkId);
1227                }
1228            }
1229        }
1230        if (LOCAL_LOGD) {
1231            if (!disabledNetwork) {
1232                Log.e(TAG, "Failed to disable network:" +
1233                      " invalid network id: " + networkId);
1234            }
1235        }
1236        return disabledNetwork;
1237    }
1238
1239    public synchronized void setScanMode(boolean isScanModeActive) {
1240        if (mIsScanModeActive != isScanModeActive) {
1241            WifiNative.setScanModeCommand(mIsScanModeActive = isScanModeActive);
1242        }
1243    }
1244
1245    private void configureInterface() {
1246        checkPollTimer();
1247        mLastSignalLevel = -1;
1248        if (!mUseStaticIp) {
1249            if (!mHaveIpAddress && !mObtainingIpAddress) {
1250                mObtainingIpAddress = true;
1251                mDhcpTarget.sendEmptyMessage(EVENT_DHCP_START);
1252            }
1253        } else {
1254            int event;
1255            if (NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) {
1256                mHaveIpAddress = true;
1257                event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
1258                if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration succeeded");
1259            } else {
1260                mHaveIpAddress = false;
1261                event = EVENT_INTERFACE_CONFIGURATION_FAILED;
1262                if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration failed");
1263            }
1264            sendEmptyMessage(event);
1265        }
1266    }
1267
1268    /**
1269     * Reset our IP state and send out broadcasts following a disconnect.
1270     * @param newState the {@code DetailedState} to set. Should be either
1271     * {@code DISCONNECTED} or {@code FAILED}.
1272     */
1273    private void handleDisconnectedState(DetailedState newState) {
1274        if (LOCAL_LOGD) Log.d(TAG, "Deconfiguring interface and stopping DHCP");
1275        if (mDisconnectPending) {
1276            cancelDisconnect();
1277        }
1278        mDisconnectExpected = false;
1279        resetInterface(true);
1280        setDetailedState(newState);
1281        sendNetworkStateChangeBroadcast(mLastBssid);
1282        mWifiInfo.setBSSID(null);
1283        mLastBssid = null;
1284        mLastSsid = null;
1285        mDisconnectPending = false;
1286    }
1287
1288    /**
1289     * Resets the Wi-Fi interface by clearing any state, resetting any sockets
1290     * using the interface, stopping DHCP, and disabling the interface.
1291     */
1292    public void resetInterface(boolean reenable) {
1293        mHaveIpAddress = false;
1294        mObtainingIpAddress = false;
1295        mWifiInfo.setIpAddress(0);
1296
1297        /*
1298         * Reset connection depends on both the interface and the IP assigned,
1299         * so it should be done before any chance of the IP being lost.
1300         */
1301        NetworkUtils.resetConnections(mInterfaceName);
1302
1303        // Stop DHCP
1304        if (mDhcpTarget != null) {
1305            mDhcpTarget.setCancelCallback(true);
1306            mDhcpTarget.removeMessages(EVENT_DHCP_START);
1307        }
1308        if (!NetworkUtils.stopDhcp(mInterfaceName)) {
1309            Log.e(TAG, "Could not stop DHCP");
1310        }
1311
1312        NetworkUtils.disableInterface(mInterfaceName);
1313        // we no longer net to start the interface (driver does this for us)
1314        // and it led to problems - removed.
1315    }
1316
1317    /**
1318     * The supplicant is reporting that we are disconnected from the current
1319     * access point. Often, however, a disconnect will be followed very shortly
1320     * by a reconnect to the same access point. Therefore, we delay resetting
1321     * the connection's IP state for a bit.
1322     */
1323    private void scheduleDisconnect() {
1324        mDisconnectPending = true;
1325        if (!hasMessages(EVENT_DEFERRED_DISCONNECT)) {
1326            sendEmptyMessageDelayed(EVENT_DEFERRED_DISCONNECT, DISCONNECT_DELAY_MSECS);
1327        }
1328    }
1329
1330    private void cancelDisconnect() {
1331        mDisconnectPending = false;
1332        removeMessages(EVENT_DEFERRED_DISCONNECT);
1333    }
1334
1335    public DhcpInfo getDhcpInfo() {
1336        return mDhcpInfo;
1337    }
1338
1339    public synchronized List<ScanResult> getScanResultsList() {
1340        return mScanResults;
1341    }
1342
1343    public synchronized void setScanResultsList(List<ScanResult> scanList) {
1344        mScanResults = scanList;
1345    }
1346
1347    /**
1348     * Get status information for the current connection, if any.
1349     * @return a {@link WifiInfo} object containing information about the current connection
1350     */
1351    public WifiInfo requestConnectionInfo() {
1352        requestConnectionStatus(mWifiInfo);
1353        requestPolledInfo(mWifiInfo, false);
1354        return mWifiInfo;
1355    }
1356
1357    private void requestConnectionStatus(WifiInfo info) {
1358        String reply;
1359        synchronized (this) {
1360            reply = WifiNative.statusCommand();
1361        }
1362        if (reply == null) {
1363            return;
1364        }
1365        /*
1366         * Parse the reply from the supplicant to the status command, and update
1367         * local state accordingly. The reply is a series of lines of the form
1368         * "name=value".
1369         */
1370        String SSID = null;
1371        String BSSID = null;
1372        String suppState = null;
1373        int netId = -1;
1374        String[] lines = reply.split("\n");
1375        for (String line : lines) {
1376            String[] prop = line.split(" *= *");
1377            if (prop.length < 2)
1378                continue;
1379            String name = prop[0];
1380            String value = prop[1];
1381            if (name.equalsIgnoreCase("id"))
1382                netId = Integer.parseInt(value);
1383            else if (name.equalsIgnoreCase("ssid"))
1384                SSID = value;
1385            else if (name.equalsIgnoreCase("bssid"))
1386                BSSID = value;
1387            else if (name.equalsIgnoreCase("wpa_state"))
1388                suppState = value;
1389        }
1390        info.setNetworkId(netId);
1391        info.setSSID(SSID);
1392        info.setBSSID(BSSID);
1393        /*
1394         * We only set the supplicant state if the previous state was
1395         * UNINITIALIZED. This should only happen when we first connect to
1396         * the supplicant. Once we're connected, we should always receive
1397         * an event upon any state change, but in this case, we want to
1398         * make sure any listeners are made aware of the state change.
1399         */
1400        if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED && suppState != null)
1401            setSupplicantState(suppState);
1402    }
1403
1404    /**
1405     * Get the dynamic information that is not reported via events.
1406     * @param info the object into which the information should be captured.
1407     */
1408    private synchronized void requestPolledInfo(WifiInfo info, boolean polling)
1409    {
1410        int newRssi = (polling ? WifiNative.getRssiApproxCommand() : WifiNative.getRssiCommand());
1411        if (newRssi != -1 && -200 < newRssi && newRssi < 256) { // screen out invalid values
1412            /* some implementations avoid negative values by adding 256
1413             * so we need to adjust for that here.
1414             */
1415            if (newRssi > 0) newRssi -= 256;
1416            info.setRssi(newRssi);
1417            /*
1418             * Rather then sending the raw RSSI out every time it
1419             * changes, we precalculate the signal level that would
1420             * be displayed in the status bar, and only send the
1421             * broadcast if that much more coarse-grained number
1422             * changes. This cuts down greatly on the number of
1423             * broadcasts, at the cost of not informing others
1424             * interested in RSSI of all the changes in signal
1425             * level.
1426             */
1427            // TODO: The second arg to the call below needs to be a symbol somewhere, but
1428            // it's actually the size of an array of icons that's private
1429            // to StatusBar Policy.
1430            int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4);
1431            if (newSignalLevel != mLastSignalLevel) {
1432                sendRssiChangeBroadcast(newRssi);
1433            }
1434            mLastSignalLevel = newSignalLevel;
1435        } else {
1436            info.setRssi(-200);
1437        }
1438        int newLinkSpeed = WifiNative.getLinkSpeedCommand();
1439        if (newLinkSpeed != -1) {
1440            info.setLinkSpeed(newLinkSpeed);
1441        }
1442    }
1443
1444    private void sendRssiChangeBroadcast(final int newRssi) {
1445        if (ActivityManagerNative.isSystemReady()) {
1446            Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION);
1447            intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi);
1448            mContext.sendBroadcast(intent);
1449        }
1450    }
1451
1452    private void sendNetworkStateChangeBroadcast(String bssid) {
1453        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
1454        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
1455        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo);
1456        if (bssid != null)
1457            intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
1458        mContext.sendStickyBroadcast(intent);
1459    }
1460
1461    /**
1462     * Disable Wi-Fi connectivity by stopping the driver.
1463     */
1464    public boolean teardown() {
1465        if (!mTornDownByConnMgr) {
1466            if (disconnectAndStop()) {
1467                setTornDownByConnMgr(true);
1468                return true;
1469            } else {
1470                return false;
1471            }
1472        } else {
1473            return true;
1474        }
1475    }
1476
1477    /**
1478     * Reenable Wi-Fi connectivity by restarting the driver.
1479     */
1480    public boolean reconnect() {
1481        if (mTornDownByConnMgr) {
1482            if (restart()) {
1483                setTornDownByConnMgr(false);
1484                return true;
1485            } else {
1486                return false;
1487            }
1488        } else {
1489            return true;
1490        }
1491    }
1492
1493    /**
1494     * We want to stop the driver, but if we're connected to a network,
1495     * we first want to disconnect, so that the supplicant is always in
1496     * a known state (DISCONNECTED) when the driver is stopped.
1497     * @return {@code true} if the operation succeeds, which means that the
1498     * disconnect or stop command was initiated.
1499     */
1500    public synchronized boolean disconnectAndStop() {
1501        if (mRunState != RUN_STATE_STOPPING && mRunState != RUN_STATE_STOPPED) {
1502            // Take down any open network notifications
1503            setNotificationVisible(false, 0, false, 0);
1504
1505            mRunState = RUN_STATE_STOPPING;
1506            if (mWifiInfo.getSupplicantState() == SupplicantState.DORMANT) {
1507                return WifiNative.stopDriverCommand();
1508            } else {
1509                return WifiNative.disconnectCommand();
1510            }
1511        } else {
1512            /*
1513             * The "driver-stop" wake lock normally is released from the
1514             * connectivity manager after the mobile data connection has
1515             * been established, or after a timeout period, if that never
1516             * happens. Because WifiService.updateWifiState() can get called
1517             * multiple times, we can end up acquiring the wake lock and calling
1518             * disconnectAndStop() even when a disconnect or stop operation
1519             * is already in progress. In that case, we want to ignore the
1520             * disconnectAndStop request and release the (ref-counted) wake
1521             * lock, so that eventually, when the mobile data connection is
1522             * established, the ref count will drop to zero.
1523             */
1524            releaseWakeLock();
1525        }
1526        return true;
1527    }
1528
1529    public synchronized boolean restart() {
1530        if (mRunState == RUN_STATE_STOPPED) {
1531            mRunState = RUN_STATE_STARTING;
1532            resetInterface(true);
1533            return WifiNative.startDriverCommand();
1534        } else if (mRunState == RUN_STATE_STOPPING) {
1535            mRunState = RUN_STATE_STARTING;
1536        }
1537        return true;
1538    }
1539
1540    public synchronized boolean removeNetwork(int networkId) {
1541        return mDisconnectExpected = WifiNative.removeNetworkCommand(networkId);
1542    }
1543
1544    public boolean setRadio(boolean turnOn) {
1545        return mWM.setWifiEnabled(turnOn);
1546    }
1547
1548    /**
1549     * {@inheritDoc}
1550     * There are currently no Wi-Fi-specific features supported.
1551     * @param feature the name of the feature
1552     * @return {@code -1} indicating failure, always
1553     */
1554    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
1555        return -1;
1556    }
1557
1558    /**
1559     * {@inheritDoc}
1560     * There are currently no Wi-Fi-specific features supported.
1561     * @param feature the name of the feature
1562     * @return {@code -1} indicating failure, always
1563     */
1564    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
1565        return -1;
1566    }
1567
1568    @Override
1569    public void interpretScanResultsAvailable() {
1570
1571        // If we shouldn't place a notification on available networks, then
1572        // don't bother doing any of the following
1573        if (!mNotificationEnabled) return;
1574
1575        NetworkInfo networkInfo = getNetworkInfo();
1576
1577        State state = networkInfo.getState();
1578        if ((state == NetworkInfo.State.DISCONNECTED)
1579                || (state == NetworkInfo.State.UNKNOWN)) {
1580
1581            // Look for an open network
1582            List<ScanResult> scanResults = getScanResultsList();
1583            if (scanResults != null) {
1584                int numOpenNetworks = 0;
1585                for (int i = scanResults.size() - 1; i >= 0; i--) {
1586                    ScanResult scanResult = scanResults.get(i);
1587
1588                    if (TextUtils.isEmpty(scanResult.capabilities)) {
1589                        numOpenNetworks++;
1590                    }
1591                }
1592
1593                if (numOpenNetworks > 0) {
1594                    if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
1595                        /*
1596                         * We've scanned continuously at least
1597                         * NUM_SCANS_BEFORE_NOTIFICATION times. The user
1598                         * probably does not have a remembered network in range,
1599                         * since otherwise supplicant would have tried to
1600                         * associate and thus resetting this counter.
1601                         */
1602                        setNotificationVisible(true, numOpenNetworks, false, 0);
1603                    }
1604                    return;
1605                }
1606            }
1607        }
1608
1609        // No open networks in range, remove the notification
1610        setNotificationVisible(false, 0, false, 0);
1611    }
1612
1613    /**
1614     * Display or don't display a notification that there are open Wi-Fi networks.
1615     * @param visible {@code true} if notification should be visible, {@code false} otherwise
1616     * @param numNetworks the number networks seen
1617     * @param force {@code true} to force notification to be shown/not-shown,
1618     * even if it is already shown/not-shown.
1619     * @param delay time in milliseconds after which the notification should be made
1620     * visible or invisible.
1621     */
1622    public void setNotificationVisible(boolean visible, int numNetworks, boolean force, int delay) {
1623
1624        // Since we use auto cancel on the notification, when the
1625        // mNetworksAvailableNotificationShown is true, the notification may
1626        // have actually been canceled.  However, when it is false we know
1627        // for sure that it is not being shown (it will not be shown any other
1628        // place than here)
1629
1630        // If it should be hidden and it is already hidden, then noop
1631        if (!visible && !mNotificationShown && !force) {
1632            return;
1633        }
1634
1635        Message message;
1636        if (visible) {
1637
1638            // Not enough time has passed to show the notification again
1639            if (System.currentTimeMillis() < mNotificationRepeatTime) {
1640                return;
1641            }
1642
1643            if (mNotification == null) {
1644                // Cache the Notification mainly so we can remove the
1645                // EVENT_NOTIFICATION_CHANGED message with this Notification from
1646                // the queue later
1647                mNotification = new Notification();
1648                mNotification.when = 0;
1649                mNotification.icon = ICON_NETWORKS_AVAILABLE;
1650                mNotification.flags = Notification.FLAG_AUTO_CANCEL;
1651                mNotification.contentIntent = PendingIntent.getActivity(mContext, 0,
1652                        new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0);
1653            }
1654
1655            CharSequence title = mContext.getResources().getQuantityText(
1656                    com.android.internal.R.plurals.wifi_available, numNetworks);
1657            CharSequence details = mContext.getResources().getQuantityText(
1658                    com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
1659            mNotification.tickerText = title;
1660            mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent);
1661
1662            mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
1663
1664            message = mTarget.obtainMessage(EVENT_NOTIFICATION_CHANGED, 1,
1665                    ICON_NETWORKS_AVAILABLE, mNotification);
1666
1667        } else {
1668
1669            // Remove any pending messages to show the notification
1670            mTarget.removeMessages(EVENT_NOTIFICATION_CHANGED, mNotification);
1671
1672            message = mTarget.obtainMessage(EVENT_NOTIFICATION_CHANGED, 0, ICON_NETWORKS_AVAILABLE);
1673        }
1674
1675        mTarget.sendMessageDelayed(message, delay);
1676
1677        mNotificationShown = visible;
1678    }
1679
1680    /**
1681     * Clears variables related to tracking whether a notification has been
1682     * shown recently.
1683     * <p>
1684     * After calling this method, the timer that prevents notifications from
1685     * being shown too often will be cleared.
1686     */
1687    private void resetNotificationTimer() {
1688        mNotificationRepeatTime = 0;
1689        mNumScansSinceNetworkStateChange = 0;
1690    }
1691
1692    public synchronized boolean addToBlacklist(String bssid) {
1693        return WifiNative.addToBlacklistCommand(bssid);
1694    }
1695
1696    public synchronized boolean clearBlacklist() {
1697        return WifiNative.clearBlacklistCommand();
1698    }
1699
1700    @Override
1701    public String toString() {
1702        StringBuffer sb = new StringBuffer();
1703        sb.append("interface ").append(mInterfaceName);
1704        sb.append(" runState=");
1705        if (mRunState >= 1 && mRunState <= mRunStateNames.length) {
1706            sb.append(mRunStateNames[mRunState-1]);
1707        } else {
1708            sb.append(mRunState);
1709        }
1710        sb.append(LS).append(mWifiInfo).append(LS);
1711        sb.append(mDhcpInfo).append(LS);
1712        sb.append("haveIpAddress=").append(mHaveIpAddress).
1713                append(", obtainingIpAddress=").append(mObtainingIpAddress).
1714                append(", scanModeActive=").append(mIsScanModeActive).append(LS).
1715                append("lastSignalLevel=").append(mLastSignalLevel).
1716                append(", explicitlyDisabled=").append(mTornDownByConnMgr);
1717        return sb.toString();
1718    }
1719
1720    private class DhcpHandler extends Handler {
1721
1722        private Handler mTarget;
1723
1724        /**
1725         * Whether to skip the DHCP result callback to the target. For example,
1726         * this could be set if the network we were requesting an IP for has
1727         * since been disconnected.
1728         * <p>
1729         * Note: There is still a chance where the client's intended DHCP
1730         * request not being canceled. For example, we are request for IP on
1731         * A, and he queues request for IP on B, and then cancels the request on
1732         * B while we're still requesting from A.
1733         */
1734        private boolean mCancelCallback;
1735
1736        /**
1737         * Instance of the bluetooth headset helper. This needs to be created
1738         * early because there is a delay before it actually 'connects', as
1739         * noted by its javadoc. If we check before it is connected, it will be
1740         * in an error state and we will not disable coexistence.
1741         */
1742        private BluetoothHeadset mBluetoothHeadset;
1743
1744        public DhcpHandler(Looper looper, Handler target) {
1745            super(looper);
1746            mTarget = target;
1747
1748            mBluetoothHeadset = new BluetoothHeadset(mContext, null);
1749        }
1750
1751        public void handleMessage(Message msg) {
1752            int event;
1753
1754            switch (msg.what) {
1755                case EVENT_DHCP_START:
1756
1757                    boolean modifiedBluetoothCoexistenceMode = false;
1758                    int powerMode = DRIVER_POWER_MODE_AUTO;
1759
1760                    if (shouldDisableCoexistenceMode()) {
1761                        /*
1762                         * There are problems setting the Wi-Fi driver's power
1763                         * mode to active when bluetooth coexistence mode is
1764                         * enabled or sense.
1765                         * <p>
1766                         * We set Wi-Fi to active mode when
1767                         * obtaining an IP address because we've found
1768                         * compatibility issues with some routers with low power
1769                         * mode.
1770                         * <p>
1771                         * In order for this active power mode to properly be set,
1772                         * we disable coexistence mode until we're done with
1773                         * obtaining an IP address.  One exception is if we
1774                         * are currently connected to a headset, since disabling
1775                         * coexistence would interrupt that connection.
1776                         */
1777                        modifiedBluetoothCoexistenceMode = true;
1778
1779                        // Disable the coexistence mode
1780                        synchronized (WifiStateTracker.this) {
1781                            WifiNative.setBluetoothCoexistenceModeCommand(
1782                                    WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
1783                        }
1784                    }
1785
1786                    synchronized (WifiStateTracker.this) {
1787                        powerMode = WifiNative.getPowerModeCommand();
1788                        if (powerMode < 0) {
1789                            // Handle the case where supplicant driver does not support
1790                            // getPowerModeCommand.
1791                            powerMode = DRIVER_POWER_MODE_AUTO;
1792                        }
1793                        if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
1794                            WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_ACTIVE);
1795                        }
1796                    }
1797                    synchronized (this) {
1798                        // A new request is being made, so assume we will callback
1799                        mCancelCallback = false;
1800                    }
1801                    Log.d(TAG, "DhcpHandler: DHCP request started");
1802                    if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {
1803                        event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
1804                        if (LOCAL_LOGD) Log.v(TAG, "DhcpHandler: DHCP request succeeded");
1805                    } else {
1806                        event = EVENT_INTERFACE_CONFIGURATION_FAILED;
1807                        Log.i(TAG, "DhcpHandler: DHCP request failed: " +
1808                            NetworkUtils.getDhcpError());
1809                    }
1810                    synchronized (WifiStateTracker.this) {
1811                        if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
1812                            WifiNative.setPowerModeCommand(powerMode);
1813                        }
1814                    }
1815
1816                    if (modifiedBluetoothCoexistenceMode) {
1817                        // Set the coexistence mode back to its default value
1818                        synchronized (WifiStateTracker.this) {
1819                            WifiNative.setBluetoothCoexistenceModeCommand(
1820                                    WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
1821                        }
1822                    }
1823
1824                    synchronized (this) {
1825                        if (!mCancelCallback) {
1826                            mTarget.sendEmptyMessage(event);
1827                        }
1828                    }
1829                    break;
1830            }
1831        }
1832
1833        public synchronized void setCancelCallback(boolean cancelCallback) {
1834            mCancelCallback = cancelCallback;
1835        }
1836
1837        /**
1838         * Whether to disable coexistence mode while obtaining IP address. This
1839         * logic will return true only if the current bluetooth
1840         * headset/handsfree state is disconnected. This means if it is in an
1841         * error state, we will NOT disable coexistence mode to err on the side
1842         * of safety.
1843         *
1844         * @return Whether to disable coexistence mode.
1845         */
1846        private boolean shouldDisableCoexistenceMode() {
1847            int state = mBluetoothHeadset.getState();
1848            return state == BluetoothHeadset.STATE_DISCONNECTED;
1849        }
1850    }
1851
1852    private void checkUseStaticIp() {
1853        mUseStaticIp = false;
1854        final ContentResolver cr = mContext.getContentResolver();
1855        try {
1856            if (Settings.System.getInt(cr, Settings.System.WIFI_USE_STATIC_IP) == 0) {
1857                return;
1858            }
1859        } catch (Settings.SettingNotFoundException e) {
1860            return;
1861        }
1862
1863        try {
1864            String addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_IP);
1865            if (addr != null) {
1866                mDhcpInfo.ipAddress = stringToIpAddr(addr);
1867            } else {
1868                return;
1869            }
1870            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_GATEWAY);
1871            if (addr != null) {
1872                mDhcpInfo.gateway = stringToIpAddr(addr);
1873            } else {
1874                return;
1875            }
1876            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_NETMASK);
1877            if (addr != null) {
1878                mDhcpInfo.netmask = stringToIpAddr(addr);
1879            } else {
1880                return;
1881            }
1882            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS1);
1883            if (addr != null) {
1884                mDhcpInfo.dns1 = stringToIpAddr(addr);
1885            } else {
1886                return;
1887            }
1888            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS2);
1889            if (addr != null) {
1890                mDhcpInfo.dns2 = stringToIpAddr(addr);
1891            } else {
1892                mDhcpInfo.dns2 = 0;
1893            }
1894        } catch (UnknownHostException e) {
1895            return;
1896        }
1897        mUseStaticIp = true;
1898    }
1899
1900    private static int stringToIpAddr(String addrString) throws UnknownHostException {
1901        try {
1902            String[] parts = addrString.split("\\.");
1903            if (parts.length != 4) {
1904                throw new UnknownHostException(addrString);
1905            }
1906
1907            int a = Integer.parseInt(parts[0])      ;
1908            int b = Integer.parseInt(parts[1]) <<  8;
1909            int c = Integer.parseInt(parts[2]) << 16;
1910            int d = Integer.parseInt(parts[3]) << 24;
1911
1912            return a | b | c | d;
1913        } catch (NumberFormatException ex) {
1914            throw new UnknownHostException(addrString);
1915        }
1916    }
1917
1918    private int getMaxDhcpRetries() {
1919        return Settings.Secure.getInt(mContext.getContentResolver(),
1920                                      Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT,
1921                                      DEFAULT_MAX_DHCP_RETRIES);
1922    }
1923
1924    private class SettingsObserver extends ContentObserver {
1925        public SettingsObserver(Handler handler) {
1926            super(handler);
1927            ContentResolver cr = mContext.getContentResolver();
1928            cr.registerContentObserver(Settings.System.getUriFor(
1929                Settings.System.WIFI_USE_STATIC_IP), false, this);
1930            cr.registerContentObserver(Settings.System.getUriFor(
1931                Settings.System.WIFI_STATIC_IP), false, this);
1932            cr.registerContentObserver(Settings.System.getUriFor(
1933                Settings.System.WIFI_STATIC_GATEWAY), false, this);
1934            cr.registerContentObserver(Settings.System.getUriFor(
1935                Settings.System.WIFI_STATIC_NETMASK), false, this);
1936            cr.registerContentObserver(Settings.System.getUriFor(
1937                Settings.System.WIFI_STATIC_DNS1), false, this);
1938            cr.registerContentObserver(Settings.System.getUriFor(
1939                Settings.System.WIFI_STATIC_DNS2), false, this);
1940        }
1941
1942        public void onChange(boolean selfChange) {
1943            super.onChange(selfChange);
1944
1945            boolean wasStaticIp = mUseStaticIp;
1946            int oIp, oGw, oMsk, oDns1, oDns2;
1947            oIp = oGw = oMsk = oDns1 = oDns2 = 0;
1948            if (wasStaticIp) {
1949                oIp = mDhcpInfo.ipAddress;
1950                oGw = mDhcpInfo.gateway;
1951                oMsk = mDhcpInfo.netmask;
1952                oDns1 = mDhcpInfo.dns1;
1953                oDns2 = mDhcpInfo.dns2;
1954            }
1955            checkUseStaticIp();
1956
1957            if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) {
1958                return;
1959            }
1960
1961            boolean changed =
1962                (wasStaticIp != mUseStaticIp) ||
1963                    (wasStaticIp && (
1964                        oIp   != mDhcpInfo.ipAddress ||
1965                        oGw   != mDhcpInfo.gateway ||
1966                        oMsk  != mDhcpInfo.netmask ||
1967                        oDns1 != mDhcpInfo.dns1 ||
1968                        oDns2 != mDhcpInfo.dns2));
1969
1970            if (changed) {
1971                resetInterface(true);
1972                configureInterface();
1973                if (mUseStaticIp) {
1974                    mTarget.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED);
1975                }
1976            }
1977        }
1978    }
1979
1980    private class NotificationEnabledSettingObserver extends ContentObserver {
1981
1982        public NotificationEnabledSettingObserver(Handler handler) {
1983            super(handler);
1984        }
1985
1986        public void register() {
1987            ContentResolver cr = mContext.getContentResolver();
1988            cr.registerContentObserver(Settings.Secure.getUriFor(
1989                Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
1990            mNotificationEnabled = getValue();
1991        }
1992
1993        @Override
1994        public void onChange(boolean selfChange) {
1995            super.onChange(selfChange);
1996
1997            mNotificationEnabled = getValue();
1998            if (!mNotificationEnabled) {
1999                // Remove any notification that may be showing
2000                setNotificationVisible(false, 0, true, 0);
2001            }
2002
2003            resetNotificationTimer();
2004        }
2005
2006        private boolean getValue() {
2007            return Settings.Secure.getInt(mContext.getContentResolver(),
2008                    Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
2009        }
2010    }
2011}
2012