content: /*
   * Copyright (C) 2013 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.wifi;
  
  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.location.LocationManager;
  import android.net.wifi.WifiManager;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Message;
  import android.os.SystemClock;
  import android.provider.Settings;
  import android.util.Log;
  
  import com.android.internal.R;
  import com.android.internal.util.Protocol;
  import com.android.internal.util.State;
  import com.android.internal.util.StateMachine;
  import com.android.server.wifi.util.WifiPermissionsUtil;
  
  /**
   * WifiController is the class used to manage wifi state for various operating
   * modes (normal, airplane, wifi hotspot, etc.).
   */
  public class WifiController extends StateMachine {
      private static final String TAG = "WifiController";
      private static final boolean DBG = false;
      private Context mContext;
      private boolean mFirstUserSignOnSeen = false;
  
      /**
       * See {@link Settings.Global#WIFI_REENABLE_DELAY_MS}.  This is the default value if a
       * Settings.Global value is not present.  This is the minimum time after wifi is disabled
       * we'll act on an enable.  Enable requests received before this delay will be deferred.
       */
      private static final long DEFAULT_REENABLE_DELAY_MS = 500;
  
      // Maximum limit to use for timeout delay if the value from overlay setting is too large.
      private static final int MAX_RECOVERY_TIMEOUT_DELAY_MS = 4000;
  
      // finding that delayed messages can sometimes be delivered earlier than expected
      // probably rounding errors.  add a margin to prevent problems
      private static final long DEFER_MARGIN_MS = 5;
  
      /* References to values tracked in WifiService */
      private final ClientModeImpl mClientModeImpl;
      private final Looper mClientModeImplLooper;
      private final ActiveModeWarden mActiveModeWarden;
      private final WifiSettingsStore mSettingsStore;
      private final FrameworkFacade mFacade;
      private final WifiPermissionsUtil mWifiPermissionsUtil;
  
      private long mReEnableDelayMillis;
  
      private int mRecoveryDelayMillis;
  
      private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
  
      static final int CMD_EMERGENCY_MODE_CHANGED                 = BASE + 1;
      static final int CMD_SCAN_ALWAYS_MODE_CHANGED               = BASE + 7;
      static final int CMD_WIFI_TOGGLED                           = BASE + 8;
      static final int CMD_AIRPLANE_TOGGLED                       = BASE + 9;
      static final int CMD_SET_AP                                 = BASE + 10;
      static final int CMD_DEFERRED_TOGGLE                        = BASE + 11;
      static final int CMD_AP_START_FAILURE                       = BASE + 13;
      static final int CMD_EMERGENCY_CALL_STATE_CHANGED           = BASE + 14;
      static final int CMD_AP_STOPPED                             = BASE + 15;
      static final int CMD_STA_START_FAILURE                      = BASE + 16;
      // Command used to trigger a wifi stack restart when in active mode
      static final int CMD_RECOVERY_RESTART_WIFI                  = BASE + 17;
      // Internal command used to complete wifi stack restart
      private static final int CMD_RECOVERY_RESTART_WIFI_CONTINUE = BASE + 18;
      // Command to disable wifi when SelfRecovery is throttled or otherwise not doing full recovery
      static final int CMD_RECOVERY_DISABLE_WIFI                  = BASE + 19;
      static final int CMD_STA_STOPPED                            = BASE + 20;
      static final int CMD_SCANNING_STOPPED                       = BASE + 21;
      static final int CMD_DEFERRED_RECOVERY_RESTART_WIFI         = BASE + 22;
  
      private DefaultState mDefaultState = new DefaultState();
      private StaEnabledState mStaEnabledState = new StaEnabledState();
      private StaDisabledState mStaDisabledState = new StaDisabledState();
      private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState();
      private EcmState mEcmState = new EcmState();
  
      private ScanOnlyModeManager.Listener mScanOnlyModeCallback = new ScanOnlyCallback();
      private ClientModeManager.Listener mClientModeCallback = new ClientModeCallback();
  
      WifiController(Context context, ClientModeImpl clientModeImpl, Looper clientModeImplLooper,
                     WifiSettingsStore wss, Looper wifiServiceLooper, FrameworkFacade f,
                     ActiveModeWarden amw, WifiPermissionsUtil wifiPermissionsUtil) {
          super(TAG, wifiServiceLooper);
          mFacade = f;
          mContext = context;
          mClientModeImpl = clientModeImpl;
          mClientModeImplLooper = clientModeImplLooper;
          mActiveModeWarden = amw;
          mSettingsStore = wss;
          mWifiPermissionsUtil = wifiPermissionsUtil;
  
          // CHECKSTYLE:OFF IndentationCheck
          addState(mDefaultState);
              addState(mStaDisabledState, mDefaultState);
              addState(mStaEnabledState, mDefaultState);
              addState(mStaDisabledWithScanState, mDefaultState);
              addState(mEcmState, mDefaultState);
          // CHECKSTYLE:ON IndentationCheck
  
          setLogRecSize(100);
          setLogOnlyTransitions(false);
  
          // register for state updates via callbacks (vs the intents registered below)
          mActiveModeWarden.registerScanOnlyCallback(mScanOnlyModeCallback);
          mActiveModeWarden.registerClientModeCallback(mClientModeCallback);
  
          readWifiReEnableDelay();
          readWifiRecoveryDelay();
      }
  
      @Override
      public void start() {
          boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();
          boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();
          boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable();
          boolean isLocationModeActive = mWifiPermissionsUtil.isLocationModeEnabled();
  
          log("isAirplaneModeOn = " + isAirplaneModeOn
                  + ", isWifiEnabled = " + isWifiEnabled
                  + ", isScanningAvailable = " + isScanningAlwaysAvailable
                  + ", isLocationModeActive = " + isLocationModeActive);
  
          if (checkScanOnlyModeAvailable()) {
              setInitialState(mStaDisabledWithScanState);
          } else {
              setInitialState(mStaDisabledState);
          }
          IntentFilter filter = new IntentFilter();
          filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
          filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
          filter.addAction(LocationManager.MODE_CHANGED_ACTION);
          mContext.registerReceiver(
                  new BroadcastReceiver() {
                      @Override
                      public void onReceive(Context context, Intent intent) {
                          String action = intent.getAction();
                          if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
                              int state = intent.getIntExtra(
                                      WifiManager.EXTRA_WIFI_AP_STATE,
                                      WifiManager.WIFI_AP_STATE_FAILED);
                              if (state == WifiManager.WIFI_AP_STATE_FAILED) {
                                  Log.e(TAG, "SoftAP start failed");
                                  sendMessage(CMD_AP_START_FAILURE);
                              } else if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
                                  sendMessage(CMD_AP_STOPPED);
                              }
                          } else if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
                              // Location mode has been toggled...  trigger with the scan change
                              // update to make sure we are in the correct mode
                              sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
                          }
                      }
                  },
                  new IntentFilter(filter));
          super.start();
      }
  
      private boolean checkScanOnlyModeAvailable() {
          // first check if Location service is disabled, if so return false
          if (!mWifiPermissionsUtil.isLocationModeEnabled()) {
              return false;
          }
          return mSettingsStore.isScanAlwaysAvailable();
      }
  
      /**
       * Listener used to receive scan mode updates - really needed for disabled updates to trigger
       * mode changes.
       */
      private class ScanOnlyCallback implements ScanOnlyModeManager.Listener {
          @Override
          public void onStateChanged(int state) {
              if (state == WifiManager.WIFI_STATE_UNKNOWN) {
                  Log.d(TAG, "ScanOnlyMode unexpected failure: state unknown");
              } else if (state == WifiManager.WIFI_STATE_DISABLED) {
                  Log.d(TAG, "ScanOnlyMode stopped");
                  sendMessage(CMD_SCANNING_STOPPED);
              } else if (state == WifiManager.WIFI_STATE_ENABLED) {
                  // scan mode is ready to go
                  Log.d(TAG, "scan mode active");
              } else {
                  Log.d(TAG, "unexpected state update: " + state);
              }
          }
      }
  
      /**
       * Listener used to receive client mode updates
       */
      private class ClientModeCallback implements ClientModeManager.Listener {
          @Override
          public void onStateChanged(int state) {
              if (state == WifiManager.WIFI_STATE_UNKNOWN) {
                  logd("ClientMode unexpected failure: state unknown");
                  sendMessage(CMD_STA_START_FAILURE);
              } else if (state == WifiManager.WIFI_STATE_DISABLED) {
                  logd("ClientMode stopped");
                  sendMessage(CMD_STA_STOPPED);
              } else if (state == WifiManager.WIFI_STATE_ENABLED) {
                  // scan mode is ready to go
                  logd("client mode active");
              } else {
                  logd("unexpected state update: " + state);
              }
          }
      }
  
      private void readWifiReEnableDelay() {
          mReEnableDelayMillis = mFacade.getLongSetting(mContext,
                  Settings.Global.WIFI_REENABLE_DELAY_MS, DEFAULT_REENABLE_DELAY_MS);
      }
  
      private void readWifiRecoveryDelay() {
          mRecoveryDelayMillis = mContext.getResources().getInteger(
                  R.integer.config_wifi_framework_recovery_timeout_delay);
          if (mRecoveryDelayMillis > MAX_RECOVERY_TIMEOUT_DELAY_MS) {
              mRecoveryDelayMillis = MAX_RECOVERY_TIMEOUT_DELAY_MS;
              Log.w(TAG, "Overriding timeout delay with maximum limit value");
          }
      }
  
      class DefaultState extends State {
          @Override
          public boolean processMessage(Message msg) {
              switch (msg.what) {
                  case CMD_SCAN_ALWAYS_MODE_CHANGED:
                  case CMD_WIFI_TOGGLED:
                  case CMD_AP_START_FAILURE:
                  case CMD_SCANNING_STOPPED:
                  case CMD_STA_STOPPED:
                  case CMD_STA_START_FAILURE:
                  case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
                  case CMD_DEFERRED_RECOVERY_RESTART_WIFI:
                      break;
                  case CMD_RECOVERY_DISABLE_WIFI:
                      log("Recovery has been throttled, disable wifi");
                      mActiveModeWarden.shutdownWifi();
                      transitionTo(mStaDisabledState);
                      break;
                  case CMD_RECOVERY_RESTART_WIFI:
                      deferMessage(obtainMessage(CMD_DEFERRED_RECOVERY_RESTART_WIFI));
                      mActiveModeWarden.shutdownWifi();
                      transitionTo(mStaDisabledState);
                      break;
                  case CMD_DEFERRED_TOGGLE:
                      log("DEFERRED_TOGGLE ignored due to state change");
                      break;
                  case CMD_SET_AP:
                      // note: CMD_SET_AP is handled/dropped in ECM mode - will not start here
                      if (msg.arg1 == 1) {
                          SoftApModeConfiguration config = (SoftApModeConfiguration) msg.obj;
                          mActiveModeWarden.enterSoftAPMode((SoftApModeConfiguration) msg.obj);
                      } else {
                          mActiveModeWarden.stopSoftAPMode(msg.arg2);
                      }
                      break;
                  case CMD_AIRPLANE_TOGGLED:
                      if (mSettingsStore.isAirplaneModeOn()) {
                          log("Airplane mode toggled, shutdown all modes");
                          mActiveModeWarden.shutdownWifi();
                          transitionTo(mStaDisabledState);
                      } else {
                          log("Airplane mode disabled, determine next state");
                          if (mSettingsStore.isWifiToggleEnabled()) {
                              transitionTo(mStaEnabledState);
                          } else if (checkScanOnlyModeAvailable()) {
                              transitionTo(mStaDisabledWithScanState);
                          }
                          // wifi should remain disabled, do not need to transition
                      }
                      break;
                  case CMD_EMERGENCY_CALL_STATE_CHANGED:
                  case CMD_EMERGENCY_MODE_CHANGED:
                      if (msg.arg1 == 1) {
                          transitionTo(mEcmState);
                      }
                      break;
                  case CMD_AP_STOPPED:
                      log("SoftAp mode disabled, determine next state");
                      if (mSettingsStore.isWifiToggleEnabled()) {
                          transitionTo(mStaEnabledState);
                      } else if (checkScanOnlyModeAvailable()) {
                          transitionTo(mStaDisabledWithScanState);
                      }
                      // wifi should remain disabled, do not need to transition
                      break;
                  default:
                      throw new RuntimeException("WifiController.handleMessage " + msg.what);
              }
              return HANDLED;
          }
  
      }
  
      class StaDisabledState extends State {
          private int mDeferredEnableSerialNumber = 0;
          private boolean mHaveDeferredEnable = false;
          private long mDisabledTimestamp;
  
          @Override
          public void enter() {
              mActiveModeWarden.disableWifi();
              // Supplicant can't restart right away, so note the time we switched off
              mDisabledTimestamp = SystemClock.elapsedRealtime();
              mDeferredEnableSerialNumber++;
              mHaveDeferredEnable = false;
          }
          @Override
          public boolean processMessage(Message msg) {
              switch (msg.what) {
                  case CMD_WIFI_TOGGLED:
                      if (mSettingsStore.isWifiToggleEnabled()) {
                          if (doDeferEnable(msg)) {
                              if (mHaveDeferredEnable) {
                                  //  have 2 toggles now, inc serial number and ignore both
                                  mDeferredEnableSerialNumber++;
                              }
                              mHaveDeferredEnable = !mHaveDeferredEnable;
                              break;
                          }
                          transitionTo(mStaEnabledState);
                      } else if (checkScanOnlyModeAvailable()) {
                          // only go to scan mode if we aren't in airplane mode
                          if (mSettingsStore.isAirplaneModeOn()) {
                              transitionTo(mStaDisabledWithScanState);
                          }
                      }
                      break;
                  case CMD_SCAN_ALWAYS_MODE_CHANGED:
                      if (checkScanOnlyModeAvailable()) {
                          transitionTo(mStaDisabledWithScanState);
                          break;
                      }
                      break;
                  case CMD_SET_AP:
                      if (msg.arg1 == 1) {
                          // remember that we were disabled, but pass the command up to start softap
                          mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
                      }
                      return NOT_HANDLED;
                  case CMD_DEFERRED_TOGGLE:
                      if (msg.arg1 != mDeferredEnableSerialNumber) {
                          log("DEFERRED_TOGGLE ignored due to serial mismatch");
                          break;
                      }
                      log("DEFERRED_TOGGLE handled");
                      sendMessage((Message)(msg.obj));
                      break;
                  case CMD_DEFERRED_RECOVERY_RESTART_WIFI:
                      // wait mRecoveryDelayMillis for letting driver clean reset.
                      sendMessageDelayed(CMD_RECOVERY_RESTART_WIFI_CONTINUE, mRecoveryDelayMillis);
                      break;
                  case CMD_RECOVERY_RESTART_WIFI_CONTINUE:
                      if (mSettingsStore.isWifiToggleEnabled()) {
                          // wifi is currently disabled but the toggle is on, must have had an
                          // interface down before the recovery triggered
                          transitionTo(mStaEnabledState);
                          break;
                      } else if (checkScanOnlyModeAvailable()) {
                          transitionTo(mStaDisabledWithScanState);
                          break;
                      }
                      break;
                  default:
                      return NOT_HANDLED;
              }
              return HANDLED;
          }
  
          private boolean doDeferEnable(Message msg) {
              long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
              if (delaySoFar >= mReEnableDelayMillis) {
                  return false;
              }
  
              log("WifiController msg " + msg + " deferred for " +
                      (mReEnableDelayMillis - delaySoFar) + "ms");
  
              // need to defer this action.
              Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
              deferredMsg.obj = Message.obtain(msg);
              deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
              sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
              return true;
          }
  
      }
  
      class StaEnabledState extends State {
          @Override
          public void enter() {
              log("StaEnabledState.enter()");
              mActiveModeWarden.enterClientMode();
          }
  
          @Override
          public boolean processMessage(Message msg) {
              switch (msg.what) {
                  case CMD_WIFI_TOGGLED:
                      if (! mSettingsStore.isWifiToggleEnabled()) {
                          if (checkScanOnlyModeAvailable()) {
                              transitionTo(mStaDisabledWithScanState);
                          } else {
                              transitionTo(mStaDisabledState);
                          }
                      }
                      break;
                  case CMD_AIRPLANE_TOGGLED:
                      // airplane mode toggled on is handled in the default state
                      if (mSettingsStore.isAirplaneModeOn()) {
                          return NOT_HANDLED;
                      } else {
                          // when airplane mode is toggled off, but wifi is on, we can keep it on
                          log("airplane mode toggled - and airplane mode is off.  return handled");
                          return HANDLED;
                      }
                  case CMD_STA_START_FAILURE:
                      if (!checkScanOnlyModeAvailable()) {
                          transitionTo(mStaDisabledState);
                      } else {
                          transitionTo(mStaDisabledWithScanState);
                      }
                      break;
                  case CMD_SET_AP:
                      if (msg.arg1 == 1) {
                          // remember that we were enabled, but pass the command up to start softap
                          mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_ENABLED);
                      }
                      return NOT_HANDLED;
                  case CMD_AP_START_FAILURE:
                  case CMD_AP_STOPPED:
                      // already in a wifi mode, no need to check where we should go with softap
                      // stopped
                      break;
                  case CMD_STA_STOPPED:
                      // Client mode stopped.  head to Disabled to wait for next command
                      transitionTo(mStaDisabledState);
                      break;
                  case CMD_RECOVERY_RESTART_WIFI:
                      final String bugTitle;
                      final String bugDetail;
                      if (msg.arg1 < SelfRecovery.REASON_STRINGS.length && msg.arg1 >= 0) {
                          bugDetail = SelfRecovery.REASON_STRINGS[msg.arg1];
                          bugTitle = "Wi-Fi BugReport: " + bugDetail;
                      } else {
                          bugDetail = "";
                          bugTitle = "Wi-Fi BugReport";
                      }
                      if (msg.arg1 != SelfRecovery.REASON_LAST_RESORT_WATCHDOG) {
                          (new Handler(mClientModeImplLooper)).post(() -> {
                              mClientModeImpl.takeBugReport(bugTitle, bugDetail);
                          });
                      }
                      // after the bug report trigger, more handling needs to be done
                      return NOT_HANDLED;
                  default:
                      return NOT_HANDLED;
              }
              return HANDLED;
          }
      }
  
      class StaDisabledWithScanState extends State {
          private int mDeferredEnableSerialNumber = 0;
          private boolean mHaveDeferredEnable = false;
          private long mDisabledTimestamp;
  
          @Override
          public void enter() {
              // now trigger the actual mode switch in ActiveModeWarden
              mActiveModeWarden.enterScanOnlyMode();
  
              // TODO b/71559473: remove the defered enable after mode management changes are complete
              // Supplicant can't restart right away, so not the time we switched off
              mDisabledTimestamp = SystemClock.elapsedRealtime();
              mDeferredEnableSerialNumber++;
              mHaveDeferredEnable = false;
          }
  
          @Override
          public boolean processMessage(Message msg) {
              switch (msg.what) {
                  case CMD_WIFI_TOGGLED:
                      if (mSettingsStore.isWifiToggleEnabled()) {
                          if (doDeferEnable(msg)) {
                              if (mHaveDeferredEnable) {
                                  // have 2 toggles now, inc serial number and ignore both
                                  mDeferredEnableSerialNumber++;
                              }
                              mHaveDeferredEnable = !mHaveDeferredEnable;
                              break;
                          }
                          transitionTo(mStaEnabledState);
                      }
                      break;
                  case CMD_SCAN_ALWAYS_MODE_CHANGED:
                      if (!checkScanOnlyModeAvailable()) {
                          log("StaDisabledWithScanState: scan no longer available");
                          transitionTo(mStaDisabledState);
                      }
                      break;
                  case CMD_SET_AP:
                      if (msg.arg1 == 1) {
                          // remember that we were disabled, but pass the command up to start softap
                          mSettingsStore.setWifiSavedState(WifiSettingsStore.WIFI_DISABLED);
                      }
                      return NOT_HANDLED;
                  case CMD_DEFERRED_TOGGLE:
                      if (msg.arg1 != mDeferredEnableSerialNumber) {
                          log("DEFERRED_TOGGLE ignored due to serial mismatch");
                          break;
                      }
                      logd("DEFERRED_TOGGLE handled");
                      sendMessage((Message)(msg.obj));
                      break;
                  case CMD_AP_START_FAILURE:
                  case CMD_AP_STOPPED:
                      // already in a wifi mode, no need to check where we should go with softap
                      // stopped
                      break;
                  case CMD_SCANNING_STOPPED:
                      // stopped due to interface destruction - return to disabled and wait
                      log("WifiController: SCANNING_STOPPED when in scan mode -> StaDisabled");
                      transitionTo(mStaDisabledState);
                      break;
                  default:
                      return NOT_HANDLED;
              }
              return HANDLED;
          }
  
          private boolean doDeferEnable(Message msg) {
              long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
              if (delaySoFar >= mReEnableDelayMillis) {
                  return false;
              }
  
              log("WifiController msg " + msg + " deferred for " +
                      (mReEnableDelayMillis - delaySoFar) + "ms");
  
              // need to defer this action.
              Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
              deferredMsg.obj = Message.obtain(msg);
              deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
              sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
              return true;
          }
  
      }
  
      /**
       * Determine the next state based on the current settings (e.g. saved
       * wifi state).
       */
      private State getNextWifiState() {
          if (mSettingsStore.getWifiSavedState() == WifiSettingsStore.WIFI_ENABLED) {
              return mStaEnabledState;
          }
  
          if (checkScanOnlyModeAvailable()) {
              return mStaDisabledWithScanState;
          }
  
          return mStaDisabledState;
      }
  
      class EcmState extends State {
          // we can enter EcmState either because an emergency call started or because
          // emergency callback mode started. This count keeps track of how many such
          // events happened; so we can exit after all are undone
  
          private int mEcmEntryCount;
          @Override
          public void enter() {
              mActiveModeWarden.stopSoftAPMode(WifiManager.IFACE_IP_MODE_UNSPECIFIED);
              boolean configWiFiDisableInECBM =
                      mFacade.getConfigWiFiDisableInECBM(mContext);
              log("WifiController msg getConfigWiFiDisableInECBM "
                      + configWiFiDisableInECBM);
              if (configWiFiDisableInECBM) {
                  mActiveModeWarden.shutdownWifi();
              }
              mEcmEntryCount = 1;
          }
  
          /**
           * Handles messages received while in EcmMode.
           */
          @Override
          public boolean processMessage(Message msg) {
              switch (msg.what) {
                  case CMD_EMERGENCY_CALL_STATE_CHANGED:
                      if (msg.arg1 == 1) {
                          // nothing to do - just says emergency call started
                          mEcmEntryCount++;
                      } else if (msg.arg1 == 0) {
                          // emergency call ended
                          decrementCountAndReturnToAppropriateState();
                      }
                      return HANDLED;
                  case CMD_EMERGENCY_MODE_CHANGED:
                      if (msg.arg1 == 1) {
                          // Transitioned into emergency callback mode
                          mEcmEntryCount++;
                      } else if (msg.arg1 == 0) {
                          // out of emergency callback mode
                          decrementCountAndReturnToAppropriateState();
                      }
                      return HANDLED;
                  case CMD_RECOVERY_RESTART_WIFI:
                  case CMD_RECOVERY_DISABLE_WIFI:
                      // do not want to restart wifi if we are in emergency mode
                      return HANDLED;
                  case CMD_AP_STOPPED:
                  case CMD_SCANNING_STOPPED:
                  case CMD_STA_STOPPED:
                      // do not want to trigger a mode switch if we are in emergency mode
                      return HANDLED;
                  case CMD_SET_AP:
                      // do not want to start softap if we are in emergency mode
                      return HANDLED;
                  default:
                      return NOT_HANDLED;
              }
          }
  
          private void decrementCountAndReturnToAppropriateState() {
              boolean exitEcm = false;
  
              if (mEcmEntryCount == 0) {
                  loge("mEcmEntryCount is 0; exiting Ecm");
                  exitEcm = true;
              } else if (--mEcmEntryCount == 0) {
                  exitEcm = true;
              }
  
              if (exitEcm) {
                  if (mSettingsStore.isWifiToggleEnabled()) {
                      transitionTo(mStaEnabledState);
                  } else if (checkScanOnlyModeAvailable()) {
                      transitionTo(mStaDisabledWithScanState);
                  } else {
                      transitionTo(mStaDisabledState);
                  }
              }
          }
      }
  }
  