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