content: /*
   * Copyright (C) 2016 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.annotation.NonNull;
  import android.content.Context;
  import android.net.wifi.WifiConfiguration;
  import android.net.wifi.WifiManager;
  import android.os.BatteryStats;
  import android.os.Handler;
  import android.os.Looper;
  import android.os.Message;
  import android.os.RemoteException;
  import android.util.ArraySet;
  import android.util.Log;
  
  import com.android.internal.app.IBatteryStats;
  import com.android.internal.util.Protocol;
  import com.android.internal.util.State;
  import com.android.internal.util.StateMachine;
  import com.android.server.wifi.WifiNative.StatusListener;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  
  /**
   * This class provides the implementation for different WiFi operating modes.
   */
  public class ActiveModeWarden {
      private static final String TAG = "WifiActiveModeWarden";
  
      private ModeStateMachine mModeStateMachine;
  
      // Holder for active mode managers
      private final ArraySet<ActiveModeManager> mActiveModeManagers;
      // DefaultModeManager used to service API calls when there are not active mode managers.
      private DefaultModeManager mDefaultModeManager;
  
      private final WifiInjector mWifiInjector;
      private final Context mContext;
      private final Looper mLooper;
      private final Handler mHandler;
      private final WifiNative mWifiNative;
      private final IBatteryStats mBatteryStats;
      private final SelfRecovery mSelfRecovery;
      private BaseWifiDiagnostics mWifiDiagnostics;
      private final ScanRequestProxy mScanRequestProxy;
  
      // The base for wifi message types
      static final int BASE = Protocol.BASE_WIFI;
  
      // The message identifiers below are mapped to those in ClientModeImpl when applicable.
      // Start the soft access point
      static final int CMD_START_AP                                       = BASE + 21;
      // Indicates soft ap start failed
      static final int CMD_START_AP_FAILURE                               = BASE + 22;
      // Stop the soft access point
      static final int CMD_STOP_AP                                        = BASE + 23;
      // Soft access point teardown is completed
      static final int CMD_AP_STOPPED                                     = BASE + 24;
  
      // Start Scan Only mode
      static final int CMD_START_SCAN_ONLY_MODE                           = BASE + 200;
      // Indicates that start Scan only mode failed
      static final int CMD_START_SCAN_ONLY_MODE_FAILURE                   = BASE + 201;
      // Indicates that scan only mode stopped
      static final int CMD_STOP_SCAN_ONLY_MODE                            = BASE + 202;
      // ScanOnly mode teardown is complete
      static final int CMD_SCAN_ONLY_MODE_STOPPED                         = BASE + 203;
      // ScanOnly mode failed
      static final int CMD_SCAN_ONLY_MODE_FAILED                          = BASE + 204;
  
      // Start Client mode
      static final int CMD_START_CLIENT_MODE                              = BASE + 300;
      // Indicates that start client mode failed
      static final int CMD_START_CLIENT_MODE_FAILURE                      = BASE + 301;
      // Indicates that client mode stopped
      static final int CMD_STOP_CLIENT_MODE                               = BASE + 302;
      // Client mode teardown is complete
      static final int CMD_CLIENT_MODE_STOPPED                            = BASE + 303;
      // Client mode failed
      static final int CMD_CLIENT_MODE_FAILED                             = BASE + 304;
  
      private StatusListener mWifiNativeStatusListener;
  
      private WifiManager.SoftApCallback mSoftApCallback;
      private ScanOnlyModeManager.Listener mScanOnlyCallback;
      private ClientModeManager.Listener mClientModeCallback;
  
      /**
       * Called from WifiServiceImpl to register a callback for notifications from SoftApManager
       */
      public void registerSoftApCallback(@NonNull WifiManager.SoftApCallback callback) {
          mSoftApCallback = callback;
      }
  
      /**
       * Called from WifiController to register a callback for notifications from ScanOnlyModeManager
       */
      public void registerScanOnlyCallback(@NonNull ScanOnlyModeManager.Listener callback) {
          mScanOnlyCallback = callback;
      }
  
      /**
       * Called from WifiController to register a callback for notifications from ClientModeManager
       */
      public void registerClientModeCallback(@NonNull ClientModeManager.Listener callback) {
          mClientModeCallback = callback;
      }
  
      ActiveModeWarden(WifiInjector wifiInjector,
                       Context context,
                       Looper looper,
                       WifiNative wifiNative,
                       DefaultModeManager defaultModeManager,
                       IBatteryStats batteryStats) {
          mWifiInjector = wifiInjector;
          mContext = context;
          mLooper = looper;
          mHandler = new Handler(looper);
          mWifiNative = wifiNative;
          mActiveModeManagers = new ArraySet();
          mDefaultModeManager = defaultModeManager;
          mBatteryStats = batteryStats;
          mSelfRecovery = mWifiInjector.getSelfRecovery();
          mWifiDiagnostics = mWifiInjector.getWifiDiagnostics();
          mScanRequestProxy = mWifiInjector.getScanRequestProxy();
          mModeStateMachine = new ModeStateMachine();
          mWifiNativeStatusListener = new WifiNativeStatusListener();
          mWifiNative.registerStatusListener(mWifiNativeStatusListener);
      }
  
      /**
       * Method to switch wifi into client mode where connections to configured networks will be
       * attempted.
       */
      public void enterClientMode() {
          changeMode(ModeStateMachine.CMD_START_CLIENT_MODE);
      }
  
      /**
       * Method to switch wifi into scan only mode where network connection attempts will not be made.
       *
       * This mode is utilized by location scans.  If wifi is disabled by a user, but they have
       * previously configured their device to perform location scans, this mode allows wifi to
       * fulfill the location scan requests but will not be used for connectivity.
       */
      public void enterScanOnlyMode() {
          changeMode(ModeStateMachine.CMD_START_SCAN_ONLY_MODE);
      }
  
      /**
       * Method to enable soft ap for wifi hotspot.
       *
       * The supplied SoftApModeConfiguration includes the target softap WifiConfiguration (or null if
       * the persisted config is to be used) and the target operating mode (ex,
       * {@link WifiManager.IFACE_IP_MODE_TETHERED} {@link WifiManager.IFACE_IP_MODE_LOCAL_ONLY}).
       *
       * @param wifiConfig SoftApModeConfiguration for the hostapd softap
       */
      public void enterSoftAPMode(@NonNull SoftApModeConfiguration wifiConfig) {
          mHandler.post(() -> {
              startSoftAp(wifiConfig);
          });
      }
  
      /**
       * Method to stop soft ap for wifi hotspot.
       *
       * This method will stop any active softAp mode managers.
       *
       * @param mode the operating mode of APs to bring down (ex,
       *             {@link WifiManager.IFACE_IP_MODE_TETHERED} or
       *             {@link WifiManager.IFACE_IP_MODE_LOCAL_ONLY}).
       *             Use {@link WifiManager.IFACE_IP_MODE_UNSPECIFIED} to stop all APs.
       */
      public void stopSoftAPMode(int mode) {
          mHandler.post(() -> {
              for (ActiveModeManager manager : mActiveModeManagers) {
                  if (!(manager instanceof SoftApManager)) continue;
                  SoftApManager softApManager = (SoftApManager) manager;
  
                  if (mode != WifiManager.IFACE_IP_MODE_UNSPECIFIED
                          && mode != softApManager.getIpMode()) {
                      continue;
                  }
                  softApManager.stop();
              }
              updateBatteryStatsWifiState(false);
          });
      }
  
      /**
       * Method to disable wifi in sta/client mode scenarios.
       *
       * This mode will stop any client/scan modes and will not perform any network scans.
       */
      public void disableWifi() {
          changeMode(ModeStateMachine.CMD_DISABLE_WIFI);
      }
  
      /**
       * Method to stop all active modes, for example, when toggling airplane mode.
       */
      public void shutdownWifi() {
          mHandler.post(() -> {
              for (ActiveModeManager manager : mActiveModeManagers) {
                  manager.stop();
              }
              updateBatteryStatsWifiState(false);
          });
      }
  
      /**
       * Dump current state for active mode managers.
       *
       * Must be called from ClientModeImpl thread.
       */
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          pw.println("Dump of " + TAG);
  
          pw.println("Current wifi mode: " + getCurrentMode());
          pw.println("NumActiveModeManagers: " + mActiveModeManagers.size());
          for (ActiveModeManager manager : mActiveModeManagers) {
              manager.dump(fd, pw, args);
          }
      }
  
      protected String getCurrentMode() {
          return mModeStateMachine.getCurrentMode();
      }
  
      private void changeMode(int newMode) {
          mModeStateMachine.sendMessage(newMode);
      }
  
      /**
       *  Helper class to wrap the ActiveModeManager callback objects.
       */
      private class ModeCallback {
          ActiveModeManager mActiveManager;
  
          void setActiveModeManager(ActiveModeManager manager) {
              mActiveManager = manager;
          }
  
          ActiveModeManager getActiveModeManager() {
              return mActiveManager;
          }
      }
  
      private class ModeStateMachine extends StateMachine {
          // Commands for the state machine  - these will be removed,
          // along with the StateMachine itself
          public static final int CMD_START_CLIENT_MODE    = 0;
          public static final int CMD_START_SCAN_ONLY_MODE = 1;
          public static final int CMD_DISABLE_WIFI         = 3;
  
          private final State mWifiDisabledState = new WifiDisabledState();
          private final State mClientModeActiveState = new ClientModeActiveState();
          private final State mScanOnlyModeActiveState = new ScanOnlyModeActiveState();
  
          ModeStateMachine() {
              super(TAG, mLooper);
  
              addState(mClientModeActiveState);
              addState(mScanOnlyModeActiveState);
              addState(mWifiDisabledState);
  
              Log.d(TAG, "Starting Wifi in WifiDisabledState");
              setInitialState(mWifiDisabledState);
              start();
          }
  
          private String getCurrentMode() {
              return getCurrentState().getName();
          }
  
          private boolean checkForAndHandleModeChange(Message message) {
              switch(message.what) {
                  case ModeStateMachine.CMD_START_CLIENT_MODE:
                      Log.d(TAG, "Switching from " + getCurrentMode() + " to ClientMode");
                      mModeStateMachine.transitionTo(mClientModeActiveState);
                      break;
                  case ModeStateMachine.CMD_START_SCAN_ONLY_MODE:
                      Log.d(TAG, "Switching from " + getCurrentMode() + " to ScanOnlyMode");
                      mModeStateMachine.transitionTo(mScanOnlyModeActiveState);
                      break;
                  case ModeStateMachine.CMD_DISABLE_WIFI:
                      Log.d(TAG, "Switching from " + getCurrentMode() + " to WifiDisabled");
                      mModeStateMachine.transitionTo(mWifiDisabledState);
                      break;
                  default:
                      return NOT_HANDLED;
              }
              return HANDLED;
          }
  
          class ModeActiveState extends State {
              ActiveModeManager mManager;
              @Override
              public boolean processMessage(Message message) {
                  // handle messages for changing modes here
                  return NOT_HANDLED;
              }
  
              @Override
              public void exit() {
                  // Active states must have a mode manager, so this should not be null, but it isn't
                  // obvious from the structure - add a null check here, just in case this is missed
                  // in the future
                  if (mManager != null) {
                      mManager.stop();
                      mActiveModeManagers.remove(mManager);
                      updateScanMode();
                  }
                  updateBatteryStatsWifiState(false);
              }
  
              // Hook to be used by sub-classes of ModeActiveState to indicate the completion of
              // bringup of the corresponding mode.
              public void onModeActivationComplete() {
                  updateScanMode();
              }
  
              // Update the scan state based on all active mode managers.
              // Note: This is an overkill currently because there is only 1 of scan-only or client
              // mode present today.
              private void updateScanMode() {
                  boolean scanEnabled = false;
                  boolean scanningForHiddenNetworksEnabled = false;
                  for (ActiveModeManager modeManager : mActiveModeManagers) {
                      @ActiveModeManager.ScanMode int scanState = modeManager.getScanMode();
                      switch (scanState) {
                          case ActiveModeManager.SCAN_NONE:
                              break;
                          case ActiveModeManager.SCAN_WITHOUT_HIDDEN_NETWORKS:
                              scanEnabled = true;
                              break;
                          case ActiveModeManager.SCAN_WITH_HIDDEN_NETWORKS:
                              scanEnabled = true;
                              scanningForHiddenNetworksEnabled = true;
                              break;
                      }
                  }
                  mScanRequestProxy.enableScanning(scanEnabled, scanningForHiddenNetworksEnabled);
              }
          }
  
          class WifiDisabledState extends ModeActiveState {
              @Override
              public void enter() {
                  Log.d(TAG, "Entering WifiDisabledState");
              }
  
              @Override
              public boolean processMessage(Message message) {
                  Log.d(TAG, "received a message in WifiDisabledState: " + message);
                  if (checkForAndHandleModeChange(message)) {
                      return HANDLED;
                  }
                  return NOT_HANDLED;
              }
  
              @Override
              public void exit() {
                  // do not have an active mode manager...  nothing to clean up
              }
  
          }
  
          class ClientModeActiveState extends ModeActiveState {
              ClientListener mListener;
              private class ClientListener implements ClientModeManager.Listener {
                  @Override
                  public void onStateChanged(int state) {
                      // make sure this listener is still active
                      if (this != mListener) {
                          Log.d(TAG, "Client mode state change from previous manager");
                          return;
                      }
  
                      Log.d(TAG, "State changed from client mode. state = " + state);
  
                      if (state == WifiManager.WIFI_STATE_UNKNOWN) {
                          // error while setting up client mode or an unexpected failure.
                          mModeStateMachine.sendMessage(CMD_CLIENT_MODE_FAILED, this);
                      } else if (state == WifiManager.WIFI_STATE_DISABLED) {
                          // client mode stopped
                          mModeStateMachine.sendMessage(CMD_CLIENT_MODE_STOPPED, this);
                      } else if (state == WifiManager.WIFI_STATE_ENABLED) {
                          // client mode is ready to go
                          Log.d(TAG, "client mode active");
                          onModeActivationComplete();
                      } else {
                          // only care if client mode stopped or started, dropping
                      }
                  }
              }
  
              @Override
              public void enter() {
                  Log.d(TAG, "Entering ClientModeActiveState");
  
                  mListener = new ClientListener();
                  mManager = mWifiInjector.makeClientModeManager(mListener);
                  mManager.start();
                  mActiveModeManagers.add(mManager);
  
                  updateBatteryStatsWifiState(true);
              }
  
              @Override
              public void exit() {
                  super.exit();
                  mListener = null;
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (checkForAndHandleModeChange(message)) {
                      return HANDLED;
                  }
  
                  switch(message.what) {
                      case CMD_START_CLIENT_MODE:
                          Log.d(TAG, "Received CMD_START_CLIENT_MODE when active - drop");
                          break;
                      case CMD_CLIENT_MODE_FAILED:
                          if (mListener != message.obj) {
                              Log.d(TAG, "Client mode state change from previous manager");
                              return HANDLED;
                          }
                          Log.d(TAG, "ClientMode failed, return to WifiDisabledState.");
                          // notify WifiController that ClientMode failed
                          mClientModeCallback.onStateChanged(WifiManager.WIFI_STATE_UNKNOWN);
                          mModeStateMachine.transitionTo(mWifiDisabledState);
                          break;
                      case CMD_CLIENT_MODE_STOPPED:
                          if (mListener != message.obj) {
                              Log.d(TAG, "Client mode state change from previous manager");
                              return HANDLED;
                          }
  
                          Log.d(TAG, "ClientMode stopped, return to WifiDisabledState.");
                          // notify WifiController that ClientMode stopped
                          mClientModeCallback.onStateChanged(WifiManager.WIFI_STATE_DISABLED);
                          mModeStateMachine.transitionTo(mWifiDisabledState);
                          break;
                      default:
                          return NOT_HANDLED;
                  }
                  return NOT_HANDLED;
              }
          }
  
          class ScanOnlyModeActiveState extends ModeActiveState {
              ScanOnlyListener mListener;
              private class ScanOnlyListener implements ScanOnlyModeManager.Listener {
                  @Override
                  public void onStateChanged(int state) {
                      if (this != mListener) {
                          Log.d(TAG, "ScanOnly mode state change from previous manager");
                          return;
                      }
  
                      if (state == WifiManager.WIFI_STATE_UNKNOWN) {
                          Log.d(TAG, "ScanOnlyMode mode failed");
                          // error while setting up scan mode or an unexpected failure.
                          mModeStateMachine.sendMessage(CMD_SCAN_ONLY_MODE_FAILED, this);
                      } else if (state == WifiManager.WIFI_STATE_DISABLED) {
                          Log.d(TAG, "ScanOnlyMode stopped");
                          //scan only mode stopped
                          mModeStateMachine.sendMessage(CMD_SCAN_ONLY_MODE_STOPPED, this);
                      } else if (state == WifiManager.WIFI_STATE_ENABLED) {
                          // scan mode is ready to go
                          Log.d(TAG, "scan mode active");
                          onModeActivationComplete();
                      } else {
                          Log.d(TAG, "unexpected state update: " + state);
                      }
                  }
              }
  
              @Override
              public void enter() {
                  Log.d(TAG, "Entering ScanOnlyModeActiveState");
  
                  mListener = new ScanOnlyListener();
                  mManager = mWifiInjector.makeScanOnlyModeManager(mListener);
                  mManager.start();
                  mActiveModeManagers.add(mManager);
  
                  updateBatteryStatsWifiState(true);
                  updateBatteryStatsScanModeActive();
              }
  
              @Override
              public void exit() {
                  super.exit();
                  mListener = null;
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (checkForAndHandleModeChange(message)) {
                      return HANDLED;
                  }
  
                  switch(message.what) {
                      case CMD_START_SCAN_ONLY_MODE:
                          Log.d(TAG, "Received CMD_START_SCAN_ONLY_MODE when active - drop");
                          break;
                      case CMD_SCAN_ONLY_MODE_FAILED:
                          if (mListener != message.obj) {
                              Log.d(TAG, "ScanOnly mode state change from previous manager");
                              return HANDLED;
                          }
  
                          Log.d(TAG, "ScanOnlyMode failed, return to WifiDisabledState.");
                          // notify WifiController that ScanOnlyMode failed
                          mScanOnlyCallback.onStateChanged(WifiManager.WIFI_STATE_UNKNOWN);
                          mModeStateMachine.transitionTo(mWifiDisabledState);
                          break;
                      case CMD_SCAN_ONLY_MODE_STOPPED:
                          if (mListener != message.obj) {
                              Log.d(TAG, "ScanOnly mode state change from previous manager");
                              return HANDLED;
                          }
  
                          Log.d(TAG, "ScanOnlyMode stopped, return to WifiDisabledState.");
                          // notify WifiController that ScanOnlyMode stopped
                          mScanOnlyCallback.onStateChanged(WifiManager.WIFI_STATE_DISABLED);
                          mModeStateMachine.transitionTo(mWifiDisabledState);
                          break;
                      default:
                          return NOT_HANDLED;
                  }
                  return HANDLED;
              }
          }
      }  // class ModeStateMachine
  
      private class SoftApCallbackImpl extends ModeCallback implements WifiManager.SoftApCallback {
          private int mMode;
  
          private SoftApCallbackImpl(int mode) {
              mMode = mode;
          }
  
          @Override
          public void onStateChanged(int state, int reason) {
              if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
                  mActiveModeManagers.remove(getActiveModeManager());
                  updateBatteryStatsWifiState(false);
              } else if (state == WifiManager.WIFI_AP_STATE_FAILED) {
                  mActiveModeManagers.remove(getActiveModeManager());
                  updateBatteryStatsWifiState(false);
              }
  
              if (mSoftApCallback != null && mMode == WifiManager.IFACE_IP_MODE_TETHERED) {
                  mSoftApCallback.onStateChanged(state, reason);
              }
          }
  
          @Override
          public void onNumClientsChanged(int numClients) {
              if (mSoftApCallback == null) {
                  Log.d(TAG, "SoftApCallback is null. Dropping NumClientsChanged event.");
              } else if (mMode == WifiManager.IFACE_IP_MODE_TETHERED) {
                  mSoftApCallback.onNumClientsChanged(numClients);
              }
          }
      }
  
      private void startSoftAp(SoftApModeConfiguration softapConfig) {
          Log.d(TAG, "Starting SoftApModeManager");
  
          WifiConfiguration config = softapConfig.getWifiConfiguration();
          if (config != null && config.SSID != null) {
              Log.d(TAG, "Passing config to SoftApManager! " + config);
          } else {
              config = null;
          }
  
          SoftApCallbackImpl callback = new SoftApCallbackImpl(softapConfig.getTargetMode());
          ActiveModeManager manager = mWifiInjector.makeSoftApManager(callback, softapConfig);
          callback.setActiveModeManager(manager);
          manager.start();
          mActiveModeManagers.add(manager);
          updateBatteryStatsWifiState(true);
      }
  
      /**
       *  Helper method to report wifi state as on/off (doesn't matter which mode).
       *
       *  @param enabled boolean indicating that some mode has been turned on or off
       */
      private void updateBatteryStatsWifiState(boolean enabled) {
          try {
              if (enabled) {
                  if (mActiveModeManagers.size() == 1) {
                      // only report wifi on if we haven't already
                      mBatteryStats.noteWifiOn();
                  }
              } else {
                  if (mActiveModeManagers.size() == 0) {
                      // only report if we don't have any active modes
                      mBatteryStats.noteWifiOff();
                  }
              }
          } catch (RemoteException e) {
              Log.e(TAG, "Failed to note battery stats in wifi");
          }
      }
  
      private void updateBatteryStatsScanModeActive() {
          try {
              mBatteryStats.noteWifiState(BatteryStats.WIFI_STATE_OFF_SCANNING, null);
          } catch (RemoteException e) {
              Log.e(TAG, "Failed to note battery stats in wifi");
          }
      }
  
      // callback used to receive callbacks about underlying native failures
      private final class WifiNativeStatusListener implements StatusListener {
  
          @Override
          public void onStatusChanged(boolean isReady) {
              if (!isReady) {
                  mHandler.post(() -> {
                      Log.e(TAG, "One of the native daemons died. Triggering recovery");
                      mWifiDiagnostics.captureBugReportData(
                              WifiDiagnostics.REPORT_REASON_WIFINATIVE_FAILURE);
  
                      // immediately trigger SelfRecovery if we receive a notice about an
                      // underlying daemon failure
                      mWifiInjector.getSelfRecovery().trigger(SelfRecovery.REASON_WIFINATIVE_FAILURE);
                  });
              }
          }
      };
  }
  