summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAhmed ElArabawy <arabawy@google.com>2019-02-11 20:13:02 -0800
committerAhmed ElArabawy <arabawy@google.com>2019-02-13 16:45:05 -0800
commit0b7dfd2253af0f4713af3a7f6ca5f233f0fbf8b9 (patch)
tree2c3c3edd2f3c46abe9aa02ab77f045b24b71c003
parentf89adbf3e7b2d56b8806ce2bd797dc9b2aec3526 (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
-rw-r--r--service/java/com/android/server/wifi/SarInfo.java8
-rw-r--r--service/java/com/android/server/wifi/SarManager.java144
-rw-r--r--service/java/com/android/server/wifi/WifiStateMachine.java2
-rw-r--r--service/java/com/android/server/wifi/WifiVendorHal.java10
-rw-r--r--tests/wifitests/src/com/android/server/wifi/SarInfoTest.java15
-rw-r--r--tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java29
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
*/