| /* |
| * 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; |
| } |
| } |