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.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.location.LocationManager;
     24 import android.net.ConnectivityManager;
     25 import android.net.NetworkInfo;
     26 import android.net.wifi.WifiManager;
     27 import android.os.Handler;
     28 import android.os.Looper;
     29 import android.os.Message;
     30 import android.os.SystemClock;
     31 import android.os.WorkSource;
     32 import android.provider.Settings;
     33 import android.util.Log;
     34 
     35 import com.android.internal.util.Protocol;
     36 import com.android.internal.util.State;
     37 import com.android.internal.util.StateMachine;
     38 
     39 /**
     40  * WifiController is the class used to manage on/off state of WifiStateMachine for various operating
     41  * modes (normal, airplane, wifi hotspot, etc.).
     42  */
     43 public class WifiController extends StateMachine {
     44     private static final String TAG = "WifiController";
     45     private static final boolean DBG = false;
     46     private Context mContext;
     47     private boolean mFirstUserSignOnSeen = false;
     48 
     49     /**
     50      * See {@link Settings.Global#WIFI_REENABLE_DELAY_MS}.  This is the default value if a
     51      * Settings.Global value is not present.  This is the minimum time after wifi is disabled
     52      * we'll act on an enable.  Enable requests received before this delay will be deferred.
     53      */
     54     private static final long DEFAULT_REENABLE_DELAY_MS = 500;
     55 
     56     // finding that delayed messages can sometimes be delivered earlier than expected
     57     // probably rounding errors.  add a margin to prevent problems
     58     private static final long DEFER_MARGIN_MS = 5;
     59 
     60     NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
     61 
     62     /* References to values tracked in WifiService */
     63     private final WifiStateMachine mWifiStateMachine;
     64     private final Looper mWifiStateMachineLooper;
     65     private final WifiStateMachinePrime mWifiStateMachinePrime;
     66     private final WifiSettingsStore mSettingsStore;
     67 
     68     /**
     69      * Temporary for computing UIDS that are responsible for starting WIFI.
     70      * Protected by mWifiStateTracker lock.
     71      */
     72     private final WorkSource mTmpWorkSource = new WorkSource();
     73 
     74     private long mReEnableDelayMillis;
     75 
     76     private FrameworkFacade mFacade;
     77 
     78     private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
     79 
     80     static final int CMD_EMERGENCY_MODE_CHANGED                 = BASE + 1;
     81     static final int CMD_SCAN_ALWAYS_MODE_CHANGED               = BASE + 7;
     82     static final int CMD_WIFI_TOGGLED                           = BASE + 8;
     83     static final int CMD_AIRPLANE_TOGGLED                       = BASE + 9;
     84     static final int CMD_SET_AP                                 = BASE + 10;
     85     static final int CMD_DEFERRED_TOGGLE                        = BASE + 11;
     86     static final int CMD_USER_PRESENT                           = BASE + 12;
     87     static final int CMD_AP_START_FAILURE                       = BASE + 13;
     88     static final int CMD_EMERGENCY_CALL_STATE_CHANGED           = BASE + 14;
     89     static final int CMD_AP_STOPPED                             = BASE + 15;
     90     static final int CMD_STA_START_FAILURE                      = BASE + 16;
     91     // Command used to trigger a wifi stack restart when in active mode
     92     static final int CMD_RECOVERY_RESTART_WIFI                  = BASE + 17;
     93     // Internal command used to complete wifi stack restart
     94     private static final int CMD_RECOVERY_RESTART_WIFI_CONTINUE = BASE + 18;
     95     // Command to disable wifi when SelfRecovery is throttled or otherwise not doing full recovery
     96     static final int CMD_RECOVERY_DISABLE_WIFI                  = BASE + 19;
     97     static final int CMD_STA_STOPPED                            = BASE + 20;
     98     static final int CMD_SCANNING_STOPPED                       = BASE + 21;
     99 
    100     private DefaultState mDefaultState = new DefaultState();
    101     private StaEnabledState mStaEnabledState = new StaEnabledState();
    102     private StaDisabledState mStaDisabledState = new StaDisabledState();
    103     private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState();
    104     private DeviceActiveState mDeviceActiveState = new DeviceActiveState();
    105     private EcmState mEcmState = new EcmState();
    106 
    107     private ScanOnlyModeManager.Listener mScanOnlyModeCallback = new ScanOnlyCallback();
    108     private ClientModeManager.Listener mClientModeCallback = new ClientModeCallback();
    109 
    110     WifiController(Context context, WifiStateMachine wsm, Looper wifiStateMachineLooper,
    111                    WifiSettingsStore wss, Looper wifiServiceLooper, FrameworkFacade f,
    112                    WifiStateMachinePrime wsmp) {
    113         super(TAG, wifiServiceLooper);
    114         mFacade = f;
    115         mContext = context;
    116         mWifiStateMachine = wsm;
    117         mWifiStateMachineLooper = wifiStateMachineLooper;
    118         mWifiStateMachinePrime = wsmp;
    119         mSettingsStore = wss;
    120 
    121         // CHECKSTYLE:OFF IndentationCheck
    122         addState(mDefaultState);
    123             addState(mStaDisabledState, mDefaultState);
    124             addState(mStaEnabledState, mDefaultState);
    125                 addState(mDeviceActiveState, mStaEnabledState);
    126             addState(mStaDisabledWithScanState, mDefaultState);
    127             addState(mEcmState, mDefaultState);
    128         // CHECKSTYLE:ON IndentationCheck
    129 
    130         boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();
    131         boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();
    132         boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable();
    133         boolean isLocationModeActive =
    134                 mSettingsStore.getLocationModeSetting(mContext)
    135                         == Settings.Secure.LOCATION_MODE_OFF;
    136 
    137         log("isAirplaneModeOn = " + isAirplaneModeOn
    138                 + ", isWifiEnabled = " + isWifiEnabled
    139                 + ", isScanningAvailable = " + isScanningAlwaysAvailable
    140                 + ", isLocationModeActive = " + isLocationModeActive);
    141 
    142         if (checkScanOnlyModeAvailable()) {
    143             setInitialState(mStaDisabledWithScanState);
    144         } else {
    145             setInitialState(mStaDisabledState);
    146         }
    147 
    148         setLogRecSize(100);
    149         setLogOnlyTransitions(false);
    150 
    151         // register for state updates via callbacks (vs the intents registered below)
    152         mWifiStateMachinePrime.registerScanOnlyCallback(mScanOnlyModeCallback);
    153         mWifiStateMachinePrime.registerClientModeCallback(mClientModeCallback);
    154 
    155         IntentFilter filter = new IntentFilter();
    156         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    157         filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
    158         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    159         filter.addAction(LocationManager.MODE_CHANGED_ACTION);
    160         mContext.registerReceiver(
    161                 new BroadcastReceiver() {
    162                     @Override
    163                     public void onReceive(Context context, Intent intent) {
    164                         String action = intent.getAction();
    165                         if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
    166                             mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
    167                                     WifiManager.EXTRA_NETWORK_INFO);
    168                         } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
    169                             int state = intent.getIntExtra(
    170                                     WifiManager.EXTRA_WIFI_AP_STATE,
    171                                     WifiManager.WIFI_AP_STATE_FAILED);
    172                             if (state == WifiManager.WIFI_AP_STATE_FAILED) {
    173                                 Log.e(TAG, "SoftAP start failed");
    174                                 sendMessage(CMD_AP_START_FAILURE);
    175                             } else if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
    176                                 sendMessage(CMD_AP_STOPPED);
    177                             }
    178                         } else if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
    179                             // Location mode has been toggled...  trigger with the scan change
    180                             // update to make sure we are in the correct mode
    181                             sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
    182                         }
    183                     }
    184                 },
    185                 new IntentFilter(filter));
    186 
    187         readWifiReEnableDelay();
    188     }
    189 
    190     private boolean checkScanOnlyModeAvailable() {
    191         // first check if Location service is disabled, if so return false
    192         if (mSettingsStore.getLocationModeSetting(mContext)
    193                 == Settings.Secure.LOCATION_MODE_OFF) {
    194             return false;
    195         }
    196         return mSettingsStore.isScanAlwaysAvailable();
    197     }
    198 
    199     /**
    200      * Listener used to receive scan mode updates - really needed for disabled updates to trigger
    201      * mode changes.
    202      */
    203     private class ScanOnlyCallback implements ScanOnlyModeManager.Listener {
    204         @Override
    205         public void onStateChanged(int state) {
    206             if (state == WifiManager.WIFI_STATE_UNKNOWN) {
    207                 Log.d(TAG, "ScanOnlyMode unexpected failure: state unknown");
    208             } else if (state == WifiManager.WIFI_STATE_DISABLED) {
    209                 Log.d(TAG, "ScanOnlyMode stopped");
    210                 sendMessage(CMD_SCANNING_STOPPED);
    211             } else if (state == WifiManager.WIFI_STATE_ENABLED) {
    212                 // scan mode is ready to go
    213                 Log.d(TAG, "scan mode active");
    214             } else {
    215                 Log.d(TAG, "unexpected state update: " + state);
    216             }
    217         }
    218     }
    219 
    220     /**
    221      * Listener used to receive client mode updates
    222      */
    223     private class ClientModeCallback implements ClientModeManager.Listener {
    224         @Override
    225         public void onStateChanged(int state) {
    226             if (state == WifiManager.WIFI_STATE_UNKNOWN) {
    227                 logd("ClientMode unexpected failure: state unknown");
    228                 sendMessage(CMD_STA_START_FAILURE);
    229             } else if (state == WifiManager.WIFI_STATE_DISABLED) {
    230                 logd("ClientMode stopped");
    231                 sendMessage(CMD_STA_STOPPED);
    232             } else if (state == WifiManager.WIFI_STATE_ENABLED) {
    233                 // scan mode is ready to go
    234                 logd("client mode active");
    235             } else {
    236                 logd("unexpected state update: " + state);
    237             }
    238         }
    239     }
    240 
    241     private void readWifiReEnableDelay() {
    242         mReEnableDelayMillis = mFacade.getLongSetting(mContext,
    243                 Settings.Global.WIFI_REENABLE_DELAY_MS, DEFAULT_REENABLE_DELAY_MS);
    244     }
    245 
    246     private void updateBatteryWorkSource() {
    247         mTmpWorkSource.clear();
    248         mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
    249     }
    250 
    251     class DefaultState extends State {
    252         @Override
    253         public boolean processMessage(Message msg) {
    254             switch (msg.what) {
    255                 case CMD_SCAN_ALWAYS_MODE_CHANGED:
    256                 case CMD_WIFI_TOGGLED:
    257                 case CMD_AP_START_FAILURE:
    258                 case CMD_SCANNING_STOPPED:
    259                 case CMD_STA_STOPPED:
    260                 case CMD_STA_START_FAILURE:
    261                 case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
    262                     break;
    263                 case CMD_RECOVERY_DISABLE_WIFI:
    264                     log("Recovery has been throttled, disable wifi");
    265                     mWifiStateMachinePrime.shutdownWifi();
    266                     transitionTo(mStaDisabledState);
    267                     break;
    268                 case CMD_RECOVERY_RESTART_WIFI:
    269                     deferMessage(obtainMessage(CMD_RECOVERY_RESTART_WIFI_CONTINUE));
    270                     mWifiStateMachinePrime.shutdownWifi();
    271                     transitionTo(mStaDisabledState);
    272                     break;
    273                 case CMD_USER_PRESENT:
    274                     mFirstUserSignOnSeen = true;
    275                     break;
    276                 case CMD_DEFERRED_TOGGLE:
    277                     log("DEFERRED_TOGGLE ignored due to state change");
    278                     break;
    279                 case CMD_SET_AP:
    280                     // note: CMD_SET_AP is handled/dropped in ECM mode - will not start here
    281 
    282                     // first make sure we aren't in airplane mode
    283                     if (mSettingsStore.isAirplaneModeOn()) {
    284                         log("drop softap requests when in airplane mode");
    285                         break;
    286                     }
    287                     if (msg.arg1 == 1) {
    288                         SoftApModeConfiguration config = (SoftApModeConfiguration) msg.obj;
    289                         mWifiStateMachinePrime.enterSoftAPMode((SoftApModeConfiguration) msg.obj);
    290                     } else {
    291                         mWifiStateMachinePrime.stopSoftAPMode();
    292                     }
    293                     break;
    294                 case CMD_AIRPLANE_TOGGLED:
    295                     if (mSettingsStore.isAirplaneModeOn()) {
    296                         log("Airplane mode toggled, shutdown all modes");
    297                         mWifiStateMachinePrime.shutdownWifi();
    298                         transitionTo(mStaDisabledState);
    299                     } else {
    300                         log("Airplane mode disabled, determine next state");
    301                         if (mSettingsStore.isWifiToggleEnabled()) {
    302                             transitionTo(mDeviceActiveState);
    303                         } else if (checkScanOnlyModeAvailable()) {
    304                             transitionTo(mStaDisabledWithScanState);
    305                         }
    306                         // wifi should remain disabled, do not need to transition
    307                     }
    308                     break;
    309                 case CMD_EMERGENCY_CALL_STATE_CHANGED:
    310                 case CMD_EMERGENCY_MODE_CHANGED:
    311                     boolean configWiFiDisableInECBM =
    312                             mFacade.getConfigWiFiDisableInECBM(mContext);
    313                     log("WifiController msg " + msg + " getConfigWiFiDisableInECBM "
    314                             + configWiFiDisableInECBM);
    315                     if ((msg.arg1 == 1) && configWiFiDisableInECBM) {
    316                         transitionTo(mEcmState);
    317                     }
    318                     break;
    319                 case CMD_AP_STOPPED:
    320                     log("SoftAp mode disabled, determine next state");
    321                     if (mSettingsStore.isWifiToggleEnabled()) {
    322                         transitionTo(mDeviceActiveState);
    323                     } else if (checkScanOnlyModeAvailable()) {
    324                         transitionTo(mStaDisabledWithScanState);
    325                     }
    326                     // wifi should remain disabled, do not need to transition
    327                     break;
    328                 default:
    329                     throw new RuntimeException("WifiController.handleMessage " + msg.what);
    330             }
    331             return HANDLED;
    332         }
    333 
    334     }
    335 
    336     class StaDisabledState extends State {
    337         private int mDeferredEnableSerialNumber = 0;
    338         private boolean mHaveDeferredEnable = false;
    339         private long mDisabledTimestamp;
    340 
    341         @Override
    342         public void enter() {
    343             mWifiStateMachinePrime.disableWifi();
    344             // Supplicant can't restart right away, so note the time we switched off
    345             mDisabledTimestamp = SystemClock.elapsedRealtime();
    346             mDeferredEnableSerialNumber++;
    347             mHaveDeferredEnable = false;
    348             mWifiStateMachine.clearANQPCache();
    349         }
    350         @Override
    351         public boolean processMessage(Message msg) {
    352             switch (msg.what) {
    353                 case CMD_WIFI_TOGGLED:
    354                     if (mSettingsStore.isWifiToggleEnabled()) {
    355                         if (doDeferEnable(msg)) {
    356                             if (mHaveDeferredEnable) {
    357                                 //  have 2 toggles now, inc serial number and ignore both
    358                                 mDeferredEnableSerialNumber++;
    359                             }
    360                             mHaveDeferredEnable = !mHaveDeferredEnable;
    361                             break;
    362                         }
    363                         transitionTo(mDeviceActiveState);
    364                     } else if (checkScanOnlyModeAvailable()) {
    365                         // only go to scan mode if we aren't in airplane mode
    366                         if (mSettingsStore.isAirplaneModeOn()) {
    367                             transitionTo(mStaDisabledWithScanState);
    368                         }
    369                     }
    370                     break;
    371                 case CMD_SCAN_ALWAYS_MODE_CHANGED:
    372                     if (checkScanOnlyModeAvailable()) {
    373                         transitionTo(mStaDisabledWithScanState);
    374                         break;
    375                     }
    376                     break;
    377                 case CMD_SET_AP:
    378                     // first make sure we aren't in airplane mode
    379                     if (mSettingsStore.isAirplaneModeOn()) {
    380                         log("drop softap requests when in airplane mode");
    381                         break;
    382                     }
    383                     if (msg.arg1 == 1) {
    384                         // remember that we were disabled, but pass the command up to start softap
    385                         mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
    386                     }
    387                     return NOT_HANDLED;
    388                 case CMD_DEFERRED_TOGGLE:
    389                     if (msg.arg1 != mDeferredEnableSerialNumber) {
    390                         log("DEFERRED_TOGGLE ignored due to serial mismatch");
    391                         break;
    392                     }
    393                     log("DEFERRED_TOGGLE handled");
    394                     sendMessage((Message)(msg.obj));
    395                     break;
    396                 case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
    397                     if (mSettingsStore.isWifiToggleEnabled()) {
    398                         // wifi is currently disabled but the toggle is on, must have had an
    399                         // interface down before the recovery triggered
    400                         transitionTo(mDeviceActiveState);
    401                         break;
    402                     } else if (checkScanOnlyModeAvailable()) {
    403                         transitionTo(mStaDisabledWithScanState);
    404                         break;
    405                     }
    406                     break;
    407                 default:
    408                     return NOT_HANDLED;
    409             }
    410             return HANDLED;
    411         }
    412 
    413         private boolean doDeferEnable(Message msg) {
    414             long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
    415             if (delaySoFar >= mReEnableDelayMillis) {
    416                 return false;
    417             }
    418 
    419             log("WifiController msg " + msg + " deferred for " +
    420                     (mReEnableDelayMillis - delaySoFar) + "ms");
    421 
    422             // need to defer this action.
    423             Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
    424             deferredMsg.obj = Message.obtain(msg);
    425             deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
    426             sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
    427             return true;
    428         }
    429 
    430     }
    431 
    432     class StaEnabledState extends State {
    433         @Override
    434         public void enter() {
    435             log("StaEnabledState.enter()");
    436         }
    437 
    438         @Override
    439         public boolean processMessage(Message msg) {
    440             switch (msg.what) {
    441                 case CMD_WIFI_TOGGLED:
    442                     if (! mSettingsStore.isWifiToggleEnabled()) {
    443                         if (checkScanOnlyModeAvailable()) {
    444                             transitionTo(mStaDisabledWithScanState);
    445                         } else {
    446                             transitionTo(mStaDisabledState);
    447                         }
    448                     }
    449                     break;
    450                 case CMD_AIRPLANE_TOGGLED:
    451                     // airplane mode toggled on is handled in the default state
    452                     if (mSettingsStore.isAirplaneModeOn()) {
    453                         return NOT_HANDLED;
    454                     } else {
    455                         // when airplane mode is toggled off, but wifi is on, we can keep it on
    456                         log("airplane mode toggled - and airplane mode is off.  return handled");
    457                         return HANDLED;
    458                     }
    459                 case CMD_STA_START_FAILURE:
    460                     if (!checkScanOnlyModeAvailable()) {
    461                         transitionTo(mStaDisabledState);
    462                     } else {
    463                         transitionTo(mStaDisabledWithScanState);
    464                     }
    465                     break;
    466                 case CMD_SET_AP:
    467                     if (msg.arg1 == 1) {
    468                         // remember that we were enabled, but pass the command up to start softap
    469                         mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_ENABLED);
    470                     }
    471                     return NOT_HANDLED;
    472                 case CMD_AP_START_FAILURE:
    473                 case CMD_AP_STOPPED:
    474                     // already in a wifi mode, no need to check where we should go with softap
    475                     // stopped
    476                     break;
    477                 case CMD_STA_STOPPED:
    478                     // Client mode stopped.  head to Disabled to wait for next command
    479                     transitionTo(mStaDisabledState);
    480                     break;
    481                 default:
    482                     return NOT_HANDLED;
    483 
    484             }
    485             return HANDLED;
    486         }
    487     }
    488 
    489     class StaDisabledWithScanState extends State {
    490         private int mDeferredEnableSerialNumber = 0;
    491         private boolean mHaveDeferredEnable = false;
    492         private long mDisabledTimestamp;
    493 
    494         @Override
    495         public void enter() {
    496             // now trigger the actual mode switch in WifiStateMachinePrime
    497             mWifiStateMachinePrime.enterScanOnlyMode();
    498 
    499             // TODO b/71559473: remove the defered enable after mode management changes are complete
    500             // Supplicant can't restart right away, so not the time we switched off
    501             mDisabledTimestamp = SystemClock.elapsedRealtime();
    502             mDeferredEnableSerialNumber++;
    503             mHaveDeferredEnable = false;
    504         }
    505 
    506         @Override
    507         public boolean processMessage(Message msg) {
    508             switch (msg.what) {
    509                 case CMD_WIFI_TOGGLED:
    510                     if (mSettingsStore.isWifiToggleEnabled()) {
    511                         if (doDeferEnable(msg)) {
    512                             if (mHaveDeferredEnable) {
    513                                 // have 2 toggles now, inc serial number and ignore both
    514                                 mDeferredEnableSerialNumber++;
    515                             }
    516                             mHaveDeferredEnable = !mHaveDeferredEnable;
    517                             break;
    518                         }
    519                         transitionTo(mDeviceActiveState);
    520                     }
    521                     break;
    522                 case CMD_SCAN_ALWAYS_MODE_CHANGED:
    523                     if (!checkScanOnlyModeAvailable()) {
    524                         log("StaDisabledWithScanState: scan no longer available");
    525                         transitionTo(mStaDisabledState);
    526                     }
    527                     break;
    528                 case CMD_SET_AP:
    529                     if (msg.arg1 == 1) {
    530                         // remember that we were disabled, but pass the command up to start softap
    531                         mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
    532                     }
    533                     return NOT_HANDLED;
    534                 case CMD_DEFERRED_TOGGLE:
    535                     if (msg.arg1 != mDeferredEnableSerialNumber) {
    536                         log("DEFERRED_TOGGLE ignored due to serial mismatch");
    537                         break;
    538                     }
    539                     logd("DEFERRED_TOGGLE handled");
    540                     sendMessage((Message)(msg.obj));
    541                     break;
    542                 case CMD_AP_START_FAILURE:
    543                 case CMD_AP_STOPPED:
    544                     // already in a wifi mode, no need to check where we should go with softap
    545                     // stopped
    546                     break;
    547                 case CMD_SCANNING_STOPPED:
    548                     // stopped due to interface destruction - return to disabled and wait
    549                     log("WifiController: SCANNING_STOPPED when in scan mode -> StaDisabled");
    550                     transitionTo(mStaDisabledState);
    551                     break;
    552                 default:
    553                     return NOT_HANDLED;
    554             }
    555             return HANDLED;
    556         }
    557 
    558         private boolean doDeferEnable(Message msg) {
    559             long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
    560             if (delaySoFar >= mReEnableDelayMillis) {
    561                 return false;
    562             }
    563 
    564             log("WifiController msg " + msg + " deferred for " +
    565                     (mReEnableDelayMillis - delaySoFar) + "ms");
    566 
    567             // need to defer this action.
    568             Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
    569             deferredMsg.obj = Message.obtain(msg);
    570             deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
    571             sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
    572             return true;
    573         }
    574 
    575     }
    576 
    577     /**
    578      * Determine the next state based on the current settings (e.g. saved
    579      * wifi state).
    580      */
    581     private State getNextWifiState() {
    582         if (mSettingsStore.getWifiSavedState() == WifiSettingsStore.WIFI_ENABLED) {
    583             return mDeviceActiveState;
    584         }
    585 
    586         if (checkScanOnlyModeAvailable()) {
    587             return mStaDisabledWithScanState;
    588         }
    589 
    590         return mStaDisabledState;
    591     }
    592 
    593     class EcmState extends State {
    594         // we can enter EcmState either because an emergency call started or because
    595         // emergency callback mode started. This count keeps track of how many such
    596         // events happened; so we can exit after all are undone
    597 
    598         private int mEcmEntryCount;
    599         @Override
    600         public void enter() {
    601             mWifiStateMachinePrime.shutdownWifi();
    602             mWifiStateMachine.clearANQPCache();
    603             mEcmEntryCount = 1;
    604         }
    605 
    606         /**
    607          * Hanles messages received while in EcmMode.
    608          *
    609          * TODO (b/78244565): move from many ifs to a switch
    610          */
    611         @Override
    612         public boolean processMessage(Message msg) {
    613             if (msg.what == CMD_EMERGENCY_CALL_STATE_CHANGED) {
    614                 if (msg.arg1 == 1) {
    615                     // nothing to do - just says emergency call started
    616                     mEcmEntryCount++;
    617                 } else if (msg.arg1 == 0) {
    618                     // emergency call ended
    619                     decrementCountAndReturnToAppropriateState();
    620                 }
    621                 return HANDLED;
    622             } else if (msg.what == CMD_EMERGENCY_MODE_CHANGED) {
    623 
    624                 if (msg.arg1 == 1) {
    625                     // Transitioned into emergency callback mode
    626                     mEcmEntryCount++;
    627                 } else if (msg.arg1 == 0) {
    628                     // out of emergency callback mode
    629                     decrementCountAndReturnToAppropriateState();
    630                 }
    631                 return HANDLED;
    632             } else if (msg.what == CMD_RECOVERY_RESTART_WIFI
    633                     || msg.what == CMD_RECOVERY_DISABLE_WIFI) {
    634                 // do not want to restart wifi if we are in emergency mode
    635                 return HANDLED;
    636             } else if (msg.what == CMD_AP_STOPPED || msg.what == CMD_SCANNING_STOPPED
    637                     || msg.what == CMD_STA_STOPPED) {
    638                 // do not want to trigger a mode switch if we are in emergency mode
    639                 return HANDLED;
    640             } else if (msg.what == CMD_SET_AP) {
    641                 // do not want to start softap if we are in emergency mode
    642                 return HANDLED;
    643             } else {
    644                 return NOT_HANDLED;
    645             }
    646         }
    647 
    648         private void decrementCountAndReturnToAppropriateState() {
    649             boolean exitEcm = false;
    650 
    651             if (mEcmEntryCount == 0) {
    652                 loge("mEcmEntryCount is 0; exiting Ecm");
    653                 exitEcm = true;
    654             } else if (--mEcmEntryCount == 0) {
    655                 exitEcm = true;
    656             }
    657 
    658             if (exitEcm) {
    659                 if (mSettingsStore.isWifiToggleEnabled()) {
    660                     transitionTo(mDeviceActiveState);
    661                 } else if (checkScanOnlyModeAvailable()) {
    662                     transitionTo(mStaDisabledWithScanState);
    663                 } else {
    664                     transitionTo(mStaDisabledState);
    665                 }
    666             }
    667         }
    668     }
    669 
    670     /**
    671      * Parent: StaEnabledState
    672      *
    673      * TODO (b/79209870): merge DeviceActiveState and StaEnabledState into a single state
    674      */
    675     class DeviceActiveState extends State {
    676         @Override
    677         public void enter() {
    678             mWifiStateMachinePrime.enterClientMode();
    679             mWifiStateMachine.setHighPerfModeEnabled(false);
    680         }
    681 
    682         @Override
    683         public boolean processMessage(Message msg) {
    684             if (msg.what == CMD_USER_PRESENT) {
    685                 // TLS networks can't connect until user unlocks keystore. KeyStore
    686                 // unlocks when the user punches PIN after the reboot. So use this
    687                 // trigger to get those networks connected.
    688                 if (mFirstUserSignOnSeen == false) {
    689                     mWifiStateMachine.reloadTlsNetworksAndReconnect();
    690                 }
    691                 mFirstUserSignOnSeen = true;
    692                 return HANDLED;
    693             } else if (msg.what == CMD_RECOVERY_RESTART_WIFI) {
    694                 final String bugTitle;
    695                 final String bugDetail;
    696                 if (msg.arg1 < SelfRecovery.REASON_STRINGS.length && msg.arg1 >= 0) {
    697                     bugDetail = SelfRecovery.REASON_STRINGS[msg.arg1];
    698                     bugTitle = "Wi-Fi BugReport: " + bugDetail;
    699                 } else {
    700                     bugDetail = "";
    701                     bugTitle = "Wi-Fi BugReport";
    702                 }
    703                 if (msg.arg1 != SelfRecovery.REASON_LAST_RESORT_WATCHDOG) {
    704                     (new Handler(mWifiStateMachineLooper)).post(() -> {
    705                         mWifiStateMachine.takeBugReport(bugTitle, bugDetail);
    706                     });
    707                 }
    708                 return NOT_HANDLED;
    709             }
    710             return NOT_HANDLED;
    711         }
    712     }
    713 }
    714