From 7db85489ea6deb3658ed6fc4895622e7a5ec3b18 Mon Sep 17 00:00:00 2001 From: Ahmed ElArabawy Date: Wed, 23 May 2018 13:51:18 -0700 Subject: WiFi: Extend SAR in WiFi with body sensors In previous commits, extending the SAR support of SAR sensors was introduced by adding the SarInfo class and its use in WifiVendorHal to select the proper SAR scenario. However, SarManager was not utilizing this new functionality. This commit provides the support of SarManager to the support for SAR for WiFi TX power backoff when device is in close proximity to head/hand/body of user with or without the presence of a voice call. Bug: 65174506 Test: Run Wifi unit test suite Test: ./frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: Iac650f586a1419540a6fbef4b75579391fda1aba Signed-off-by: Ahmed ElArabawy --- .../java/com/android/server/wifi/SarManager.java | 298 +++++++++---- .../java/com/android/server/wifi/WifiInjector.java | 3 +- .../com/android/server/wifi/SarManagerTest.java | 493 +++++++++++++++++++-- 3 files changed, 656 insertions(+), 138 deletions(-) diff --git a/service/java/com/android/server/wifi/SarManager.java b/service/java/com/android/server/wifi/SarManager.java index da48a8537..f157ac41e 100644 --- a/service/java/com/android/server/wifi/SarManager.java +++ b/service/java/com/android/server/wifi/SarManager.java @@ -20,14 +20,16 @@ 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.net.wifi.WifiManager; 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; @@ -41,23 +43,25 @@ import java.util.List; * It deals with the following: * - Tracking the STA state through calls from the ClientModeManager. * - Tracking the state of the Cellular calls or data. - * - Based on above, selecting the SAR profile to use and programming it in wifi hal. + * - Tracking the sensor indicating proximity to user head/hand/body. + * - It constructs the sar info and send it towards the HAL */ public class SarManager { - /* For Logging */ private static final String TAG = "WifiSarManager"; private boolean mVerboseLoggingEnabled = true; + private SarInfo mSarInfo; + /* Configuration for SAR */ private boolean mEnableSarTxPowerLimit; + private boolean mEnableSarBodyProximity; + /* Sensor event definitions */ + private int mSarSensorEventFreeSpace; + private int mSarSensorEventNearBody; + private int mSarSensorEventNearHand; + private int mSarSensorEventNearHead; - /* Current SAR Scenario */ - private int mCurrentSarScenario = WifiNative.TX_POWER_SCENARIO_NORMAL; - - /* Booleans for Cell and wifi states */ - private boolean mCellOn = false; - private boolean mWifiStaEnabled = false; /** * Other parameters passed in or created in the constructor. */ @@ -65,6 +69,8 @@ public class SarManager { private final TelephonyManager mTelephonyManager; private final WifiPhoneStateListener mPhoneStateListener; private final WifiNative mWifiNative; + private final SarSensorEventListener mSensorListener; + private final SensorManager mSensorManager; private final Looper mLooper; /** @@ -73,30 +79,105 @@ public class SarManager { SarManager(Context context, TelephonyManager telephonyManager, Looper looper, - WifiNative wifiNative) { + WifiNative wifiNative, + SensorManager sensorManager) { mContext = context; mTelephonyManager = telephonyManager; mWifiNative = wifiNative; mLooper = looper; + mSensorManager = sensorManager; mPhoneStateListener = new WifiPhoneStateListener(looper); + mSensorListener = new SarSensorEventListener(); + + readSarConfigs(); + if (mEnableSarTxPowerLimit) { + mSarInfo = new SarInfo(mEnableSarBodyProximity); + registerListeners(); + } + } + + private void readSarConfigs() { + mEnableSarTxPowerLimit = mContext.getResources().getBoolean( + R.bool.config_wifi_framework_enable_sar_tx_power_limit); + /* In case SAR is disabled, + then SAR sensor is automatically disabled as well (irrespective of the config) */ + if (!mEnableSarTxPowerLimit) { + mEnableSarBodyProximity = false; + return; + } - registerListeners(); + mEnableSarBodyProximity = mContext.getResources().getBoolean( + R.bool.config_wifi_framework_enable_body_proximity_sar_tx_power_limit); + + /* Read the sar sensor event Ids */ + if (mEnableSarBodyProximity) { + 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 registerListeners() { + /* Listen for Phone State changes */ + registerPhoneStateListener(); + + /* Only listen for SAR sensor if supported */ + if (mEnableSarBodyProximity) { + /* 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"); + /*TODO Need to add a metric to determine how often this happens */ + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + } + } } /** - * Starts the SAR Manager by initializing the different listeners + * Register the phone state listener. */ - private void registerListeners() { - /* First read the configuration for SAR Support */ - mEnableSarTxPowerLimit = mContext.getResources().getBoolean( - R.bool.config_wifi_framework_enable_voice_call_sar_tx_power_limit); + private void registerPhoneStateListener() { + Log.i(TAG, "Registering for telephony call state changes"); + mTelephonyManager.listen( + mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); + } - /* Only Start listening for events if SAR is enabled */ - if (mEnableSarTxPowerLimit) { - Log.d(TAG, "Registering Listeners for the SAR Manager"); + /** + * 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 enabled */ + if (!mEnableSarTxPowerLimit) { + 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; + } - /* Listen for Phone State changes */ - registerPhoneListener(); + /* Report change to HAL if needed */ + if (mSarInfo.mIsWifiClientEnabled != newIsEnabled) { + mSarInfo.mIsWifiClientEnabled = newIsEnabled; + updateSarScenario(); } } @@ -104,42 +185,52 @@ public class SarManager { * Report Cell state event */ private void onCellStateChangeEvent(int state) { - boolean currentCellOn = mCellOn; - + boolean newIsVoiceCall; switch (state) { case CALL_STATE_OFFHOOK: case CALL_STATE_RINGING: - mCellOn = true; + newIsVoiceCall = true; break; case CALL_STATE_IDLE: - mCellOn = false; + newIsVoiceCall = false; break; default: Log.e(TAG, "Invalid Cell State: " + state); + return; } - if (mCellOn != currentCellOn) { + /* Report change to HAL if needed */ + if (mSarInfo.mIsVoiceCall != newIsVoiceCall) { + mSarInfo.mIsVoiceCall = newIsVoiceCall; updateSarScenario(); } } /** - * Update Wifi Client State + * Report an event from the SAR sensor */ - public void setClientWifiState(int state) { - /* No action is taken if SAR is not enabled */ - if (!mEnableSarTxPowerLimit) return; - - if (state == WifiManager.WIFI_STATE_DISABLED && mWifiStaEnabled) { - mWifiStaEnabled = false; - } else if (state == WifiManager.WIFI_STATE_ENABLED && !mWifiStaEnabled) { - mWifiStaEnabled = true; + 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; + } - /* Since no wifi interface was up, - time for SAR scenario to take effect */ - sendTxPowerScenario(mCurrentSarScenario); + /* Report change to HAL if needed */ + if (mSarInfo.mSensorState != newSensorState) { + Log.d(TAG, "Setting Sensor state to " + SarInfo.sensorStateToString(newSensorState)); + mSarInfo.mSensorState = newSensorState; + updateSarScenario(); } } @@ -155,18 +246,16 @@ public class SarManager { } } - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("*** WiFi SAR Manager Dump ***"); - pw.println("Current SAR Scenario is " + scenarioToString(mCurrentSarScenario)); - } - /** - * Register the phone listener. + * dump() + * Dumps SarManager state (as well as its SarInfo member variable state) */ - private void registerPhoneListener() { - Log.i(TAG, "Registering for telephony call state changes"); - mTelephonyManager.listen( - mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("*** WiFi SAR Manager Dump ***"); + pw.println("isSarEnabled: " + mEnableSarTxPowerLimit); + pw.println("isSarSensorEnabled: " + mEnableSarBodyProximity); + pw.println(""); + mSarInfo.dump(fd, pw, args); } /** @@ -177,69 +266,96 @@ public class SarManager { super(looper); } + /** + * onCallStateChanged() + * This callback is called when a SAR sensor event is received + * Note that this runs in the WifiStateMachineHandlerThread + * 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 (!mEnableSarTxPowerLimit) return; - + if (!mEnableSarTxPowerLimit) { + return; + } onCellStateChangeEvent(state); } } - /** - * update the Current SAR Scenario based on factors including: - * - Do we have an ongoing cellular voice call. - */ - private void updateSarScenario() { - int newSarScenario; + private class SarSensorEventListener implements SensorEventListener { - if (mCellOn) { - newSarScenario = WifiNative.TX_POWER_SCENARIO_VOICE_CALL; - } else { - newSarScenario = WifiNative.TX_POWER_SCENARIO_NORMAL; - } + 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; + } - if (newSarScenario != mCurrentSarScenario) { + /* 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; + } - // Only update HAL with new scenario if WiFi interface is enabled - if (mWifiStaEnabled) { - Log.d(TAG, "Sending SAR Scenario #" + scenarioToString(newSarScenario)); - sendTxPowerScenario(newSarScenario); + /* Now register the listener */ + if (!mSensorManager.registerListener(this, sensor, + SensorManager.SENSOR_DELAY_NORMAL)) { + Log.e(TAG, "Failed to register SAR Sensor Listener"); + return false; } - mCurrentSarScenario = newSarScenario; + return true; } - } - /** - * sendTxPowerScenario() - * Update HAL with the new power scenario. - */ - private void sendTxPowerScenario(int newSarScenario) { - if (!mWifiNative.selectTxPowerScenario(newSarScenario)) { - Log.e(TAG, "Failed to set TX power scenario"); + /** + * onSensorChanged() + * This callback is called when a SAR sensor event is received + * Note that this runs in the WifiStateMachineHandlerThread + * 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) { } } /** - * Convert SAR Scenario to string + * updateSarScenario() + * Update HAL with the new SAR scenario if needed. */ - private String scenarioToString(int scenario) { - String str; - switch(scenario) { - case WifiNative.TX_POWER_SCENARIO_NORMAL: - str = "TX_POWER_SCENARIO_NORMAL"; - break; - case WifiNative.TX_POWER_SCENARIO_VOICE_CALL: - str = "TX_POWER_SCENARIO_VOICE_CALL"; - break; - default: - str = "Invalid Scenario"; - break; + 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 str; + return; } } diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java index 0e30af841..5a4fe570b 100644 --- a/service/java/com/android/server/wifi/WifiInjector.java +++ b/service/java/com/android/server/wifi/WifiInjector.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.app.ActivityManager; import android.app.AppOpsManager; import android.content.Context; +import android.hardware.SystemSensorManager; import android.net.NetworkKey; import android.net.NetworkScoreManager; import android.net.wifi.IWifiScanner; @@ -253,7 +254,7 @@ public class WifiInjector { this, mWifiConfigManager, mWifiPermissionsUtil, mWifiMetrics, mClock); mSarManager = new SarManager(mContext, makeTelephonyManager(), wifiStateMachineLooper, - mWifiNative); + mWifiNative, new SystemSensorManager(mContext, wifiStateMachineLooper)); if (mUseRealLogger) { mWifiDiagnostics = new WifiDiagnostics( mContext, this, mWifiNative, mBuildProperties, diff --git a/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java index 163280a39..69878cdcf 100644 --- a/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java @@ -16,39 +16,44 @@ package com.android.server.wifi; +import static android.telephony.TelephonyManager.CALL_STATE_IDLE; +import static android.telephony.TelephonyManager.CALL_STATE_OFFHOOK; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.*; -import static android.telephony.TelephonyManager.CALL_STATE_IDLE; -import static android.telephony.TelephonyManager.CALL_STATE_OFFHOOK; - -import android.app.test.MockAnswerUtil.AnswerWithArguments; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.hardware.Sensor; import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SystemSensorManager; import android.net.wifi.WifiManager; import android.os.Build; import android.os.test.TestLooper; import android.support.test.filters.SmallTest; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; -import android.util.Log; import com.android.internal.R; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + /** * unit tests for {@link com.android.server.wifi.SarManager}. */ @@ -56,6 +61,12 @@ import org.mockito.MockitoAnnotations; public class SarManagerTest { private static final String TAG = "WifiSarManagerTest"; private static final String OP_PACKAGE_NAME = "com.xxx"; + private static final String SAR_SENSOR_NAME = "com.google.sensor.sar"; + + private static final int SAR_SENSOR_EVENT_FREE_SPACE = 1; + private static final int SAR_SENSOR_EVENT_HAND = 2; + private static final int SAR_SENSOR_EVENT_HEAD = 3; + private static final int SAR_SENSOR_EVENT_BODY = 4; private void enableDebugLogs() { mSarMgr.enableVerboseLogging(1); @@ -70,23 +81,26 @@ public class SarManagerTest { private TestLooper mLooper; private MockResources mResources; private PhoneStateListener mPhoneStateListener; + private List mSensorList; + private Sensor mSensor; + private SarInfo mSarInfo; - @Mock private Context mContext; + @Mock private Context mContext; + @Mock SensorEventListener mSensorEventListener; + @Mock SystemSensorManager mSensorManager; @Mock TelephonyManager mTelephonyManager; @Mock private ApplicationInfo mMockApplInfo; @Mock WifiNative mWifiNative; @Before public void setUp() throws Exception { - Log.e(TAG, "Setting Up ..."); - - // Ensure Looper exists + /* Ensure Looper exists */ mLooper = new TestLooper(); MockitoAnnotations.initMocks(this); /* Default behavior is to return with success */ - when(mWifiNative.selectTxPowerScenario(anyInt())).thenReturn(true); + when(mWifiNative.selectTxPowerScenario(any(SarInfo.class))).thenReturn(true); mResources = getMockResources(); @@ -104,16 +118,79 @@ public class SarManagerTest { mResources = null; } + /** + * Helper function to capture SarInfo object + */ + private void captureSarInfo(WifiNative wifiNative) { + /* Capture the SensorEventListener */ + ArgumentCaptor sarInfoCaptor = ArgumentCaptor.forClass(SarInfo.class); + verify(wifiNative).selectTxPowerScenario(sarInfoCaptor.capture()); + mSarInfo = sarInfoCaptor.getValue(); + assertNotNull(mSarInfo); + } + + /** + * Helper function to create and prepare sensor info + */ + private void prepareSensorInfo(boolean registerReturn) { + /* Create a sensor object (note, this can not be mocked since it is a final class) */ + Constructor constructor = + (Constructor) Sensor.class.getDeclaredConstructors()[0]; + constructor.setAccessible(true); + + try { + mSensor = constructor.newInstance(); + } catch (Exception e) { + fail("Failed to create a sensor object"); + } + + /* Now set the mStringType field with the proper field */ + Field declaredField = null; + try { + declaredField = Sensor.class.getDeclaredField("mStringType"); + declaredField.setAccessible(true); + declaredField.set(mSensor, SAR_SENSOR_NAME); + } catch (Exception e) { + fail("Could not set sensor string type"); + } + + /* Prepare the sensor list */ + mSensorList = new ArrayList(); + mSensorList.add(mSensor); + when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(mSensorList); + when(mSensorManager.registerListener(any(SensorEventListener.class), any(Sensor.class), + anyInt())).thenReturn(registerReturn); + } + /** * Helper function to set configuration for SAR and create the SAR Manager * */ - private void createSarManager(boolean isSarEnabled) { + private void createSarManager(boolean isSarEnabled, boolean isSarSensorEnabled) { mResources.setBoolean( - R.bool.config_wifi_framework_enable_voice_call_sar_tx_power_limit, isSarEnabled); + R.bool.config_wifi_framework_enable_sar_tx_power_limit, isSarEnabled); + mResources.setBoolean( + R.bool.config_wifi_framework_enable_body_proximity_sar_tx_power_limit, + isSarSensorEnabled); + mResources.setString(R.string.config_wifi_sar_sensor_type, SAR_SENSOR_NAME); + + /* Set the event id configs */ + mResources.setInteger(R.integer.config_wifi_framework_sar_free_space_event_id, + SAR_SENSOR_EVENT_FREE_SPACE); + mResources.setInteger(R.integer.config_wifi_framework_sar_near_hand_event_id, + SAR_SENSOR_EVENT_HAND); + mResources.setInteger(R.integer.config_wifi_framework_sar_near_head_event_id, + SAR_SENSOR_EVENT_HEAD); + mResources.setInteger(R.integer.config_wifi_framework_sar_near_body_event_id, + SAR_SENSOR_EVENT_BODY); + + /* Prepare sensor info only if SarSensorEnabled */ + if (isSarSensorEnabled) { + prepareSensorInfo(true); + } mSarMgr = new SarManager(mContext, mTelephonyManager, mLooper.getLooper(), - mWifiNative); + mWifiNative, mSensorManager); if (isSarEnabled) { /* Capture the PhoneStateListener */ @@ -122,19 +199,88 @@ public class SarManagerTest { verify(mTelephonyManager).listen(phoneStateListenerCaptor.capture(), eq(PhoneStateListener.LISTEN_CALL_STATE)); mPhoneStateListener = phoneStateListenerCaptor.getValue(); + assertNotNull(mPhoneStateListener); + } + + if (isSarSensorEnabled) { + /* Capture the SensorEventListener */ + ArgumentCaptor sensorEventListenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(sensorEventListenerCaptor.capture(), + any(Sensor.class), anyInt()); + mSensorEventListener = sensorEventListenerCaptor.getValue(); + assertNotNull(mSensorEventListener); + } + + /* Enable logs from SarManager */ + enableDebugLogs(); + } + + /** + * Helper function to create SarManager with some error cases for sensor handling + */ + private void createSarManagerSensorNegTest(String configSensorName, boolean addToConfigs, + boolean sensorRegisterReturn) { + mResources.setBoolean( + R.bool.config_wifi_framework_enable_sar_tx_power_limit, true); + mResources.setBoolean( + R.bool.config_wifi_framework_enable_body_proximity_sar_tx_power_limit, true); + if (addToConfigs) { + mResources.setString(R.string.config_wifi_sar_sensor_type, configSensorName); } + /* Set the event id configs */ + mResources.setInteger(R.integer.config_wifi_framework_sar_free_space_event_id, + SAR_SENSOR_EVENT_FREE_SPACE); + mResources.setInteger(R.integer.config_wifi_framework_sar_near_hand_event_id, + SAR_SENSOR_EVENT_HAND); + mResources.setInteger(R.integer.config_wifi_framework_sar_near_head_event_id, + SAR_SENSOR_EVENT_HEAD); + mResources.setInteger(R.integer.config_wifi_framework_sar_near_body_event_id, + SAR_SENSOR_EVENT_BODY); + + prepareSensorInfo(sensorRegisterReturn); + + mSarMgr = new SarManager(mContext, mTelephonyManager, mLooper.getLooper(), + mWifiNative, mSensorManager); + + /* Capture the PhoneStateListener */ + ArgumentCaptor phoneStateListenerCaptor = + ArgumentCaptor.forClass(PhoneStateListener.class); + verify(mTelephonyManager).listen(phoneStateListenerCaptor.capture(), + eq(PhoneStateListener.LISTEN_CALL_STATE)); + mPhoneStateListener = phoneStateListenerCaptor.getValue(); + assertNotNull(mPhoneStateListener); + /* Enable logs from SarManager */ enableDebugLogs(); } + /** + * Helper function to create and pass a sensor event + */ + private void sendSensorEvent(int eventId) { + SensorEvent event; + Constructor constructor = + (Constructor) SensorEvent.class.getDeclaredConstructors()[0]; + constructor.setAccessible(true); + + try { + event = constructor.newInstance(1); + event.values[0] = (float) eventId; + mSensorEventListener.onSensorChanged(event); + } catch (Exception e) { + fail("Failed to create a Sensor Event"); + } + } + /** * Test that we do register the telephony call state listener on devices which do support * setting/resetting Tx power limit. */ @Test public void testSarMgr_enabledTxPowerScenario_registerPhone() throws Exception { - createSarManager(true); + createSarManager(true, false); verify(mTelephonyManager).listen(any(), eq(PhoneStateListener.LISTEN_CALL_STATE)); } @@ -144,7 +290,7 @@ public class SarManagerTest { */ @Test public void testSarMgr_disabledTxPowerScenario_registerPhone() throws Exception { - createSarManager(false); + createSarManager(false, false); verify(mTelephonyManager, never()).listen(any(), anyInt()); } @@ -153,27 +299,24 @@ public class SarManagerTest { * Tx power scenario upon receiving {@link TelephonyManager#CALL_STATE_OFFHOOK} when WiFi STA * is enabled * In this case Wifi is enabled first, then off-hook is detected - * Expectation is to get {@link WifiNative#TX_POWER_SCENARIO_NORMAL} when WiFi is turned on - * followed by {@link WifiNative#TX_POWER_SCENARIO_VOICE_CALL} when OFFHOOK event is detected */ @Test public void testSarMgr_enabledTxPowerScenario_wifiOn_offHook() throws Exception { - createSarManager(true); - assertNotNull(mPhoneStateListener); + createSarManager(true, false); InOrder inOrder = inOrder(mWifiNative); /* Enable WiFi State */ mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); - inOrder.verify(mWifiNative).selectTxPowerScenario( - eq(WifiNative.TX_POWER_SCENARIO_NORMAL)); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); /* Set phone state to OFFHOOK */ mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); - - inOrder.verify(mWifiNative).selectTxPowerScenario( - eq(WifiNative.TX_POWER_SCENARIO_VOICE_CALL)); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertTrue(mSarInfo.mIsVoiceCall); } /** @@ -181,66 +324,66 @@ public class SarManagerTest { * Tx power scenario upon receiving {@link TelephonyManager#CALL_STATE_OFFHOOK} when WiFi STA * is enabled * In this case off-hook event is detected first, then wifi is turned on - * Expectation is to get {@link WifiNative#TX_POWER_SCENARIO_VOICE_CALL} once wifi is turned on */ @Test public void testSarMgr_enabledTxPowerScenario_offHook_wifiOn() throws Exception { - createSarManager(true); - assertNotNull(mPhoneStateListener); + createSarManager(true, false); + + InOrder inOrder = inOrder(mWifiNative); /* Set phone state to OFFHOOK */ mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); /* Enable WiFi State */ mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); - verify(mWifiNative).selectTxPowerScenario( - eq(WifiNative.TX_POWER_SCENARIO_VOICE_CALL)); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertTrue(mSarInfo.mIsVoiceCall); } /** * Test that for devices that support setting/resetting Tx Power limits, device sets the proper * Tx power scenarios upon receiving {@link TelephonyManager#CALL_STATE_OFFHOOK} and - * {@link TelephonyManager#CALL_STATE_OFFHOOK} when WiFi STA is enabled + * {@link TelephonyManager#CALL_STATE_IDLE} when WiFi STA is enabled */ @Test public void testSarMgr_enabledTxPowerScenario_wifiOn_offHook_onHook() throws Exception { - createSarManager(true); - assertNotNull(mPhoneStateListener); + createSarManager(true, false); InOrder inOrder = inOrder(mWifiNative); /* Enable WiFi State */ mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); /* Now device should set tx power scenario to NORMAL */ - inOrder.verify(mWifiNative).selectTxPowerScenario( - eq(WifiNative.TX_POWER_SCENARIO_NORMAL)); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); /* Set phone state to OFFHOOK */ mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); /* Device should set tx power scenario to Voice call */ - inOrder.verify(mWifiNative).selectTxPowerScenario( - eq(WifiNative.TX_POWER_SCENARIO_VOICE_CALL)); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertTrue(mSarInfo.mIsVoiceCall); /* Set state back to ONHOOK */ mPhoneStateListener.onCallStateChanged(CALL_STATE_IDLE, ""); /* Device should set tx power scenario to NORMAL again */ - inOrder.verify(mWifiNative).selectTxPowerScenario( - eq(WifiNative.TX_POWER_SCENARIO_NORMAL)); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); } /** * Test that for devices that support setting/resetting Tx Power limits, device does not * sets the Tx power scenarios upon receiving {@link TelephonyManager#CALL_STATE_OFFHOOK} and - * {@link TelephonyManager#CALL_STATE_OFFHOOK} when WiFi STA is disabled + * {@link TelephonyManager#CALL_STATE_IDLE} when WiFi STA is disabled */ @Test public void testSarMgr_enabledTxPowerScenario_wifiOff_offHook_onHook() throws Exception { - createSarManager(true); - assertNotNull(mPhoneStateListener); + createSarManager(true, false); InOrder inOrder = inOrder(mWifiNative); @@ -251,6 +394,264 @@ public class SarManagerTest { mPhoneStateListener.onCallStateChanged(CALL_STATE_IDLE, ""); /* Device should not set tx power scenario at all */ - inOrder.verify(mWifiNative, never()).selectTxPowerScenario(anyInt()); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + } + + /** + * Test that for a device that has SAR enabled, with sar sensor enabled, + * wifi enabled, Then Tx power scenarios follow events from sensor for body/hand/head/none + */ + @Test + public void testSarMgr_sarSensorOn_WifiOn_sensorEventsTriggered() throws Exception { + createSarManager(true, true); + + InOrder inOrder = inOrder(mWifiNative); + + /* Enable Wifi Client */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_FREE_SPACE, mSarInfo.mSensorState); + + /* Sensor event */ + sendSensorEvent(SAR_SENSOR_EVENT_BODY); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_BODY, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + + /* Sensor event */ + sendSensorEvent(SAR_SENSOR_EVENT_HEAD); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + + /* Sensor event */ + sendSensorEvent(SAR_SENSOR_EVENT_HAND); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HAND, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + + /* Sensor event */ + sendSensorEvent(SAR_SENSOR_EVENT_FREE_SPACE); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_FREE_SPACE, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + } + + /** + * Test that for a device that has SAR enabled, with sar sensor enabled, + * wifi enabled, cellOn, + * then Tx power scenarios follow events from sensor for body/hand/head/none + */ + @Test + public void testSarMgr_sarSensorOn_wifiOn_cellOn_sensorEventsTriggered() throws Exception { + createSarManager(true, true); + + InOrder inOrder = inOrder(mWifiNative); + + /* Enable Wifi Client */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + /* Should get the an event with no calls */ + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_FREE_SPACE, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + + /* Start a Cell call */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(any(SarInfo.class)); + assertEquals(SarInfo.SAR_SENSOR_FREE_SPACE, mSarInfo.mSensorState); + assertTrue(mSarInfo.mIsVoiceCall); + + /* Sensor event */ + sendSensorEvent(SAR_SENSOR_EVENT_BODY); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_BODY, mSarInfo.mSensorState); + assertTrue(mSarInfo.mIsVoiceCall); + + /* Sensor event */ + sendSensorEvent(SAR_SENSOR_EVENT_HEAD); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertTrue(mSarInfo.mIsVoiceCall); + + /* Sensor event */ + sendSensorEvent(SAR_SENSOR_EVENT_HAND); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HAND, mSarInfo.mSensorState); + assertTrue(mSarInfo.mIsVoiceCall); + + /* Sensor event */ + sendSensorEvent(SAR_SENSOR_EVENT_FREE_SPACE); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_FREE_SPACE, mSarInfo.mSensorState); + assertTrue(mSarInfo.mIsVoiceCall); + } + + /** + * Test that for a device that has SAR enabled, with sar sensor enabled, + * wifi enabled, device next to user head, a call has started and stopped, + * then Tx power scenarios should adjust properly + */ + @Test + public void testSarMgr_sarSensorOn_wifiOn_onHead_cellOnOff() throws Exception { + createSarManager(true, true); + + InOrder inOrder = inOrder(mWifiNative); + + /* Enable Wifi Client */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_FREE_SPACE, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + + /* Sensor event */ + sendSensorEvent(SAR_SENSOR_EVENT_HEAD); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + + /* Start a Cell call */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertTrue(mSarInfo.mIsVoiceCall); + + /* End a Cell call */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_IDLE, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + } + + /** + * Test that for a device that has SAR enabled, with sar sensor enabled, + * all wifi states disabled, when a sensor event is triggered no setting of Tx power scenario + * is initiated. + * Then when Wifi is enabled, Tx power setting will be initiated to reflect the sensor event. + */ + @Test + public void testSarMgr_sarSensorOn_WifiOffOn_sensorEventTriggered() throws Exception { + createSarManager(true, true); + + InOrder inOrder = inOrder(mWifiNative); + + /* Sensor event */ + sendSensorEvent(SAR_SENSOR_EVENT_BODY); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + + /* Enable Wifi Client */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_BODY, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + } + + /** + * Test the error case when SAR sensor name does not exist in configuration. + * In this case, SarManager should assume operation near head all the time. + */ + @Test + public void testSarMgr_error_sar_name_does_not_exist() throws Exception { + createSarManagerSensorNegTest(SAR_SENSOR_NAME, false, true); + + InOrder inOrder = inOrder(mWifiNative); + + verify(mSensorManager, never()).registerListener(any(SensorEventListener.class), + any(Sensor.class), anyInt()); + + /* Enable WiFi Client */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + + /* Start a Cell Call */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertTrue(mSarInfo.mIsVoiceCall); + + /* End the call */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_IDLE, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + } + + /** + * Test the error case when SarManager uses the wrong sensor name in configuration. + * In this case, SarManager should assume operation near head all the time. + */ + @Test + public void testSarMgr_error_sar_name_mismatch() throws Exception { + createSarManagerSensorNegTest("wrong.sensor.name", true, true); + + InOrder inOrder = inOrder(mWifiNative); + + verify(mSensorManager, never()).registerListener(any(SensorEventListener.class), + any(Sensor.class), anyInt()); + + /* Enable WiFi Client */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + + /* Start a Cell Call */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertTrue(mSarInfo.mIsVoiceCall); + + /* End the call */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_IDLE, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + } + + /** + * Test the error case when SarManager fails to register as a SensorEventListener. + * In this case, SarManager should assume operation near head all the time. + */ + @Test + public void testSarMgr_error_sar_register_failure() throws Exception { + createSarManagerSensorNegTest(SAR_SENSOR_NAME, true, false); + + verify(mSensorManager).registerListener(any(SensorEventListener.class), + any(Sensor.class), anyInt()); + + InOrder inOrder = inOrder(mWifiNative); + + /* Enable WiFi Client */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); + + /* Start a Cell Call */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertTrue(mSarInfo.mIsVoiceCall); + + /* End the call */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_IDLE, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsVoiceCall); } } -- cgit v1.2.3