blob: a35f65f00b0167190763ea38b9133fc537493c2a [file] [log] [blame]
/*
* 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;
}
}