content: /*
   * Copyright (C) 2018 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 static android.telephony.TelephonyManager.CALL_STATE_IDLE;
  import static android.telephony.TelephonyManager.CALL_STATE_OFFHOOK;
  import static android.telephony.TelephonyManager.CALL_STATE_RINGING;
  
  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.hardware.Sensor;
  import android.hardware.SensorEvent;
  import android.hardware.SensorEventListener;
  import android.hardware.SensorManager;
  import android.media.AudioManager;
  import android.media.AudioSystem;
  import android.net.wifi.WifiManager;
  import android.os.Handler;
  import android.os.Looper;
  import android.telephony.PhoneStateListener;
  import android.telephony.TelephonyManager;
  import android.text.TextUtils;
  import android.util.Log;
  
  import com.android.internal.R;
  import com.android.server.wifi.util.WifiHandler;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.List;
  
  /**
   * This class provides the Support for SAR to control WiFi TX power limits.
   * It deals with the following:
   * - Tracking the STA state through calls from  the ClientModeManager.
   * - Tracking the SAP state through calls from SoftApManager
   * - Tracking the Scan-Only state through ScanOnlyModeManager
   * - Tracking the state of the Cellular calls or data.
   * - Tracking the sensor indicating proximity to user head/hand/body.
   * - It constructs the sar info and send it towards the HAL
   */
  public class SarManager {
      // Period for checking on voice steam active (in ms)
      private static final int CHECK_VOICE_STREAM_INTERVAL_MS = 5000;
      /* For Logging */
      private static final String TAG = "WifiSarManager";
      private boolean mVerboseLoggingEnabled = true;
  
      private SarInfo mSarInfo;
  
      /* Configuration for SAR support */
      private boolean mSupportSarTxPowerLimit;
      private boolean mSupportSarVoiceCall;
      private boolean mSupportSarSoftAp;
      private boolean mSupportSarSensor;
      /* Sensor event definitions */
      private int mSarSensorEventFreeSpace;
      private int mSarSensorEventNearBody;
      private int mSarSensorEventNearHand;
      private int mSarSensorEventNearHead;
  
      // Device starts with screen on
      private boolean mScreenOn = false;
      private boolean mIsVoiceStreamCheckEnabled = false;
  
      /**
       * Other parameters passed in or created in the constructor.
       */
      private final Context mContext;
      private final TelephonyManager mTelephonyManager;
      private final WifiPhoneStateListener mPhoneStateListener;
      private final WifiNative mWifiNative;
      private final SarSensorEventListener mSensorListener;
      private final SensorManager mSensorManager;
      private final Handler mHandler;
      private final Looper mLooper;
      private final WifiMetrics mWifiMetrics;
  
      /**
       * Create new instance of SarManager.
       */
      SarManager(Context context,
                 TelephonyManager telephonyManager,
                 Looper looper,
                 WifiNative wifiNative,
                 SensorManager sensorManager,
                 WifiMetrics wifiMetrics) {
          mContext = context;
          mTelephonyManager = telephonyManager;
          mWifiNative = wifiNative;
          mLooper = looper;
          mHandler = new WifiHandler(TAG, looper);
          mSensorManager = sensorManager;
          mWifiMetrics = wifiMetrics;
          mPhoneStateListener = new WifiPhoneStateListener(looper);
          mSensorListener = new SarSensorEventListener();
  
          readSarConfigs();
          if (mSupportSarTxPowerLimit) {
              mSarInfo = new SarInfo();
              setSarConfigsInInfo();
              registerListeners();
          }
      }
  
      /**
       * Notify SarManager of screen status change
       */
      public void handleScreenStateChanged(boolean screenOn) {
          if (!mSupportSarVoiceCall) {
              return;
          }
  
          if (mScreenOn == screenOn) {
              return;
          }
  
          if (mVerboseLoggingEnabled) {
              Log.d(TAG, "handleScreenStateChanged: screenOn = " + screenOn);
          }
  
          mScreenOn = screenOn;
  
          // Only schedule a voice stream check if screen is turning on, and it is currently not
          // scheduled
          if (mScreenOn && !mIsVoiceStreamCheckEnabled) {
              mHandler.post(() -> {
                  checkAudioDevice();
              });
  
              mIsVoiceStreamCheckEnabled = true;
          }
      }
  
      private boolean isVoiceCallOnEarpiece() {
          AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
  
          return (audioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)
                  == AudioManager.DEVICE_OUT_EARPIECE);
      }
  
      private boolean isVoiceCallStreamActive() {
          return AudioSystem.isStreamActive(AudioManager.STREAM_VOICE_CALL, 0);
      }
  
      private void checkAudioDevice() {
          // First Check if audio stream is on
          boolean voiceStreamActive = isVoiceCallStreamActive();
          boolean earPieceActive;
  
          if (voiceStreamActive) {
              // Check on the audio route
              earPieceActive = isVoiceCallOnEarpiece();
  
              if (mVerboseLoggingEnabled) {
                  Log.d(TAG, "EarPiece active = " + earPieceActive);
              }
          } else {
              earPieceActive = false;
          }
  
          // If audio route has changed, update SAR
          if (earPieceActive != mSarInfo.isEarPieceActive) {
              mSarInfo.isEarPieceActive = earPieceActive;
              updateSarScenario();
          }
  
          // Now should we proceed with the checks
          if (!mScreenOn && !voiceStreamActive) {
              // No need to continue checking
              mIsVoiceStreamCheckEnabled = false;
          } else {
              // Schedule another check
              mHandler.postDelayed(() -> {
                  checkAudioDevice();
              }, CHECK_VOICE_STREAM_INTERVAL_MS);
          }
      }
  
      private void readSarConfigs() {
          mSupportSarTxPowerLimit = mContext.getResources().getBoolean(
                  R.bool.config_wifi_framework_enable_sar_tx_power_limit);
          /* In case SAR is disabled,
             then all SAR inputs are automatically disabled as well (irrespective of the config) */
          if (!mSupportSarTxPowerLimit) {
              mSupportSarVoiceCall = false;
              mSupportSarSoftAp = false;
              mSupportSarSensor = false;
              return;
          }
  
          /* Voice calls are supported when SAR is supported */
          mSupportSarVoiceCall = true;
  
          mSupportSarSoftAp = mContext.getResources().getBoolean(
                  R.bool.config_wifi_framework_enable_soft_ap_sar_tx_power_limit);
  
          mSupportSarSensor = mContext.getResources().getBoolean(
                  R.bool.config_wifi_framework_enable_body_proximity_sar_tx_power_limit);
  
          /* Read the sar sensor event Ids */
          if (mSupportSarSensor) {
              mSarSensorEventFreeSpace = mContext.getResources().getInteger(
                      R.integer.config_wifi_framework_sar_free_space_event_id);
              mSarSensorEventNearBody = mContext.getResources().getInteger(
                      R.integer.config_wifi_framework_sar_near_body_event_id);
              mSarSensorEventNearHand = mContext.getResources().getInteger(
                      R.integer.config_wifi_framework_sar_near_hand_event_id);
              mSarSensorEventNearHead = mContext.getResources().getInteger(
                      R.integer.config_wifi_framework_sar_near_head_event_id);
          }
      }
  
      private void setSarConfigsInInfo() {
          mSarInfo.sarVoiceCallSupported = mSupportSarVoiceCall;
          mSarInfo.sarSapSupported = mSupportSarSoftAp;
          mSarInfo.sarSensorSupported = mSupportSarSensor;
      }
  
      private void registerListeners() {
          if (mSupportSarVoiceCall) {
              /* Listen for Phone State changes */
              registerPhoneStateListener();
              registerVoiceStreamListener();
          }
  
          /* Only listen for SAR sensor if supported */
          if (mSupportSarSensor) {
              /* Register the SAR sensor listener.
               * If this fails, we will assume worst case (near head) */
              if (!registerSensorListener()) {
                  Log.e(TAG, "Failed to register sensor listener, setting Sensor to NearHead");
                  mSarInfo.sensorState = SarInfo.SAR_SENSOR_NEAR_HEAD;
                  mWifiMetrics.incrementNumSarSensorRegistrationFailures();
              }
          }
      }
  
      private void registerVoiceStreamListener() {
          Log.i(TAG, "Registering for voice stream status");
  
          // Register for listening to transitions of change of voice stream devices
          IntentFilter filter = new IntentFilter();
          filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
  
          mContext.registerReceiver(
                  new BroadcastReceiver() {
                      @Override
                      public void onReceive(Context context, Intent intent) {
                          boolean voiceStreamActive = isVoiceCallStreamActive();
                          if (!voiceStreamActive) {
                              // No need to proceed, there is no voice call ongoing
                              return;
                          }
  
                          String action = intent.getAction();
                          int streamType =
                                  intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                          int device = intent.getIntExtra(
                                  AudioManager.EXTRA_VOLUME_STREAM_DEVICES, -1);
                          int oldDevice = intent.getIntExtra(
                                  AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, -1);
  
                          if (streamType == AudioManager.STREAM_VOICE_CALL) {
                              boolean earPieceActive = mSarInfo.isEarPieceActive;
                              if (device == AudioManager.DEVICE_OUT_EARPIECE) {
                                  if (mVerboseLoggingEnabled) {
                                      Log.d(TAG, "Switching to earpiece : HEAD ON");
                                      Log.d(TAG, "Old device = " + oldDevice);
                                  }
                                  earPieceActive = true;
                              } else if (oldDevice == AudioManager.DEVICE_OUT_EARPIECE) {
                                  if (mVerboseLoggingEnabled) {
                                      Log.d(TAG, "Switching from earpiece : HEAD OFF");
                                      Log.d(TAG, "New device = " + device);
                                  }
                                  earPieceActive = false;
                              }
  
                              if (earPieceActive != mSarInfo.isEarPieceActive) {
                                  mSarInfo.isEarPieceActive = earPieceActive;
                                  updateSarScenario();
                              }
                          }
                      }
                  }, filter, null, mHandler);
      }
  
      /**
       * Register the phone state listener.
       */
      private void registerPhoneStateListener() {
          Log.i(TAG, "Registering for telephony call state changes");
          mTelephonyManager.listen(
                  mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
      }
  
      /**
       * Register the body/hand/head proximity sensor.
       */
      private boolean registerSensorListener() {
          Log.i(TAG, "Registering for Sensor notification Listener");
          return mSensorListener.register();
      }
  
      /**
       * Update Wifi Client State
       */
      public void setClientWifiState(int state) {
          boolean newIsEnabled;
          /* No action is taken if SAR is not supported */
          if (!mSupportSarTxPowerLimit) {
              return;
          }
  
          if (state == WifiManager.WIFI_STATE_DISABLED) {
              newIsEnabled = false;
          } else if (state == WifiManager.WIFI_STATE_ENABLED) {
              newIsEnabled = true;
          } else {
              /* No change so exiting with no action */
              return;
          }
  
          /* Report change to HAL if needed */
          if (mSarInfo.isWifiClientEnabled != newIsEnabled) {
              mSarInfo.isWifiClientEnabled = newIsEnabled;
              updateSarScenario();
          }
      }
  
      /**
       * Update Wifi SoftAP State
       */
      public void setSapWifiState(int state) {
          boolean newIsEnabled;
          /* No action is taken if SAR is not supported */
          if (!mSupportSarTxPowerLimit) {
              return;
          }
  
          if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
              newIsEnabled = false;
          } else if (state == WifiManager.WIFI_AP_STATE_ENABLED) {
              newIsEnabled = true;
          } else {
              /* No change so exiting with no action */
              return;
          }
  
          /* Report change to HAL if needed */
          if (mSarInfo.isWifiSapEnabled != newIsEnabled) {
              mSarInfo.isWifiSapEnabled = newIsEnabled;
              updateSarScenario();
          }
      }
  
      /**
       * Update Wifi ScanOnly State
       */
      public void setScanOnlyWifiState(int state) {
          boolean newIsEnabled;
          /* No action is taken if SAR is not supported */
          if (!mSupportSarTxPowerLimit) {
              return;
          }
  
          if (state == WifiManager.WIFI_STATE_DISABLED) {
              newIsEnabled = false;
          } else if (state == WifiManager.WIFI_STATE_ENABLED) {
              newIsEnabled = true;
          } else {
              /* No change so exiting with no action */
              return;
          }
  
          /* Report change to HAL if needed */
          if (mSarInfo.isWifiScanOnlyEnabled != newIsEnabled) {
              mSarInfo.isWifiScanOnlyEnabled = newIsEnabled;
              updateSarScenario();
          }
      }
  
      /**
       * Report Cell state event
       */
      private void onCellStateChangeEvent(int state) {
          boolean newIsVoiceCall;
          switch (state) {
              case CALL_STATE_OFFHOOK:
              case CALL_STATE_RINGING:
                  newIsVoiceCall = true;
                  break;
  
              case CALL_STATE_IDLE:
                  newIsVoiceCall = false;
                  break;
  
              default:
                  Log.e(TAG, "Invalid Cell State: " + state);
                  return;
          }
  
          /* Report change to HAL if needed */
          if (mSarInfo.isVoiceCall != newIsVoiceCall) {
              mSarInfo.isVoiceCall = newIsVoiceCall;
  
              if (mVerboseLoggingEnabled) {
                  Log.d(TAG, "Voice Call = " + newIsVoiceCall);
              }
              updateSarScenario();
          }
      }
  
      /**
       * Report an event from the SAR sensor
       */
      private void onSarSensorEvent(int sarSensorEvent) {
          int newSensorState;
          if (sarSensorEvent == mSarSensorEventFreeSpace) {
              newSensorState = SarInfo.SAR_SENSOR_FREE_SPACE;
          } else if (sarSensorEvent == mSarSensorEventNearBody) {
              newSensorState = SarInfo.SAR_SENSOR_NEAR_BODY;
          } else if (sarSensorEvent == mSarSensorEventNearHand) {
              newSensorState = SarInfo.SAR_SENSOR_NEAR_HAND;
          } else if (sarSensorEvent == mSarSensorEventNearHead) {
              newSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD;
          } else {
              Log.e(TAG, "Invalid SAR sensor event id: " + sarSensorEvent);
              return;
          }
  
          /* Report change to HAL if needed */
          if (mSarInfo.sensorState != newSensorState) {
              Log.d(TAG, "Setting Sensor state to " + SarInfo.sensorStateToString(newSensorState));
              mSarInfo.sensorState = newSensorState;
              updateSarScenario();
          }
      }
  
      /**
       * Enable/disable verbose logging.
       */
      public void enableVerboseLogging(int verbose) {
          if (verbose > 0) {
              mVerboseLoggingEnabled = true;
          } else {
              mVerboseLoggingEnabled = false;
          }
      }
  
      /**
       * dump()
       * Dumps SarManager state (as well as its SarInfo member variable state)
       */
      public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
          pw.println("Dump of SarManager");
          pw.println("isSarSupported: " + mSupportSarTxPowerLimit);
          pw.println("isSarVoiceCallSupported: " + mSupportSarVoiceCall);
          pw.println("isSarSoftApSupported: " + mSupportSarSoftAp);
          pw.println("isSarSensorSupported: " + mSupportSarSensor);
          pw.println("");
          if (mSarInfo != null) {
              mSarInfo.dump(fd, pw, args);
          }
      }
  
      /**
       * Listen for phone call state events to set/reset TX power limits for SAR requirements.
       */
      private class WifiPhoneStateListener extends PhoneStateListener {
          WifiPhoneStateListener(Looper looper) {
              super(looper);
          }
  
          /**
           * onCallStateChanged()
           * This callback is called when a SAR sensor event is received
           * Note that this runs in the WifiCoreHandlerThread
           * since the corresponding Looper was passed to the WifiPhoneStateListener constructor.
           */
          @Override
          public void onCallStateChanged(int state, String incomingNumber) {
              Log.d(TAG, "Received Phone State Change: " + state);
  
              /* In case of an unsolicited event */
              if (!mSupportSarTxPowerLimit || !mSupportSarVoiceCall) {
                  return;
              }
              onCellStateChangeEvent(state);
          }
      }
  
      private class SarSensorEventListener implements SensorEventListener {
  
          private Sensor mSensor;
  
          /**
           * Register the SAR listener to get SAR sensor events
           */
          private boolean register() {
              /* Get the sensor type from configuration */
              String sensorType = mContext.getResources().getString(
                      R.string.config_wifi_sar_sensor_type);
              if (TextUtils.isEmpty(sensorType)) {
                  Log.e(TAG, "Empty SAR sensor type");
                  return false;
              }
  
              /* Get the sensor object */
              Sensor sensor = null;
              List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
              for (Sensor s : sensorList) {
                  if (sensorType.equals(s.getStringType())) {
                      sensor = s;
                      break;
                  }
              }
              if (sensor == null) {
                  Log.e(TAG, "Failed to Find the SAR Sensor");
                  return false;
              }
  
              /* Now register the listener */
              if (!mSensorManager.registerListener(this, sensor,
                      SensorManager.SENSOR_DELAY_NORMAL)) {
                  Log.e(TAG, "Failed to register SAR Sensor Listener");
                  return false;
              }
  
              return true;
          }
  
          /**
           * onSensorChanged()
           * This callback is called when a SAR sensor event is received
           * Note that this runs in the WifiCoreHandlerThread
           * since, the corresponding Looper was passed to the SensorManager instance.
           */
          @Override
          public void onSensorChanged(SensorEvent event) {
              onSarSensorEvent((int) event.values[0]);
          }
  
          @Override
          public void onAccuracyChanged(Sensor sensor, int accuracy) {
          }
      }
  
      /**
       * updateSarScenario()
       * Update HAL with the new SAR scenario if needed.
       */
      private void updateSarScenario() {
          if (!mSarInfo.shouldReport()) {
              return;
          }
  
          /* Report info to HAL*/
          if (mWifiNative.selectTxPowerScenario(mSarInfo)) {
              mSarInfo.reportingSuccessful();
          } else {
              Log.e(TAG, "Failed in WifiNative.selectTxPowerScenario()");
          }
  
          return;
      }
  }
  