diff options
author | Ahmed ElArabawy <arabawy@google.com> | 2019-02-11 20:13:02 -0800 |
---|---|---|
committer | Ahmed ElArabawy <arabawy@google.com> | 2019-02-13 16:45:05 -0800 |
commit | 0b7dfd2253af0f4713af3a7f6ca5f233f0fbf8b9 (patch) | |
tree | 2c3c3edd2f3c46abe9aa02ab77f045b24b71c003 | |
parent | f89adbf3e7b2d56b8806ce2bd797dc9b2aec3526 (diff) |
Support SAR for OTT VOWifi Apps
Current SAR implementation only supports power backoff for cellular calls.
This commit extends the power backoff support to OTT VoIP apps as well.
Bug: 124163143
Test: atest com.android.server.wifi
Test: Manual
Change-Id: I3fa51852f9bd022749011135b4c1ec299204b64a
6 files changed, 202 insertions, 6 deletions
diff --git a/service/java/com/android/server/wifi/SarInfo.java b/service/java/com/android/server/wifi/SarInfo.java index a62307e13..7d580656b 100644 --- a/service/java/com/android/server/wifi/SarInfo.java +++ b/service/java/com/android/server/wifi/SarInfo.java @@ -74,6 +74,7 @@ public class SarInfo { public boolean isWifiSapEnabled = false; public boolean isWifiScanOnlyEnabled = false; public boolean isVoiceCall = false; + public boolean isEarPieceActive = false; public int attemptedSarScenario = RESET_SAR_SCENARIO; private boolean mAllWifiDisabled = true; @@ -82,6 +83,7 @@ public class SarInfo { private int mLastReportedSensorState = SAR_SENSOR_FREE_SPACE; private boolean mLastReportedIsWifiSapEnabled = false; private boolean mLastReportedIsVoiceCall = false; + private boolean mLastReportedIsEarPieceActive = false; private int mLastReportedScenario = INITIAL_SAR_SCENARIO; private long mLastReportedScenarioTs = 0; @@ -113,7 +115,8 @@ public class SarInfo { /* Check if some change happened since last successful reporting */ if ((sensorState != mLastReportedSensorState) || (isWifiSapEnabled != mLastReportedIsWifiSapEnabled) - || (isVoiceCall != mLastReportedIsVoiceCall)) { + || (isVoiceCall != mLastReportedIsVoiceCall) + || (isEarPieceActive != mLastReportedIsEarPieceActive)) { return true; } else { return false; @@ -129,6 +132,7 @@ public class SarInfo { mLastReportedSensorState = sensorState; mLastReportedIsWifiSapEnabled = isWifiSapEnabled; mLastReportedIsVoiceCall = isVoiceCall; + mLastReportedIsEarPieceActive = isEarPieceActive; mLastReportedScenario = attemptedSarScenario; mLastReportedScenarioTs = System.currentTimeMillis(); @@ -169,10 +173,12 @@ public class SarInfo { pw.println(" Wifi Client state is: " + isWifiClientEnabled); pw.println(" Wifi Soft AP state is: " + isWifiSapEnabled); pw.println(" Wifi ScanOnly state is: " + isWifiScanOnlyEnabled); + pw.println(" Earpiece state is : " + isEarPieceActive); pw.println("Last reported values:"); pw.println(" Sensor state is: " + sensorStateToString(mLastReportedSensorState)); pw.println(" Soft AP state is: " + mLastReportedIsWifiSapEnabled); pw.println(" Voice Call state is: " + mLastReportedIsVoiceCall); + pw.println(" Earpiece state is: " + mLastReportedIsEarPieceActive); pw.println("Last reported scenario: " + mLastReportedScenario); pw.println("Reported " + (System.currentTimeMillis() - mLastReportedScenarioTs) / 1000 + " seconds ago"); diff --git a/service/java/com/android/server/wifi/SarManager.java b/service/java/com/android/server/wifi/SarManager.java index 598e5c964..f0319fb6b 100644 --- a/service/java/com/android/server/wifi/SarManager.java +++ b/service/java/com/android/server/wifi/SarManager.java @@ -20,12 +20,18 @@ 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; @@ -33,6 +39,7 @@ 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; @@ -49,6 +56,8 @@ import java.util.List; * - 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; @@ -66,6 +75,10 @@ public class SarManager { 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. */ @@ -75,6 +88,7 @@ public class SarManager { private final WifiNative mWifiNative; private final SarSensorEventListener mSensorListener; private final SensorManager mSensorManager; + private final Handler mHandler; private final Looper mLooper; /** @@ -89,6 +103,7 @@ public class SarManager { mTelephonyManager = telephonyManager; mWifiNative = wifiNative; mLooper = looper; + mHandler = new WifiHandler(TAG, looper); mSensorManager = sensorManager; mPhoneStateListener = new WifiPhoneStateListener(looper); mSensorListener = new SarSensorEventListener(); @@ -101,6 +116,80 @@ public class SarManager { } } + /** + * 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); @@ -145,6 +234,7 @@ public class SarManager { if (mSupportSarVoiceCall) { /* Listen for Phone State changes */ registerPhoneStateListener(); + registerVoiceStreamListener(); } /* Only listen for SAR sensor if supported */ @@ -159,6 +249,56 @@ public class SarManager { } } + 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. */ @@ -277,6 +417,10 @@ public class SarManager { /* Report change to HAL if needed */ if (mSarInfo.isVoiceCall != newIsVoiceCall) { mSarInfo.isVoiceCall = newIsVoiceCall; + + if (mVerboseLoggingEnabled) { + Log.d(TAG, "Voice Call = " + newIsVoiceCall); + } updateSarScenario(); } } diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java index 67b1ad6e7..390a10238 100644 --- a/service/java/com/android/server/wifi/WifiStateMachine.java +++ b/service/java/com/android/server/wifi/WifiStateMachine.java @@ -2392,6 +2392,8 @@ public class WifiStateMachine extends StateMachine { mWifiConnectivityManager.handleScreenStateChanged(screenOn); } + mSarManager.handleScreenStateChanged(screenOn); + if (mVerboseLoggingEnabled) log("handleScreenStateChanged Exit: " + screenOn); } diff --git a/service/java/com/android/server/wifi/WifiVendorHal.java b/service/java/com/android/server/wifi/WifiVendorHal.java index fad90d140..cff9a91e7 100644 --- a/service/java/com/android/server/wifi/WifiVendorHal.java +++ b/service/java/com/android/server/wifi/WifiVendorHal.java @@ -2664,7 +2664,7 @@ public class WifiVendorHal { /* As long as no voice call is active (in case voice call is supported), * no backoff is needed */ if (sarInfo.sarVoiceCallSupported) { - return sarInfo.isVoiceCall; + return (sarInfo.isVoiceCall || sarInfo.isEarPieceActive); } else { return false; } @@ -2679,7 +2679,7 @@ public class WifiVendorHal { * Otherwise, an exception is thrown. */ private int frameworkToHalTxPowerScenario_1_1(SarInfo sarInfo) { - if (sarInfo.sarVoiceCallSupported && sarInfo.isVoiceCall) { + if (sarInfo.sarVoiceCallSupported && (sarInfo.isVoiceCall || sarInfo.isEarPieceActive)) { return android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL; } else { throw new IllegalArgumentException("bad scenario: voice call not active/supported"); @@ -2703,7 +2703,7 @@ public class WifiVendorHal { if (sarInfo.sarSapSupported && sarInfo.isWifiSapEnabled) { return true; } - if (sarInfo.sarVoiceCallSupported && sarInfo.isVoiceCall) { + if (sarInfo.sarVoiceCallSupported && (sarInfo.isVoiceCall || sarInfo.isEarPieceActive)) { return true; } return false; @@ -2752,7 +2752,7 @@ public class WifiVendorHal { throw new IllegalArgumentException("bad scenario: Invalid sensor state"); } } else if (sarInfo.sarSapSupported && sarInfo.sarVoiceCallSupported) { - if (sarInfo.isVoiceCall) { + if (sarInfo.isVoiceCall || sarInfo.isEarPieceActive) { return android.hardware.wifi.V1_2.IWifiChip .TxPowerScenario.ON_HEAD_CELL_ON; } else if (sarInfo.isWifiSapEnabled) { @@ -2763,7 +2763,7 @@ public class WifiVendorHal { } } else if (sarInfo.sarVoiceCallSupported) { /* SAR Sensors and SoftAP not supported, act like V1_1 */ - if (sarInfo.isVoiceCall) { + if (sarInfo.isVoiceCall || sarInfo.isEarPieceActive) { return android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL; } else { throw new IllegalArgumentException("bad scenario: voice call not active"); diff --git a/tests/wifitests/src/com/android/server/wifi/SarInfoTest.java b/tests/wifitests/src/com/android/server/wifi/SarInfoTest.java index 2bf1f3ee4..50aec5917 100644 --- a/tests/wifitests/src/com/android/server/wifi/SarInfoTest.java +++ b/tests/wifitests/src/com/android/server/wifi/SarInfoTest.java @@ -231,6 +231,21 @@ public class SarInfoTest { } /** + * Test a change in earpiece status, shouldReport should return true + * Note: will need to report once before making the change to remove + * the effect of sensor state change. + */ + @Test + public void testSarInfo_earpiece_wifi_enabled() throws Exception { + mSarInfo.isWifiClientEnabled = true; + assertTrue(mSarInfo.shouldReport()); + mSarInfo.reportingSuccessful(); + + mSarInfo.isEarPieceActive = true; + assertTrue(mSarInfo.shouldReport()); + } + + /** * Test starting SAP, shouldReport should return true * Note: will need to report once before starting SAP to remove * the effect of sensor state change. diff --git a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java index f089ebbe7..ebeecbd28 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java @@ -2259,6 +2259,35 @@ public class WifiVendorHalTest { } /** + * Test the selectTxPowerScenario HIDL method invocation with no sensor support, but with + * SAP and voice call support. + * When earpiece is active, should result in cell with near head scenario + * Using IWifiChip 1.2 interface + */ + @Test + public void testEarPieceScenarios_SelectTxPowerV1_2() throws RemoteException { + // Create a SAR info record (with sensor and SAP support) + SarInfo sarInfo = new SarInfo(); + sarInfo.sarVoiceCallSupported = true; + sarInfo.sarSapSupported = true; + sarInfo.sarSensorSupported = false; + + sarInfo.isEarPieceActive = true; + + // Expose the 1.2 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess); + + // ON_HEAD_CELL_ON + assertTrue(mWifiVendorHal.startVendorHalSta()); + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV12).selectTxPowerScenario_1_2( + eq(android.hardware.wifi.V1_2.IWifiChip.TxPowerScenario.ON_HEAD_CELL_ON)); + verify(mIWifiChipV12, never()).resetTxPowerScenario(); + mWifiVendorHal.stopVendorHal(); + } + + /** * Test the selectTxPowerScenario HIDL method invocation with sensor related scenarios * to IWifiChip 1.2 interface */ |