WifiController.java revision 2a70519406937402c761a6ce418c15e3b5598688
1/*
2 * Copyright (C) 2013 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 com.android.server.wifi;
18
19import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
20import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
21import static android.net.wifi.WifiManager.WIFI_MODE_NO_LOCKS_HELD;
22import static android.net.wifi.WifiManager.WIFI_MODE_SCAN_ONLY;
23
24import android.app.AlarmManager;
25import android.app.PendingIntent;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.database.ContentObserver;
31import android.net.ConnectivityManager;
32import android.net.NetworkInfo;
33import android.net.wifi.WifiConfiguration;
34import android.net.wifi.WifiManager;
35import android.os.Handler;
36import android.os.Looper;
37import android.os.Message;
38import android.os.SystemClock;
39import android.os.WorkSource;
40import android.provider.Settings;
41import android.util.Slog;
42
43import com.android.internal.util.Protocol;
44import com.android.internal.util.State;
45import com.android.internal.util.StateMachine;
46
47import java.io.FileDescriptor;
48import java.io.PrintWriter;
49
50/**
51 * WifiController is the class used to manage on/off state of WifiStateMachine for various operating
52 * modes (normal, airplane, wifi hotspot, etc.).
53 */
54public class WifiController extends StateMachine {
55    private static final String TAG = "WifiController";
56    private static final boolean DBG = false;
57    private Context mContext;
58    private boolean mScreenOff;
59    private boolean mDeviceIdle;
60    private int mPluggedType;
61    private int mStayAwakeConditions;
62    private long mIdleMillis;
63    private int mSleepPolicy;
64    private boolean mFirstUserSignOnSeen = false;
65
66    private AlarmManager mAlarmManager;
67    private PendingIntent mIdleIntent;
68    private static final int IDLE_REQUEST = 0;
69
70    /**
71     * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a
72     * Settings.Global value is not present. This timeout value is chosen as
73     * the approximate point at which the battery drain caused by Wi-Fi
74     * being enabled but not active exceeds the battery drain caused by
75     * re-establishing a connection to the mobile data network.
76     */
77    private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
78
79    /**
80     * See {@link Settings.Global#WIFI_REENABLE_DELAY_MS}.  This is the default value if a
81     * Settings.Global value is not present.  This is the minimum time after wifi is disabled
82     * we'll act on an enable.  Enable requests received before this delay will be deferred.
83     */
84    private static final long DEFAULT_REENABLE_DELAY_MS = 500;
85
86    // finding that delayed messages can sometimes be delivered earlier than expected
87    // probably rounding errors..  add a margin to prevent problems
88    private static final long DEFER_MARGIN_MS = 5;
89
90    NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
91
92    private static final String ACTION_DEVICE_IDLE =
93            "com.android.server.WifiManager.action.DEVICE_IDLE";
94
95    /* References to values tracked in WifiService */
96    private final WifiStateMachine mWifiStateMachine;
97    private final WifiSettingsStore mSettingsStore;
98    private final WifiLockManager mWifiLockManager;
99
100    /**
101     * Temporary for computing UIDS that are responsible for starting WIFI.
102     * Protected by mWifiStateTracker lock.
103     */
104    private final WorkSource mTmpWorkSource = new WorkSource();
105
106    private long mReEnableDelayMillis;
107
108    private FrameworkFacade mFacade;
109
110    private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
111
112    static final int CMD_EMERGENCY_MODE_CHANGED        = BASE + 1;
113    static final int CMD_SCREEN_ON                     = BASE + 2;
114    static final int CMD_SCREEN_OFF                    = BASE + 3;
115    static final int CMD_BATTERY_CHANGED               = BASE + 4;
116    static final int CMD_DEVICE_IDLE                   = BASE + 5;
117    static final int CMD_LOCKS_CHANGED                 = BASE + 6;
118    static final int CMD_SCAN_ALWAYS_MODE_CHANGED      = BASE + 7;
119    static final int CMD_WIFI_TOGGLED                  = BASE + 8;
120    static final int CMD_AIRPLANE_TOGGLED              = BASE + 9;
121    static final int CMD_SET_AP                        = BASE + 10;
122    static final int CMD_DEFERRED_TOGGLE               = BASE + 11;
123    static final int CMD_USER_PRESENT                  = BASE + 12;
124    static final int CMD_AP_START_FAILURE              = BASE + 13;
125    static final int CMD_EMERGENCY_CALL_STATE_CHANGED  = BASE + 14;
126    static final int CMD_AP_STOPPED                    = BASE + 15;
127    static final int CMD_STA_START_FAILURE             = BASE + 16;
128    // Command used to trigger a wifi stack restart when in active mode
129    static final int CMD_RESTART_WIFI                  = BASE + 17;
130    // Internal command used to complete wifi stack restart
131    private static final int CMD_RESTART_WIFI_CONTINUE = BASE + 18;
132
133    private DefaultState mDefaultState = new DefaultState();
134    private StaEnabledState mStaEnabledState = new StaEnabledState();
135    private ApStaDisabledState mApStaDisabledState = new ApStaDisabledState();
136    private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState();
137    private ApEnabledState mApEnabledState = new ApEnabledState();
138    private DeviceActiveState mDeviceActiveState = new DeviceActiveState();
139    private DeviceInactiveState mDeviceInactiveState = new DeviceInactiveState();
140    private ScanOnlyLockHeldState mScanOnlyLockHeldState = new ScanOnlyLockHeldState();
141    private FullLockHeldState mFullLockHeldState = new FullLockHeldState();
142    private FullHighPerfLockHeldState mFullHighPerfLockHeldState = new FullHighPerfLockHeldState();
143    private NoLockHeldState mNoLockHeldState = new NoLockHeldState();
144    private EcmState mEcmState = new EcmState();
145
146    WifiController(Context context, WifiStateMachine wsm, WifiSettingsStore wss,
147            WifiLockManager wifiLockManager, Looper looper, FrameworkFacade f) {
148        super(TAG, looper);
149        mFacade = f;
150        mContext = context;
151        mWifiStateMachine = wsm;
152        mSettingsStore = wss;
153        mWifiLockManager = wifiLockManager;
154
155        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
156        Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
157        mIdleIntent = mFacade.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
158
159        addState(mDefaultState);
160            addState(mApStaDisabledState, mDefaultState);
161            addState(mStaEnabledState, mDefaultState);
162                addState(mDeviceActiveState, mStaEnabledState);
163                addState(mDeviceInactiveState, mStaEnabledState);
164                    addState(mScanOnlyLockHeldState, mDeviceInactiveState);
165                    addState(mFullLockHeldState, mDeviceInactiveState);
166                    addState(mFullHighPerfLockHeldState, mDeviceInactiveState);
167                    addState(mNoLockHeldState, mDeviceInactiveState);
168            addState(mStaDisabledWithScanState, mDefaultState);
169            addState(mApEnabledState, mDefaultState);
170            addState(mEcmState, mDefaultState);
171
172        boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();
173        boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();
174        boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable();
175
176        log("isAirplaneModeOn = " + isAirplaneModeOn +
177                ", isWifiEnabled = " + isWifiEnabled +
178                ", isScanningAvailable = " + isScanningAlwaysAvailable);
179
180        if (isScanningAlwaysAvailable) {
181            setInitialState(mStaDisabledWithScanState);
182        } else {
183            setInitialState(mApStaDisabledState);
184        }
185
186        setLogRecSize(100);
187        setLogOnlyTransitions(false);
188
189        IntentFilter filter = new IntentFilter();
190        filter.addAction(ACTION_DEVICE_IDLE);
191        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
192        filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
193        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
194        mContext.registerReceiver(
195                new BroadcastReceiver() {
196                    @Override
197                    public void onReceive(Context context, Intent intent) {
198                        String action = intent.getAction();
199                        if (action.equals(ACTION_DEVICE_IDLE)) {
200                            sendMessage(CMD_DEVICE_IDLE);
201                        } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
202                            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
203                                    WifiManager.EXTRA_NETWORK_INFO);
204                        } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
205                            int state = intent.getIntExtra(
206                                    WifiManager.EXTRA_WIFI_AP_STATE,
207                                    WifiManager.WIFI_AP_STATE_FAILED);
208                            if (state == WifiManager.WIFI_AP_STATE_FAILED) {
209                                loge(TAG + "SoftAP start failed");
210                                sendMessage(CMD_AP_START_FAILURE);
211                            } else if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
212                                sendMessage(CMD_AP_STOPPED);
213                            }
214                        } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
215                            int state = intent.getIntExtra(
216                                    WifiManager.EXTRA_WIFI_STATE,
217                                    WifiManager.WIFI_STATE_UNKNOWN);
218                            if (state == WifiManager.WIFI_STATE_UNKNOWN) {
219                                loge(TAG + "Wifi turn on failed");
220                                sendMessage(CMD_STA_START_FAILURE);
221                            }
222                        }
223                    }
224                },
225                new IntentFilter(filter));
226
227        initializeAndRegisterForSettingsChange(looper);
228    }
229
230    private void initializeAndRegisterForSettingsChange(Looper looper) {
231        Handler handler = new Handler(looper);
232        readStayAwakeConditions();
233        registerForStayAwakeModeChange(handler);
234        readWifiIdleTime();
235        registerForWifiIdleTimeChange(handler);
236        readWifiSleepPolicy();
237        registerForWifiSleepPolicyChange(handler);
238        readWifiReEnableDelay();
239    }
240
241    private void readStayAwakeConditions() {
242        mStayAwakeConditions = mFacade.getIntegerSetting(mContext,
243                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
244    }
245
246    private void readWifiIdleTime() {
247        mIdleMillis = mFacade.getLongSetting(mContext,
248                Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
249    }
250
251    private void readWifiSleepPolicy() {
252        mSleepPolicy = mFacade.getIntegerSetting(mContext,
253                Settings.Global.WIFI_SLEEP_POLICY,
254                Settings.Global.WIFI_SLEEP_POLICY_NEVER);
255    }
256
257    private void readWifiReEnableDelay() {
258        mReEnableDelayMillis = mFacade.getLongSetting(mContext,
259                Settings.Global.WIFI_REENABLE_DELAY_MS, DEFAULT_REENABLE_DELAY_MS);
260    }
261
262    /**
263     * Observes settings changes to scan always mode.
264     */
265    private void registerForStayAwakeModeChange(Handler handler) {
266        ContentObserver contentObserver = new ContentObserver(handler) {
267            @Override
268            public void onChange(boolean selfChange) {
269                readStayAwakeConditions();
270            }
271        };
272
273        mFacade.registerContentObserver(mContext,
274                Settings.Global.getUriFor(Settings.Global.STAY_ON_WHILE_PLUGGED_IN), false,
275                contentObserver);
276    }
277
278    /**
279     * Observes settings changes to wifi idle time.
280     */
281    private void registerForWifiIdleTimeChange(Handler handler) {
282        ContentObserver contentObserver = new ContentObserver(handler) {
283            @Override
284            public void onChange(boolean selfChange) {
285                readWifiIdleTime();
286            }
287        };
288
289        mFacade.registerContentObserver(mContext,
290                Settings.Global.getUriFor(Settings.Global.WIFI_IDLE_MS), false, contentObserver);
291    }
292
293    /**
294     * Observes changes to wifi sleep policy
295     */
296    private void registerForWifiSleepPolicyChange(Handler handler) {
297        ContentObserver contentObserver = new ContentObserver(handler) {
298            @Override
299            public void onChange(boolean selfChange) {
300                readWifiSleepPolicy();
301            }
302        };
303        mFacade.registerContentObserver(mContext,
304                Settings.Global.getUriFor(Settings.Global.WIFI_SLEEP_POLICY), false,
305                contentObserver);
306    }
307
308    /**
309     * Determines whether the Wi-Fi chipset should stay awake or be put to
310     * sleep. Looks at the setting for the sleep policy and the current
311     * conditions.
312     *
313     * @see #shouldDeviceStayAwake(int)
314     */
315    private boolean shouldWifiStayAwake(int pluggedType) {
316        if (mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
317            // Never sleep
318            return true;
319        } else if ((mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
320                (pluggedType != 0)) {
321            // Never sleep while plugged, and we're plugged
322            return true;
323        } else {
324            // Default
325            return shouldDeviceStayAwake(pluggedType);
326        }
327    }
328
329    /**
330     * Determine whether the bit value corresponding to {@code pluggedType} is set in
331     * the bit string mStayAwakeConditions. This determines whether the device should
332     * stay awake based on the current plugged type.
333     *
334     * @param pluggedType the type of plug (USB, AC, or none) for which the check is
335     * being made
336     * @return {@code true} if {@code pluggedType} indicates that the device is
337     * supposed to stay awake, {@code false} otherwise.
338     */
339    private boolean shouldDeviceStayAwake(int pluggedType) {
340        return (mStayAwakeConditions & pluggedType) != 0;
341    }
342
343    private void updateBatteryWorkSource() {
344        mTmpWorkSource.clear();
345        if (mDeviceIdle) {
346            mTmpWorkSource.add(mWifiLockManager.createMergedWorkSource());
347        }
348        mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
349    }
350
351    class DefaultState extends State {
352        @Override
353        public boolean processMessage(Message msg) {
354            switch (msg.what) {
355                case CMD_SCREEN_ON:
356                    mAlarmManager.cancel(mIdleIntent);
357                    mScreenOff = false;
358                    mDeviceIdle = false;
359                    updateBatteryWorkSource();
360                    break;
361                case CMD_SCREEN_OFF:
362                    mScreenOff = true;
363                    /*
364                    * Set a timer to put Wi-Fi to sleep, but only if the screen is off
365                    * AND the "stay on while plugged in" setting doesn't match the
366                    * current power conditions (i.e, not plugged in, plugged in to USB,
367                    * or plugged in to AC).
368                    */
369                    if (!shouldWifiStayAwake(mPluggedType)) {
370                        //Delayed shutdown if wifi is connected
371                        if (mNetworkInfo.getDetailedState() ==
372                                NetworkInfo.DetailedState.CONNECTED) {
373                            if (DBG) Slog.d(TAG, "set idle timer: " + mIdleMillis + " ms");
374                            mAlarmManager.set(AlarmManager.RTC_WAKEUP,
375                                    System.currentTimeMillis() + mIdleMillis, mIdleIntent);
376                        } else {
377                            sendMessage(CMD_DEVICE_IDLE);
378                        }
379                    }
380                    break;
381                case CMD_DEVICE_IDLE:
382                    mDeviceIdle = true;
383                    updateBatteryWorkSource();
384                    break;
385                case CMD_BATTERY_CHANGED:
386                    /*
387                    * Set a timer to put Wi-Fi to sleep, but only if the screen is off
388                    * AND we are transitioning from a state in which the device was supposed
389                    * to stay awake to a state in which it is not supposed to stay awake.
390                    * If "stay awake" state is not changing, we do nothing, to avoid resetting
391                    * the already-set timer.
392                    */
393                    int pluggedType = msg.arg1;
394                    if (DBG) Slog.d(TAG, "battery changed pluggedType: " + pluggedType);
395                    if (mScreenOff && shouldWifiStayAwake(mPluggedType) &&
396                            !shouldWifiStayAwake(pluggedType)) {
397                        long triggerTime = System.currentTimeMillis() + mIdleMillis;
398                        if (DBG) Slog.d(TAG, "set idle timer for " + mIdleMillis + "ms");
399                        mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
400                    }
401
402                    mPluggedType = pluggedType;
403                    break;
404                case CMD_SET_AP:
405                case CMD_SCAN_ALWAYS_MODE_CHANGED:
406                case CMD_LOCKS_CHANGED:
407                case CMD_WIFI_TOGGLED:
408                case CMD_AIRPLANE_TOGGLED:
409                case CMD_EMERGENCY_MODE_CHANGED:
410                case CMD_EMERGENCY_CALL_STATE_CHANGED:
411                case CMD_AP_START_FAILURE:
412                case CMD_AP_STOPPED:
413                case CMD_STA_START_FAILURE:
414                case CMD_RESTART_WIFI:
415                case CMD_RESTART_WIFI_CONTINUE:
416                    break;
417                case CMD_USER_PRESENT:
418                    mFirstUserSignOnSeen = true;
419                    break;
420                case CMD_DEFERRED_TOGGLE:
421                    log("DEFERRED_TOGGLE ignored due to state change");
422                    break;
423                default:
424                    throw new RuntimeException("WifiController.handleMessage " + msg.what);
425            }
426            return HANDLED;
427        }
428
429    }
430
431    class ApStaDisabledState extends State {
432        private int mDeferredEnableSerialNumber = 0;
433        private boolean mHaveDeferredEnable = false;
434        private long mDisabledTimestamp;
435
436        @Override
437        public void enter() {
438            mWifiStateMachine.setSupplicantRunning(false);
439            // Supplicant can't restart right away, so not the time we switched off
440            mDisabledTimestamp = SystemClock.elapsedRealtime();
441            mDeferredEnableSerialNumber++;
442            mHaveDeferredEnable = false;
443            mWifiStateMachine.clearANQPCache();
444        }
445        @Override
446        public boolean processMessage(Message msg) {
447            switch (msg.what) {
448                case CMD_WIFI_TOGGLED:
449                case CMD_AIRPLANE_TOGGLED:
450                    if (mSettingsStore.isWifiToggleEnabled()) {
451                        if (doDeferEnable(msg)) {
452                            if (mHaveDeferredEnable) {
453                                //  have 2 toggles now, inc serial number an ignore both
454                                mDeferredEnableSerialNumber++;
455                            }
456                            mHaveDeferredEnable = !mHaveDeferredEnable;
457                            break;
458                        }
459                        if (mDeviceIdle == false) {
460                            // wifi is toggled, we need to explicitly tell WifiStateMachine that we
461                            // are headed to connect mode before going to the DeviceActiveState
462                            // since that will start supplicant and WifiStateMachine may not know
463                            // what state to head to (it might go to scan mode).
464                            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
465                            transitionTo(mDeviceActiveState);
466                        } else {
467                            checkLocksAndTransitionWhenDeviceIdle();
468                        }
469                    } else if (mSettingsStore.isScanAlwaysAvailable()) {
470                        transitionTo(mStaDisabledWithScanState);
471                    }
472                    break;
473                case CMD_SCAN_ALWAYS_MODE_CHANGED:
474                    if (mSettingsStore.isScanAlwaysAvailable()) {
475                        transitionTo(mStaDisabledWithScanState);
476                    }
477                    break;
478                case CMD_SET_AP:
479                    if (msg.arg1 == 1) {
480                        if (msg.arg2 == 0) { // previous wifi state has not been saved yet
481                            mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
482                        }
483                        mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
484                                true);
485                        transitionTo(mApEnabledState);
486                    }
487                    break;
488                case CMD_DEFERRED_TOGGLE:
489                    if (msg.arg1 != mDeferredEnableSerialNumber) {
490                        log("DEFERRED_TOGGLE ignored due to serial mismatch");
491                        break;
492                    }
493                    log("DEFERRED_TOGGLE handled");
494                    sendMessage((Message)(msg.obj));
495                    break;
496                case CMD_RESTART_WIFI_CONTINUE:
497                    transitionTo(mDeviceActiveState);
498                    break;
499                default:
500                    return NOT_HANDLED;
501            }
502            return HANDLED;
503        }
504
505        private boolean doDeferEnable(Message msg) {
506            long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
507            if (delaySoFar >= mReEnableDelayMillis) {
508                return false;
509            }
510
511            log("WifiController msg " + msg + " deferred for " +
512                    (mReEnableDelayMillis - delaySoFar) + "ms");
513
514            // need to defer this action.
515            Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
516            deferredMsg.obj = Message.obtain(msg);
517            deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
518            sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
519            return true;
520        }
521
522    }
523
524    class StaEnabledState extends State {
525        @Override
526        public void enter() {
527            mWifiStateMachine.setSupplicantRunning(true);
528        }
529        @Override
530        public boolean processMessage(Message msg) {
531            switch (msg.what) {
532                case CMD_WIFI_TOGGLED:
533                    if (! mSettingsStore.isWifiToggleEnabled()) {
534                        if (mSettingsStore.isScanAlwaysAvailable()) {
535                            transitionTo(mStaDisabledWithScanState);
536                        } else {
537                            transitionTo(mApStaDisabledState);
538                        }
539                    }
540                    break;
541                case CMD_AIRPLANE_TOGGLED:
542                    /* When wi-fi is turned off due to airplane,
543                    * disable entirely (including scan)
544                    */
545                    if (! mSettingsStore.isWifiToggleEnabled()) {
546                        transitionTo(mApStaDisabledState);
547                    }
548                    break;
549                case CMD_STA_START_FAILURE:
550                    if (!mSettingsStore.isScanAlwaysAvailable()) {
551                        transitionTo(mApStaDisabledState);
552                    } else {
553                        transitionTo(mStaDisabledWithScanState);
554                    }
555                    break;
556                case CMD_EMERGENCY_CALL_STATE_CHANGED:
557                case CMD_EMERGENCY_MODE_CHANGED:
558                    boolean getConfigWiFiDisableInECBM = mFacade.getConfigWiFiDisableInECBM(mContext);
559                    log("WifiController msg " + msg + " getConfigWiFiDisableInECBM "
560                            + getConfigWiFiDisableInECBM);
561                    if ((msg.arg1 == 1) && getConfigWiFiDisableInECBM) {
562                        transitionTo(mEcmState);
563                    }
564                    break;
565                case CMD_SET_AP:
566                    if (msg.arg1 == 1) {
567                        // remeber that we were enabled
568                        mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_ENABLED);
569                        deferMessage(obtainMessage(msg.what, msg.arg1, 1, msg.obj));
570                        transitionTo(mApStaDisabledState);
571                    }
572                    break;
573                default:
574                    return NOT_HANDLED;
575
576            }
577            return HANDLED;
578        }
579    }
580
581    class StaDisabledWithScanState extends State {
582        private int mDeferredEnableSerialNumber = 0;
583        private boolean mHaveDeferredEnable = false;
584        private long mDisabledTimestamp;
585
586        @Override
587        public void enter() {
588            // need to set the mode before starting supplicant because WSM will assume we are going
589            // in to client mode
590            mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
591            mWifiStateMachine.setSupplicantRunning(true);
592            // Supplicant can't restart right away, so not the time we switched off
593            mDisabledTimestamp = SystemClock.elapsedRealtime();
594            mDeferredEnableSerialNumber++;
595            mHaveDeferredEnable = false;
596            mWifiStateMachine.clearANQPCache();
597        }
598
599        @Override
600        public boolean processMessage(Message msg) {
601            switch (msg.what) {
602                case CMD_WIFI_TOGGLED:
603                    if (mSettingsStore.isWifiToggleEnabled()) {
604                        if (doDeferEnable(msg)) {
605                            if (mHaveDeferredEnable) {
606                                // have 2 toggles now, inc serial number and ignore both
607                                mDeferredEnableSerialNumber++;
608                            }
609                            mHaveDeferredEnable = !mHaveDeferredEnable;
610                            break;
611                        }
612                        if (mDeviceIdle == false) {
613                            transitionTo(mDeviceActiveState);
614                        } else {
615                            checkLocksAndTransitionWhenDeviceIdle();
616                        }
617                    }
618                    break;
619                case CMD_AIRPLANE_TOGGLED:
620                    if (mSettingsStore.isAirplaneModeOn() &&
621                            ! mSettingsStore.isWifiToggleEnabled()) {
622                        transitionTo(mApStaDisabledState);
623                    }
624                    break;
625                case CMD_SCAN_ALWAYS_MODE_CHANGED:
626                    if (! mSettingsStore.isScanAlwaysAvailable()) {
627                        transitionTo(mApStaDisabledState);
628                    }
629                    break;
630                case CMD_SET_AP:
631                    // Before starting tethering, turn off supplicant for scan mode
632                    if (msg.arg1 == 1) {
633                        mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
634                        deferMessage(obtainMessage(msg.what, msg.arg1, 1, msg.obj));
635                        transitionTo(mApStaDisabledState);
636                    }
637                    break;
638                case CMD_DEFERRED_TOGGLE:
639                    if (msg.arg1 != mDeferredEnableSerialNumber) {
640                        log("DEFERRED_TOGGLE ignored due to serial mismatch");
641                        break;
642                    }
643                    logd("DEFERRED_TOGGLE handled");
644                    sendMessage((Message)(msg.obj));
645                    break;
646                default:
647                    return NOT_HANDLED;
648            }
649            return HANDLED;
650        }
651
652        private boolean doDeferEnable(Message msg) {
653            long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
654            if (delaySoFar >= mReEnableDelayMillis) {
655                return false;
656            }
657
658            log("WifiController msg " + msg + " deferred for " +
659                    (mReEnableDelayMillis - delaySoFar) + "ms");
660
661            // need to defer this action.
662            Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
663            deferredMsg.obj = Message.obtain(msg);
664            deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
665            sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
666            return true;
667        }
668
669    }
670
671    /**
672     * Only transition out of this state when AP failed to start or AP is stopped.
673     */
674    class ApEnabledState extends State {
675        /**
676         * Save the pending state when stopping the AP, so that it will transition
677         * to the correct state when AP is stopped.  This is to avoid a possible
678         * race condition where the new state might try to update the driver/interface
679         * state before AP is completely torn down.
680         */
681        private State mPendingState = null;
682
683        /**
684         * Determine the next state based on the current settings (e.g. saved
685         * wifi state).
686         */
687        private State getNextWifiState() {
688            if (mSettingsStore.getWifiSavedState() == WifiSettingsStore.WIFI_ENABLED) {
689                return mDeviceActiveState;
690            }
691
692            if (mSettingsStore.isScanAlwaysAvailable()) {
693                return mStaDisabledWithScanState;
694            }
695
696            return mApStaDisabledState;
697        }
698
699        @Override
700        public boolean processMessage(Message msg) {
701            switch (msg.what) {
702                case CMD_AIRPLANE_TOGGLED:
703                    if (mSettingsStore.isAirplaneModeOn()) {
704                        mWifiStateMachine.setHostApRunning(null, false);
705                        mPendingState = mApStaDisabledState;
706                    }
707                    break;
708                case CMD_WIFI_TOGGLED:
709                    if (mSettingsStore.isWifiToggleEnabled()) {
710                        mWifiStateMachine.setHostApRunning(null, false);
711                        mPendingState = mDeviceActiveState;
712                    }
713                    break;
714                case CMD_SET_AP:
715                    if (msg.arg1 == 0) {
716                        mWifiStateMachine.setHostApRunning(null, false);
717                        mPendingState = getNextWifiState();
718                    }
719                    break;
720                case CMD_AP_STOPPED:
721                    if (mPendingState == null) {
722                        /**
723                         * Stop triggered internally, either tether notification
724                         * timed out or wifi is untethered for some reason.
725                         */
726                        mPendingState = getNextWifiState();
727                    }
728                    if (mPendingState == mDeviceActiveState && mDeviceIdle) {
729                        checkLocksAndTransitionWhenDeviceIdle();
730                    } else {
731                        // go ahead and transition because we are not idle or we are not going
732                        // to the active state.
733                        transitionTo(mPendingState);
734                    }
735                    break;
736                case CMD_EMERGENCY_CALL_STATE_CHANGED:
737                case CMD_EMERGENCY_MODE_CHANGED:
738                    if (msg.arg1 == 1) {
739                        mWifiStateMachine.setHostApRunning(null, false);
740                        mPendingState = mEcmState;
741                    }
742                    break;
743                case CMD_AP_START_FAILURE:
744                    transitionTo(getNextWifiState());
745                    break;
746                default:
747                    return NOT_HANDLED;
748            }
749            return HANDLED;
750        }
751    }
752
753    class EcmState extends State {
754        // we can enter EcmState either because an emergency call started or because
755        // emergency callback mode started. This count keeps track of how many such
756        // events happened; so we can exit after all are undone
757
758        private int mEcmEntryCount;
759        @Override
760        public void enter() {
761            mWifiStateMachine.setSupplicantRunning(false);
762            mWifiStateMachine.clearANQPCache();
763            mEcmEntryCount = 1;
764        }
765
766        @Override
767        public boolean processMessage(Message msg) {
768            if (msg.what == CMD_EMERGENCY_CALL_STATE_CHANGED) {
769                if (msg.arg1 == 1) {
770                    // nothing to do - just says emergency call started
771                    mEcmEntryCount++;
772                } else if (msg.arg1 == 0) {
773                    // emergency call ended
774                    decrementCountAndReturnToAppropriateState();
775                }
776                return HANDLED;
777            } else if (msg.what == CMD_EMERGENCY_MODE_CHANGED) {
778
779                if (msg.arg1 == 1) {
780                    // Transitioned into emergency callback mode
781                    mEcmEntryCount++;
782                } else if (msg.arg1 == 0) {
783                    // out of emergency callback mode
784                    decrementCountAndReturnToAppropriateState();
785                }
786                return HANDLED;
787            } else {
788                return NOT_HANDLED;
789            }
790        }
791
792        private void decrementCountAndReturnToAppropriateState() {
793            boolean exitEcm = false;
794
795            if (mEcmEntryCount == 0) {
796                loge("mEcmEntryCount is 0; exiting Ecm");
797                exitEcm = true;
798            } else if (--mEcmEntryCount == 0) {
799                exitEcm = true;
800            }
801
802            if (exitEcm) {
803                if (mSettingsStore.isWifiToggleEnabled()) {
804                    if (mDeviceIdle == false) {
805                        transitionTo(mDeviceActiveState);
806                    } else {
807                        checkLocksAndTransitionWhenDeviceIdle();
808                    }
809                } else if (mSettingsStore.isScanAlwaysAvailable()) {
810                    transitionTo(mStaDisabledWithScanState);
811                } else {
812                    transitionTo(mApStaDisabledState);
813                }
814            }
815        }
816    }
817
818    /* Parent: StaEnabledState */
819    class DeviceActiveState extends State {
820        @Override
821        public void enter() {
822            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
823            mWifiStateMachine.setHighPerfModeEnabled(false);
824        }
825
826        @Override
827        public boolean processMessage(Message msg) {
828            if (msg.what == CMD_DEVICE_IDLE) {
829                checkLocksAndTransitionWhenDeviceIdle();
830                // We let default state handle the rest of work
831            } else if (msg.what == CMD_USER_PRESENT) {
832                // TLS networks can't connect until user unlocks keystore. KeyStore
833                // unlocks when the user punches PIN after the reboot. So use this
834                // trigger to get those networks connected.
835                if (mFirstUserSignOnSeen == false) {
836                    mWifiStateMachine.reloadTlsNetworksAndReconnect();
837                }
838                mFirstUserSignOnSeen = true;
839                return HANDLED;
840            } else if (msg.what == CMD_RESTART_WIFI) {
841                deferMessage(obtainMessage(CMD_RESTART_WIFI_CONTINUE));
842                transitionTo(mApStaDisabledState);
843                return HANDLED;
844            }
845            return NOT_HANDLED;
846        }
847    }
848
849    /* Parent: StaEnabledState */
850    class DeviceInactiveState extends State {
851        @Override
852        public boolean processMessage(Message msg) {
853            switch (msg.what) {
854                case CMD_LOCKS_CHANGED:
855                    checkLocksAndTransitionWhenDeviceIdle();
856                    updateBatteryWorkSource();
857                    return HANDLED;
858                case CMD_SCREEN_ON:
859                    transitionTo(mDeviceActiveState);
860                    // More work in default state
861                    return NOT_HANDLED;
862                default:
863                    return NOT_HANDLED;
864            }
865        }
866    }
867
868    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a scan only lock. */
869    class ScanOnlyLockHeldState extends State {
870        @Override
871        public void enter() {
872            mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
873        }
874    }
875
876    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a full lock. */
877    class FullLockHeldState extends State {
878        @Override
879        public void enter() {
880            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
881            mWifiStateMachine.setHighPerfModeEnabled(false);
882        }
883    }
884
885    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a high perf lock. */
886    class FullHighPerfLockHeldState extends State {
887        @Override
888        public void enter() {
889            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
890            mWifiStateMachine.setHighPerfModeEnabled(true);
891        }
892    }
893
894    /* Parent: DeviceInactiveState. Device is inactive and no app is holding a wifi lock. */
895    class NoLockHeldState extends State {
896        @Override
897        public void enter() {
898            mWifiStateMachine.setOperationalMode(WifiStateMachine.DISABLED_MODE);
899        }
900    }
901
902    private void checkLocksAndTransitionWhenDeviceIdle() {
903        switch (mWifiLockManager.getStrongestLockMode()) {
904            case WIFI_MODE_NO_LOCKS_HELD:
905                if (mSettingsStore.isScanAlwaysAvailable()) {
906                    transitionTo(mScanOnlyLockHeldState);
907                } else {
908                    transitionTo(mNoLockHeldState);
909                }
910                break;
911            case WIFI_MODE_FULL:
912                transitionTo(mFullLockHeldState);
913                break;
914            case WIFI_MODE_FULL_HIGH_PERF:
915                transitionTo(mFullHighPerfLockHeldState);
916                break;
917            case WIFI_MODE_SCAN_ONLY:
918                transitionTo(mScanOnlyLockHeldState);
919                break;
920        }
921    }
922
923    @Override
924    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
925        super.dump(fd, pw, args);
926
927        pw.println("mScreenOff " + mScreenOff);
928        pw.println("mDeviceIdle " + mDeviceIdle);
929        pw.println("mPluggedType " + mPluggedType);
930        pw.println("mIdleMillis " + mIdleMillis);
931        pw.println("mSleepPolicy " + mSleepPolicy);
932    }
933}
934