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