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