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.content.Intent;
  import android.net.wifi.WifiManager;
  import android.os.Looper;
  import android.os.Message;
  import android.os.UserHandle;
  import android.text.TextUtils;
  import android.util.Log;
  
  import com.android.internal.util.IState;
  import com.android.internal.util.State;
  import com.android.internal.util.StateMachine;
  import com.android.server.wifi.WifiNative.InterfaceCallback;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  
  /**
   * Manager WiFi in Client Mode where we connect to configured networks.
   */
  public class ClientModeManager implements ActiveModeManager {
      private static final String TAG = "WifiClientModeManager";
  
      private final ClientModeStateMachine mStateMachine;
  
      private final Context mContext;
      private final WifiNative mWifiNative;
  
      private final WifiMetrics mWifiMetrics;
      private final Listener mListener;
      private final ClientModeImpl mClientModeImpl;
  
      private String mClientInterfaceName;
      private boolean mIfaceIsUp = false;
  
      private boolean mExpectedStop = false;
  
      ClientModeManager(Context context, @NonNull Looper looper, WifiNative wifiNative,
              Listener listener, WifiMetrics wifiMetrics, ClientModeImpl clientModeImpl) {
          mContext = context;
          mWifiNative = wifiNative;
          mListener = listener;
          mWifiMetrics = wifiMetrics;
          mClientModeImpl = clientModeImpl;
          mStateMachine = new ClientModeStateMachine(looper);
      }
  
      /**
       * Start client mode.
       */
      public void start() {
          mStateMachine.sendMessage(ClientModeStateMachine.CMD_START);
      }
  
      /**
       * Disconnect from any currently connected networks and stop client mode.
       */
      public void stop() {
          Log.d(TAG, " currentstate: " + getCurrentStateName());
          mExpectedStop = true;
          if (mClientInterfaceName != null) {
              if (mIfaceIsUp) {
                  updateWifiState(WifiManager.WIFI_STATE_DISABLING,
                                  WifiManager.WIFI_STATE_ENABLED);
              } else {
                  updateWifiState(WifiManager.WIFI_STATE_DISABLING,
                                  WifiManager.WIFI_STATE_ENABLING);
              }
          }
          mStateMachine.quitNow();
      }
  
      public @ScanMode int getScanMode() {
          return SCAN_WITH_HIDDEN_NETWORKS;
      }
  
      /**
       * Dump info about this ClientMode manager.
       */
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          pw.println("--Dump of ClientModeManager--");
  
          pw.println("current StateMachine mode: " + getCurrentStateName());
          pw.println("mClientInterfaceName: " + mClientInterfaceName);
          pw.println("mIfaceIsUp: " + mIfaceIsUp);
      }
  
      /**
       * Listener for ClientMode state changes.
       */
      public interface Listener {
          /**
           * Invoke when wifi state changes.
           * @param state new wifi state
           */
          void onStateChanged(int state);
      }
  
      private String getCurrentStateName() {
          IState currentState = mStateMachine.getCurrentState();
  
          if (currentState != null) {
              return currentState.getName();
          }
  
          return "StateMachine not active";
      }
  
      /**
       * Update Wifi state and send the broadcast.
       * @param newState new Wifi state
       * @param currentState current wifi state
       */
      private void updateWifiState(int newState, int currentState) {
          if (!mExpectedStop) {
              mListener.onStateChanged(newState);
          } else {
              Log.d(TAG, "expected stop, not triggering callbacks: newState = " + newState);
          }
  
          // Once we report the mode has stopped/failed any other stop signals are redundant
          // note: this can happen in failure modes where we get multiple callbacks as underlying
          // components/interface stops or the underlying interface is destroyed in cleanup
          if (newState == WifiManager.WIFI_STATE_UNKNOWN
                  || newState == WifiManager.WIFI_STATE_DISABLED) {
              mExpectedStop = true;
          }
  
          if (newState == WifiManager.WIFI_STATE_UNKNOWN) {
              // do not need to broadcast failure to system
              return;
          }
  
          mClientModeImpl.setWifiStateForApiCalls(newState);
  
          final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
          intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
          intent.putExtra(WifiManager.EXTRA_WIFI_STATE, newState);
          intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, currentState);
          mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
      }
  
      private class ClientModeStateMachine extends StateMachine {
          // Commands for the state machine.
          public static final int CMD_START = 0;
          public static final int CMD_INTERFACE_STATUS_CHANGED = 3;
          public static final int CMD_INTERFACE_DESTROYED = 4;
          public static final int CMD_INTERFACE_DOWN = 5;
          private final State mIdleState = new IdleState();
          private final State mStartedState = new StartedState();
  
          private final InterfaceCallback mWifiNativeInterfaceCallback = new InterfaceCallback() {
              @Override
              public void onDestroyed(String ifaceName) {
                  if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
                      Log.d(TAG, "STA iface " + ifaceName + " was destroyed, stopping client mode");
  
                      // we must immediately clean up state in ClientModeImpl to unregister
                      // all client mode related objects
                      // Note: onDestroyed is only called from the ClientModeImpl thread
                      mClientModeImpl.handleIfaceDestroyed();
  
                      sendMessage(CMD_INTERFACE_DESTROYED);
                  }
              }
  
              @Override
              public void onUp(String ifaceName) {
                  if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
                      sendMessage(CMD_INTERFACE_STATUS_CHANGED, 1);
                  }
              }
  
              @Override
              public void onDown(String ifaceName) {
                  if (mClientInterfaceName != null && mClientInterfaceName.equals(ifaceName)) {
                      sendMessage(CMD_INTERFACE_STATUS_CHANGED, 0);
                  }
              }
          };
  
          ClientModeStateMachine(Looper looper) {
              super(TAG, looper);
  
              addState(mIdleState);
              addState(mStartedState);
  
              setInitialState(mIdleState);
              start();
          }
  
          private class IdleState extends State {
  
              @Override
              public void enter() {
                  Log.d(TAG, "entering IdleState");
                  mClientInterfaceName = null;
                  mIfaceIsUp = false;
              }
  
              @Override
              public boolean processMessage(Message message) {
                  switch (message.what) {
                      case CMD_START:
                          updateWifiState(WifiManager.WIFI_STATE_ENABLING,
                                          WifiManager.WIFI_STATE_DISABLED);
  
                          mClientInterfaceName =
                                  mWifiNative.setupInterfaceForClientInConnectivityMode(
                                  mWifiNativeInterfaceCallback);
                          if (TextUtils.isEmpty(mClientInterfaceName)) {
                              Log.e(TAG, "Failed to create ClientInterface. Sit in Idle");
                              updateWifiState(WifiManager.WIFI_STATE_UNKNOWN,
                                              WifiManager.WIFI_STATE_ENABLING);
                              updateWifiState(WifiManager.WIFI_STATE_DISABLED,
                                              WifiManager.WIFI_STATE_UNKNOWN);
                              break;
                          }
                          transitionTo(mStartedState);
                          break;
                      default:
                          Log.d(TAG, "received an invalid message: " + message);
                          return NOT_HANDLED;
                  }
                  return HANDLED;
              }
          }
  
          private class StartedState extends State {
  
              private void onUpChanged(boolean isUp) {
                  if (isUp == mIfaceIsUp) {
                      return;  // no change
                  }
                  mIfaceIsUp = isUp;
                  if (isUp) {
                      Log.d(TAG, "Wifi is ready to use for client mode");
                      mClientModeImpl.setOperationalMode(ClientModeImpl.CONNECT_MODE,
                                                         mClientInterfaceName);
                      updateWifiState(WifiManager.WIFI_STATE_ENABLED,
                                      WifiManager.WIFI_STATE_ENABLING);
                  } else {
                      if (mClientModeImpl.isConnectedMacRandomizationEnabled()) {
                          // Handle the error case where our underlying interface went down if we
                          // do not have mac randomization enabled (b/72459123).
                          return;
                      }
                      // if the interface goes down we should exit and go back to idle state.
                      Log.d(TAG, "interface down!");
                      updateWifiState(WifiManager.WIFI_STATE_UNKNOWN,
                                      WifiManager.WIFI_STATE_ENABLED);
                      mStateMachine.sendMessage(CMD_INTERFACE_DOWN);
                  }
              }
  
              @Override
              public void enter() {
                  Log.d(TAG, "entering StartedState");
                  mIfaceIsUp = false;
                  onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName));
              }
  
              @Override
              public boolean processMessage(Message message) {
                  switch(message.what) {
                      case CMD_START:
                          // Already started, ignore this command.
                          break;
                      case CMD_INTERFACE_DOWN:
                          Log.e(TAG, "Detected an interface down, reporting failure to SelfRecovery");
                          mClientModeImpl.failureDetected(SelfRecovery.REASON_STA_IFACE_DOWN);
  
                          updateWifiState(WifiManager.WIFI_STATE_DISABLING,
                                          WifiManager.WIFI_STATE_UNKNOWN);
                          transitionTo(mIdleState);
                          break;
                      case CMD_INTERFACE_STATUS_CHANGED:
                          boolean isUp = message.arg1 == 1;
                          onUpChanged(isUp);
                          break;
                      case CMD_INTERFACE_DESTROYED:
                          Log.d(TAG, "interface destroyed - client mode stopping");
  
                          updateWifiState(WifiManager.WIFI_STATE_DISABLING,
                                          WifiManager.WIFI_STATE_ENABLED);
                          mClientInterfaceName = null;
                          transitionTo(mIdleState);
                          break;
                      default:
                          return NOT_HANDLED;
                  }
                  return HANDLED;
              }
  
              /**
               * Clean up state, unregister listeners and update wifi state.
               */
              @Override
              public void exit() {
                  mClientModeImpl.setOperationalMode(ClientModeImpl.DISABLED_MODE, null);
  
                  if (mClientInterfaceName != null) {
                      mWifiNative.teardownInterface(mClientInterfaceName);
                      mClientInterfaceName = null;
                      mIfaceIsUp = false;
                  }
  
                  updateWifiState(WifiManager.WIFI_STATE_DISABLED,
                                  WifiManager.WIFI_STATE_DISABLING);
  
                  // once we leave started, nothing else to do...  stop the state machine
                  mStateMachine.quitNow();
              }
          }
      }
  }
  