Home | History | Annotate | Download | only in wifi
      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 
     17 package com.android.server.wifi;
     18 
     19 import android.app.AlarmManager;
     20 import android.app.PendingIntent;
     21 import android.content.BroadcastReceiver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.database.ContentObserver;
     26 import android.net.ConnectivityManager;
     27 import android.net.NetworkInfo;
     28 import android.net.wifi.WifiConfiguration;
     29 import android.net.wifi.WifiManager;
     30 import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
     31 import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
     32 import static android.net.wifi.WifiManager.WIFI_MODE_SCAN_ONLY;
     33 import android.net.wifi.WifiStateMachine;
     34 import android.os.Handler;
     35 import android.os.Looper;
     36 import android.os.Message;
     37 import android.os.SystemClock;
     38 import android.os.WorkSource;
     39 import android.provider.Settings;
     40 import android.util.Slog;
     41 
     42 import com.android.internal.util.Protocol;
     43 import com.android.internal.util.State;
     44 import com.android.internal.util.StateMachine;
     45 import com.android.server.wifi.WifiService.LockList;
     46 
     47 import java.io.FileDescriptor;
     48 import java.io.PrintWriter;
     49 
     50 class WifiController extends StateMachine {
     51     private static final String TAG = "WifiController";
     52     private static final boolean DBG = false;
     53     private Context mContext;
     54     private boolean mScreenOff;
     55     private boolean mDeviceIdle;
     56     private int mPluggedType;
     57     private int mStayAwakeConditions;
     58     private long mIdleMillis;
     59     private int mSleepPolicy;
     60 
     61     private AlarmManager mAlarmManager;
     62     private PendingIntent mIdleIntent;
     63     private static final int IDLE_REQUEST = 0;
     64 
     65     /**
     66      * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a
     67      * Settings.Global value is not present. This timeout value is chosen as
     68      * the approximate point at which the battery drain caused by Wi-Fi
     69      * being enabled but not active exceeds the battery drain caused by
     70      * re-establishing a connection to the mobile data network.
     71      */
     72     private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
     73 
     74     /**
     75      * See {@link Settings.Global#WIFI_REENABLE_DELAY_MS}.  This is the default value if a
     76      * Settings.Global value is not present.  This is the minimum time after wifi is disabled
     77      * we'll act on an enable.  Enable requests received before this delay will be deferred.
     78      */
     79     private static final long DEFAULT_REENABLE_DELAY_MS = 500;
     80 
     81     // finding that delayed messages can sometimes be delivered earlier than expected
     82     // probably rounding errors..  add a margin to prevent problems
     83     private static final long DEFER_MARGIN_MS = 5;
     84 
     85     NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
     86 
     87     private static final String ACTION_DEVICE_IDLE =
     88             "com.android.server.WifiManager.action.DEVICE_IDLE";
     89 
     90     /* References to values tracked in WifiService */
     91     final WifiStateMachine mWifiStateMachine;
     92     final WifiSettingsStore mSettingsStore;
     93     final LockList mLocks;
     94 
     95     /**
     96      * Temporary for computing UIDS that are responsible for starting WIFI.
     97      * Protected by mWifiStateTracker lock.
     98      */
     99     private final WorkSource mTmpWorkSource = new WorkSource();
    100 
    101     private long mReEnableDelayMillis;
    102 
    103     private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
    104 
    105     static final int CMD_EMERGENCY_MODE_CHANGED     = BASE + 1;
    106     static final int CMD_SCREEN_ON                  = BASE + 2;
    107     static final int CMD_SCREEN_OFF                 = BASE + 3;
    108     static final int CMD_BATTERY_CHANGED            = BASE + 4;
    109     static final int CMD_DEVICE_IDLE                = BASE + 5;
    110     static final int CMD_LOCKS_CHANGED              = BASE + 6;
    111     static final int CMD_SCAN_ALWAYS_MODE_CHANGED   = BASE + 7;
    112     static final int CMD_WIFI_TOGGLED               = BASE + 8;
    113     static final int CMD_AIRPLANE_TOGGLED           = BASE + 9;
    114     static final int CMD_SET_AP                     = BASE + 10;
    115     static final int CMD_DEFERRED_TOGGLE            = BASE + 11;
    116 
    117     private DefaultState mDefaultState = new DefaultState();
    118     private StaEnabledState mStaEnabledState = new StaEnabledState();
    119     private ApStaDisabledState mApStaDisabledState = new ApStaDisabledState();
    120     private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState();
    121     private ApEnabledState mApEnabledState = new ApEnabledState();
    122     private DeviceActiveState mDeviceActiveState = new DeviceActiveState();
    123     private DeviceInactiveState mDeviceInactiveState = new DeviceInactiveState();
    124     private ScanOnlyLockHeldState mScanOnlyLockHeldState = new ScanOnlyLockHeldState();
    125     private FullLockHeldState mFullLockHeldState = new FullLockHeldState();
    126     private FullHighPerfLockHeldState mFullHighPerfLockHeldState = new FullHighPerfLockHeldState();
    127     private NoLockHeldState mNoLockHeldState = new NoLockHeldState();
    128     private EcmState mEcmState = new EcmState();
    129 
    130     WifiController(Context context, WifiService service, Looper looper) {
    131         super(TAG, looper);
    132         mContext = context;
    133         mWifiStateMachine = service.mWifiStateMachine;
    134         mSettingsStore = service.mSettingsStore;
    135         mLocks = service.mLocks;
    136 
    137         mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
    138         Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
    139         mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
    140 
    141         addState(mDefaultState);
    142             addState(mApStaDisabledState, mDefaultState);
    143             addState(mStaEnabledState, mDefaultState);
    144                 addState(mDeviceActiveState, mStaEnabledState);
    145                 addState(mDeviceInactiveState, mStaEnabledState);
    146                     addState(mScanOnlyLockHeldState, mDeviceInactiveState);
    147                     addState(mFullLockHeldState, mDeviceInactiveState);
    148                     addState(mFullHighPerfLockHeldState, mDeviceInactiveState);
    149                     addState(mNoLockHeldState, mDeviceInactiveState);
    150             addState(mStaDisabledWithScanState, mDefaultState);
    151             addState(mApEnabledState, mDefaultState);
    152             addState(mEcmState, mDefaultState);
    153         if (mSettingsStore.isScanAlwaysAvailable()) {
    154             setInitialState(mStaDisabledWithScanState);
    155         } else {
    156             setInitialState(mApStaDisabledState);
    157         }
    158         setLogRecSize(100);
    159         setLogOnlyTransitions(false);
    160 
    161         IntentFilter filter = new IntentFilter();
    162         filter.addAction(ACTION_DEVICE_IDLE);
    163         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    164         mContext.registerReceiver(
    165                 new BroadcastReceiver() {
    166                     @Override
    167                     public void onReceive(Context context, Intent intent) {
    168                         String action = intent.getAction();
    169                         if (action.equals(ACTION_DEVICE_IDLE)) {
    170                             sendMessage(CMD_DEVICE_IDLE);
    171                         } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
    172                             mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
    173                                     WifiManager.EXTRA_NETWORK_INFO);
    174                         }
    175                     }
    176                 },
    177                 new IntentFilter(filter));
    178 
    179         initializeAndRegisterForSettingsChange(looper);
    180     }
    181 
    182     private void initializeAndRegisterForSettingsChange(Looper looper) {
    183         Handler handler = new Handler(looper);
    184         readStayAwakeConditions();
    185         registerForStayAwakeModeChange(handler);
    186         readWifiIdleTime();
    187         registerForWifiIdleTimeChange(handler);
    188         readWifiSleepPolicy();
    189         registerForWifiSleepPolicyChange(handler);
    190         readWifiReEnableDelay();
    191     }
    192 
    193     private void readStayAwakeConditions() {
    194         mStayAwakeConditions = Settings.Global.getInt(mContext.getContentResolver(),
    195                 Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
    196     }
    197 
    198     private void readWifiIdleTime() {
    199         mIdleMillis = Settings.Global.getLong(mContext.getContentResolver(),
    200                 Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
    201     }
    202 
    203     private void readWifiSleepPolicy() {
    204         mSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(),
    205                 Settings.Global.WIFI_SLEEP_POLICY,
    206                 Settings.Global.WIFI_SLEEP_POLICY_NEVER);
    207     }
    208 
    209     private void readWifiReEnableDelay() {
    210         mReEnableDelayMillis = Settings.Global.getLong(mContext.getContentResolver(),
    211                 Settings.Global.WIFI_REENABLE_DELAY_MS, DEFAULT_REENABLE_DELAY_MS);
    212     }
    213 
    214     /**
    215      * Observes settings changes to scan always mode.
    216      */
    217     private void registerForStayAwakeModeChange(Handler handler) {
    218         ContentObserver contentObserver = new ContentObserver(handler) {
    219             @Override
    220             public void onChange(boolean selfChange) {
    221                 readStayAwakeConditions();
    222             }
    223         };
    224 
    225         mContext.getContentResolver().registerContentObserver(
    226                 Settings.Global.getUriFor(Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
    227                 false, contentObserver);
    228     }
    229 
    230     /**
    231      * Observes settings changes to scan always mode.
    232      */
    233     private void registerForWifiIdleTimeChange(Handler handler) {
    234         ContentObserver contentObserver = new ContentObserver(handler) {
    235             @Override
    236             public void onChange(boolean selfChange) {
    237                 readWifiIdleTime();
    238             }
    239         };
    240 
    241         mContext.getContentResolver().registerContentObserver(
    242                 Settings.Global.getUriFor(Settings.Global.WIFI_IDLE_MS),
    243                 false, contentObserver);
    244     }
    245 
    246     /**
    247      * Observes changes to wifi sleep policy
    248      */
    249     private void registerForWifiSleepPolicyChange(Handler handler) {
    250         ContentObserver contentObserver = new ContentObserver(handler) {
    251             @Override
    252             public void onChange(boolean selfChange) {
    253                 readWifiSleepPolicy();
    254             }
    255         };
    256         mContext.getContentResolver().registerContentObserver(
    257                 Settings.Global.getUriFor(Settings.Global.WIFI_SLEEP_POLICY),
    258                 false, contentObserver);
    259     }
    260 
    261     /**
    262      * Determines whether the Wi-Fi chipset should stay awake or be put to
    263      * sleep. Looks at the setting for the sleep policy and the current
    264      * conditions.
    265      *
    266      * @see #shouldDeviceStayAwake(int)
    267      */
    268     private boolean shouldWifiStayAwake(int pluggedType) {
    269         if (mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
    270             // Never sleep
    271             return true;
    272         } else if ((mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
    273                 (pluggedType != 0)) {
    274             // Never sleep while plugged, and we're plugged
    275             return true;
    276         } else {
    277             // Default
    278             return shouldDeviceStayAwake(pluggedType);
    279         }
    280     }
    281 
    282     /**
    283      * Determine whether the bit value corresponding to {@code pluggedType} is set in
    284      * the bit string mStayAwakeConditions. This determines whether the device should
    285      * stay awake based on the current plugged type.
    286      *
    287      * @param pluggedType the type of plug (USB, AC, or none) for which the check is
    288      * being made
    289      * @return {@code true} if {@code pluggedType} indicates that the device is
    290      * supposed to stay awake, {@code false} otherwise.
    291      */
    292     private boolean shouldDeviceStayAwake(int pluggedType) {
    293         return (mStayAwakeConditions & pluggedType) != 0;
    294     }
    295 
    296     private void updateBatteryWorkSource() {
    297         mTmpWorkSource.clear();
    298         if (mDeviceIdle) {
    299             mLocks.updateWorkSource(mTmpWorkSource);
    300         }
    301         mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
    302     }
    303 
    304     class DefaultState extends State {
    305         @Override
    306         public boolean processMessage(Message msg) {
    307             switch (msg.what) {
    308                 case CMD_SCREEN_ON:
    309                     mAlarmManager.cancel(mIdleIntent);
    310                     mScreenOff = false;
    311                     mDeviceIdle = false;
    312                     updateBatteryWorkSource();
    313                     break;
    314                 case CMD_SCREEN_OFF:
    315                     mScreenOff = true;
    316                     /*
    317                     * Set a timer to put Wi-Fi to sleep, but only if the screen is off
    318                     * AND the "stay on while plugged in" setting doesn't match the
    319                     * current power conditions (i.e, not plugged in, plugged in to USB,
    320                     * or plugged in to AC).
    321                     */
    322                     if (!shouldWifiStayAwake(mPluggedType)) {
    323                         //Delayed shutdown if wifi is connected
    324                         if (mNetworkInfo.getDetailedState() ==
    325                                 NetworkInfo.DetailedState.CONNECTED) {
    326                             if (DBG) Slog.d(TAG, "set idle timer: " + mIdleMillis + " ms");
    327                             mAlarmManager.set(AlarmManager.RTC_WAKEUP,
    328                                     System.currentTimeMillis() + mIdleMillis, mIdleIntent);
    329                         } else {
    330                             sendMessage(CMD_DEVICE_IDLE);
    331                         }
    332                     }
    333                     break;
    334                 case CMD_DEVICE_IDLE:
    335                     mDeviceIdle = true;
    336                     updateBatteryWorkSource();
    337                     break;
    338                 case CMD_BATTERY_CHANGED:
    339                     /*
    340                     * Set a timer to put Wi-Fi to sleep, but only if the screen is off
    341                     * AND we are transitioning from a state in which the device was supposed
    342                     * to stay awake to a state in which it is not supposed to stay awake.
    343                     * If "stay awake" state is not changing, we do nothing, to avoid resetting
    344                     * the already-set timer.
    345                     */
    346                     int pluggedType = msg.arg1;
    347                     if (DBG) Slog.d(TAG, "battery changed pluggedType: " + pluggedType);
    348                     if (mScreenOff && shouldWifiStayAwake(mPluggedType) &&
    349                             !shouldWifiStayAwake(pluggedType)) {
    350                         long triggerTime = System.currentTimeMillis() + mIdleMillis;
    351                         if (DBG) Slog.d(TAG, "set idle timer for " + mIdleMillis + "ms");
    352                         mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
    353                     }
    354 
    355                     mPluggedType = pluggedType;
    356                     break;
    357                 case CMD_SET_AP:
    358                 case CMD_SCAN_ALWAYS_MODE_CHANGED:
    359                 case CMD_LOCKS_CHANGED:
    360                 case CMD_WIFI_TOGGLED:
    361                 case CMD_AIRPLANE_TOGGLED:
    362                 case CMD_EMERGENCY_MODE_CHANGED:
    363                     break;
    364                 case CMD_DEFERRED_TOGGLE:
    365                     log("DEFERRED_TOGGLE ignored due to state change");
    366                     break;
    367                 default:
    368                     throw new RuntimeException("WifiController.handleMessage " + msg.what);
    369             }
    370             return HANDLED;
    371         }
    372 
    373     }
    374 
    375     class ApStaDisabledState extends State {
    376         private int mDeferredEnableSerialNumber = 0;
    377         private boolean mHaveDeferredEnable = false;
    378         private long mDisabledTimestamp;
    379 
    380         @Override
    381         public void enter() {
    382             mWifiStateMachine.setSupplicantRunning(false);
    383             // Supplicant can't restart right away, so not the time we switched off
    384             mDisabledTimestamp = SystemClock.elapsedRealtime();
    385             mDeferredEnableSerialNumber++;
    386             mHaveDeferredEnable = false;
    387         }
    388         @Override
    389         public boolean processMessage(Message msg) {
    390             switch (msg.what) {
    391                 case CMD_WIFI_TOGGLED:
    392                 case CMD_AIRPLANE_TOGGLED:
    393                     if (mSettingsStore.isWifiToggleEnabled()) {
    394                         if (doDeferEnable(msg)) {
    395                             if (mHaveDeferredEnable) {
    396                                 //  have 2 toggles now, inc serial number an ignore both
    397                                 mDeferredEnableSerialNumber++;
    398                             }
    399                             mHaveDeferredEnable = !mHaveDeferredEnable;
    400                             break;
    401                         }
    402                         if (mDeviceIdle == false) {
    403                             transitionTo(mDeviceActiveState);
    404                         } else {
    405                             checkLocksAndTransitionWhenDeviceIdle();
    406                         }
    407                     }
    408                     break;
    409                 case CMD_SCAN_ALWAYS_MODE_CHANGED:
    410                     if (mSettingsStore.isScanAlwaysAvailable()) {
    411                         transitionTo(mStaDisabledWithScanState);
    412                     }
    413                     break;
    414                 case CMD_SET_AP:
    415                     if (msg.arg1 == 1) {
    416                         mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
    417                                 true);
    418                         transitionTo(mApEnabledState);
    419                     }
    420                     break;
    421                 case CMD_DEFERRED_TOGGLE:
    422                     if (msg.arg1 != mDeferredEnableSerialNumber) {
    423                         log("DEFERRED_TOGGLE ignored due to serial mismatch");
    424                         break;
    425                     }
    426                     log("DEFERRED_TOGGLE handled");
    427                     sendMessage((Message)(msg.obj));
    428                     break;
    429                 default:
    430                     return NOT_HANDLED;
    431             }
    432             return HANDLED;
    433         }
    434 
    435         private boolean doDeferEnable(Message msg) {
    436             long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
    437             if (delaySoFar >= mReEnableDelayMillis) {
    438                 return false;
    439             }
    440 
    441             log("WifiController msg " + msg + " deferred for " +
    442                     (mReEnableDelayMillis - delaySoFar) + "ms");
    443 
    444             // need to defer this action.
    445             Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
    446             deferredMsg.obj = Message.obtain(msg);
    447             deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
    448             sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
    449             return true;
    450         }
    451 
    452     }
    453 
    454     class StaEnabledState extends State {
    455         @Override
    456         public void enter() {
    457             mWifiStateMachine.setSupplicantRunning(true);
    458         }
    459         @Override
    460         public boolean processMessage(Message msg) {
    461             switch (msg.what) {
    462                 case CMD_WIFI_TOGGLED:
    463                     if (! mSettingsStore.isWifiToggleEnabled()) {
    464                         if (mSettingsStore.isScanAlwaysAvailable()) {
    465                             transitionTo(mStaDisabledWithScanState);
    466                         } else {
    467                             transitionTo(mApStaDisabledState);
    468                         }
    469                     }
    470                     break;
    471                 case CMD_AIRPLANE_TOGGLED:
    472                     /* When wi-fi is turned off due to airplane,
    473                     * disable entirely (including scan)
    474                     */
    475                     if (! mSettingsStore.isWifiToggleEnabled()) {
    476                         transitionTo(mApStaDisabledState);
    477                     }
    478                     break;
    479                 case CMD_EMERGENCY_MODE_CHANGED:
    480                     if (msg.arg1 == 1) {
    481                         transitionTo(mEcmState);
    482                         break;
    483                     }
    484                 default:
    485                     return NOT_HANDLED;
    486 
    487             }
    488             return HANDLED;
    489         }
    490     }
    491 
    492     class StaDisabledWithScanState extends State {
    493         private int mDeferredEnableSerialNumber = 0;
    494         private boolean mHaveDeferredEnable = false;
    495         private long mDisabledTimestamp;
    496 
    497         @Override
    498         public void enter() {
    499             mWifiStateMachine.setSupplicantRunning(true);
    500             mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
    501             mWifiStateMachine.setDriverStart(true);
    502             // Supplicant can't restart right away, so not the time we switched off
    503             mDisabledTimestamp = SystemClock.elapsedRealtime();
    504             mDeferredEnableSerialNumber++;
    505             mHaveDeferredEnable = false;
    506         }
    507 
    508         @Override
    509         public boolean processMessage(Message msg) {
    510             switch (msg.what) {
    511                 case CMD_WIFI_TOGGLED:
    512                     if (mSettingsStore.isWifiToggleEnabled()) {
    513                         if (doDeferEnable(msg)) {
    514                             if (mHaveDeferredEnable) {
    515                                 // have 2 toggles now, inc serial number and ignore both
    516                                 mDeferredEnableSerialNumber++;
    517                             }
    518                             mHaveDeferredEnable = !mHaveDeferredEnable;
    519                             break;
    520                         }
    521                         if (mDeviceIdle == false) {
    522                             transitionTo(mDeviceActiveState);
    523                         } else {
    524                             checkLocksAndTransitionWhenDeviceIdle();
    525                         }
    526                     }
    527                     break;
    528                 case CMD_AIRPLANE_TOGGLED:
    529                     if (mSettingsStore.isAirplaneModeOn() &&
    530                             ! mSettingsStore.isWifiToggleEnabled()) {
    531                         transitionTo(mApStaDisabledState);
    532                     }
    533                 case CMD_SCAN_ALWAYS_MODE_CHANGED:
    534                     if (! mSettingsStore.isScanAlwaysAvailable()) {
    535                         transitionTo(mApStaDisabledState);
    536                     }
    537                     break;
    538                 case CMD_SET_AP:
    539                     // Before starting tethering, turn off supplicant for scan mode
    540                     if (msg.arg1 == 1) {
    541                         deferMessage(msg);
    542                         transitionTo(mApStaDisabledState);
    543                     }
    544                     break;
    545                 case CMD_DEFERRED_TOGGLE:
    546                     if (msg.arg1 != mDeferredEnableSerialNumber) {
    547                         log("DEFERRED_TOGGLE ignored due to serial mismatch");
    548                         break;
    549                     }
    550                     logd("DEFERRED_TOGGLE handled");
    551                     sendMessage((Message)(msg.obj));
    552                     break;
    553                 default:
    554                     return NOT_HANDLED;
    555             }
    556             return HANDLED;
    557         }
    558 
    559         private boolean doDeferEnable(Message msg) {
    560             long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
    561             if (delaySoFar >= mReEnableDelayMillis) {
    562                 return false;
    563             }
    564 
    565             log("WifiController msg " + msg + " deferred for " +
    566                     (mReEnableDelayMillis - delaySoFar) + "ms");
    567 
    568             // need to defer this action.
    569             Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
    570             deferredMsg.obj = Message.obtain(msg);
    571             deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
    572             sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
    573             return true;
    574         }
    575 
    576     }
    577 
    578     class ApEnabledState extends State {
    579         @Override
    580         public boolean processMessage(Message msg) {
    581             switch (msg.what) {
    582                 case CMD_AIRPLANE_TOGGLED:
    583                     if (mSettingsStore.isAirplaneModeOn()) {
    584                         mWifiStateMachine.setHostApRunning(null, false);
    585                         transitionTo(mApStaDisabledState);
    586                     }
    587                     break;
    588                 case CMD_SET_AP:
    589                     if (msg.arg1 == 0) {
    590                         mWifiStateMachine.setHostApRunning(null, false);
    591                         transitionTo(mApStaDisabledState);
    592                     }
    593                     break;
    594                 default:
    595                     return NOT_HANDLED;
    596             }
    597             return HANDLED;
    598         }
    599     }
    600 
    601     class EcmState extends State {
    602         @Override
    603         public void enter() {
    604             mWifiStateMachine.setSupplicantRunning(false);
    605         }
    606 
    607         @Override
    608         public boolean processMessage(Message msg) {
    609             if (msg.what == CMD_EMERGENCY_MODE_CHANGED && msg.arg1 == 0) {
    610                 if (mSettingsStore.isWifiToggleEnabled()) {
    611                     if (mDeviceIdle == false) {
    612                         transitionTo(mDeviceActiveState);
    613                     } else {
    614                         checkLocksAndTransitionWhenDeviceIdle();
    615                     }
    616                 } else if (mSettingsStore.isScanAlwaysAvailable()) {
    617                     transitionTo(mStaDisabledWithScanState);
    618                 } else {
    619                     transitionTo(mApStaDisabledState);
    620                 }
    621                 return HANDLED;
    622             } else {
    623                 return NOT_HANDLED;
    624             }
    625         }
    626     }
    627 
    628     /* Parent: StaEnabledState */
    629     class DeviceActiveState extends State {
    630         @Override
    631         public void enter() {
    632             mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
    633             mWifiStateMachine.setDriverStart(true);
    634             mWifiStateMachine.setHighPerfModeEnabled(false);
    635         }
    636 
    637         @Override
    638         public boolean processMessage(Message msg) {
    639             if (msg.what == CMD_DEVICE_IDLE) {
    640                 checkLocksAndTransitionWhenDeviceIdle();
    641                 // We let default state handle the rest of work
    642             }
    643             return NOT_HANDLED;
    644         }
    645     }
    646 
    647     /* Parent: StaEnabledState */
    648     class DeviceInactiveState extends State {
    649         @Override
    650         public boolean processMessage(Message msg) {
    651             switch (msg.what) {
    652                 case CMD_LOCKS_CHANGED:
    653                     checkLocksAndTransitionWhenDeviceIdle();
    654                     updateBatteryWorkSource();
    655                     return HANDLED;
    656                 case CMD_SCREEN_ON:
    657                     transitionTo(mDeviceActiveState);
    658                     // More work in default state
    659                     return NOT_HANDLED;
    660                 default:
    661                     return NOT_HANDLED;
    662             }
    663         }
    664     }
    665 
    666     /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a scan only lock. */
    667     class ScanOnlyLockHeldState extends State {
    668         @Override
    669         public void enter() {
    670             mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
    671             mWifiStateMachine.setDriverStart(true);
    672         }
    673     }
    674 
    675     /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a full lock. */
    676     class FullLockHeldState extends State {
    677         @Override
    678         public void enter() {
    679             mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
    680             mWifiStateMachine.setDriverStart(true);
    681             mWifiStateMachine.setHighPerfModeEnabled(false);
    682         }
    683     }
    684 
    685     /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a high perf lock. */
    686     class FullHighPerfLockHeldState extends State {
    687         @Override
    688         public void enter() {
    689             mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
    690             mWifiStateMachine.setDriverStart(true);
    691             mWifiStateMachine.setHighPerfModeEnabled(true);
    692         }
    693     }
    694 
    695     /* Parent: DeviceInactiveState. Device is inactive and no app is holding a wifi lock. */
    696     class NoLockHeldState extends State {
    697         @Override
    698         public void enter() {
    699             mWifiStateMachine.setDriverStart(false);
    700         }
    701     }
    702 
    703     private void checkLocksAndTransitionWhenDeviceIdle() {
    704         if (mLocks.hasLocks()) {
    705             switch (mLocks.getStrongestLockMode()) {
    706                 case WIFI_MODE_FULL:
    707                     transitionTo(mFullLockHeldState);
    708                     break;
    709                 case WIFI_MODE_FULL_HIGH_PERF:
    710                     transitionTo(mFullHighPerfLockHeldState);
    711                     break;
    712                 case WIFI_MODE_SCAN_ONLY:
    713                     transitionTo(mScanOnlyLockHeldState);
    714                     break;
    715                 default:
    716                     loge("Illegal lock " + mLocks.getStrongestLockMode());
    717             }
    718         } else {
    719             if (mSettingsStore.isScanAlwaysAvailable()) {
    720                 transitionTo(mScanOnlyLockHeldState);
    721             } else {
    722                 transitionTo(mNoLockHeldState);
    723             }
    724         }
    725     }
    726 
    727     @Override
    728     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    729         super.dump(fd, pw, args);
    730 
    731         pw.println("mScreenOff " + mScreenOff);
    732         pw.println("mDeviceIdle " + mDeviceIdle);
    733         pw.println("mPluggedType " + mPluggedType);
    734         pw.println("mIdleMillis " + mIdleMillis);
    735         pw.println("mSleepPolicy " + mSleepPolicy);
    736     }
    737 }
    738