From dc3d066444208c13ed4caf69bebee6b68e7488f3 Mon Sep 17 00:00:00 2001 From: Ahmed ElArabawy Date: Tue, 22 May 2018 18:22:03 -0700 Subject: WiFi: SAR Support: Add SarInfo Current implementation has limited support for SAR in Wifi. It just includes the support of power backoff when an ongoing voice call is happening while Wifi is transmitting. This commit is the first stage to provide more support for SAR for WiFi TX power backoff when the device is in close proximity to head or body of user with or without the presence of a voice call. This stage adds the SarInfo which is a data structure that is used by SarManager to communicate SAR triggers towards the WifiVendorHal Bug: 65174506 Test: Run Wifi unit test suite Test: ./frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: Ic5f002430db1e92f656ae861e65934241a8590ef Signed-off-by: Ahmed ElArabawy --- service/java/com/android/server/wifi/SarInfo.java | 194 ++++++++++++++ .../src/com/android/server/wifi/SarInfoTest.java | 287 +++++++++++++++++++++ 2 files changed, 481 insertions(+) create mode 100644 service/java/com/android/server/wifi/SarInfo.java create mode 100644 tests/wifitests/src/com/android/server/wifi/SarInfoTest.java diff --git a/service/java/com/android/server/wifi/SarInfo.java b/service/java/com/android/server/wifi/SarInfo.java new file mode 100644 index 000000000..6eb777c65 --- /dev/null +++ b/service/java/com/android/server/wifi/SarInfo.java @@ -0,0 +1,194 @@ +/* + * 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 java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * This class represents the list of SAR inputs that will be used to select the proper + * power profile. + * This includes: + * - SAR body sensor status + * - Is there an ongoing voice call + * - Is SoftAP active + * It also contains info about state of the other Wifi modes + * - Client mode (Sta) + * - ScanOnly mode + * It also keeps history for the reporting of SAR states/scenario to avoid unnecessary reporting + * - keeps track of the last reported states + * - keeps track of the last reported SAR scenario + * - keeps track of if all wifi modes were disabled (no reporting should happen then) + */ +public class SarInfo { + /** + * This value is used as an initial value for the last reported scenario + * It is intended to be different than all valid SAR scenario values (including the + * reset value). + * Using this to initialize the lastReportedScenario results in that the first scenario + * (including reset) would be reported. + */ + public static final int INITIAL_SAR_SCENARIO = -2; + + /** + * This value is used for the reset scenario (no TX Power backoff) + * Valid scenario values only include scenarios with Tx Power backoff, + * so we need this one to represent the "No backoff" case. + */ + public static final int RESET_SAR_SCENARIO = -1; + + private static final String SAR_SENSOR_FREE_SPACE_STR = "SAR_SENSOR_FREE_SPACE"; + private static final String SAR_SENSOR_NEAR_BODY_STR = "SAR_SENSOR_NEAR_BODY"; + private static final String SAR_SENSOR_NEAR_HAND_STR = "SAR_SENSOR_NEAR_HAND"; + private static final String SAR_SENSOR_NEAR_HEAD_STR = "SAR_SENSOR_NEAR_HEAD"; + + public static final int SAR_SENSOR_FREE_SPACE = 1; + public static final int SAR_SENSOR_NEAR_HAND = 2; + public static final int SAR_SENSOR_NEAR_HEAD = 3; + public static final int SAR_SENSOR_NEAR_BODY = 4; + + /* For Logging */ + private static final String TAG = "WifiSarInfo"; + + public boolean mSarSensorEnabled; + + public int mSensorState = SAR_SENSOR_FREE_SPACE; + public boolean mIsWifiClientEnabled = false; + public boolean mIsWifiSapEnabled = false; + public boolean mIsWifiScanOnlyEnabled = false; + public boolean mIsVoiceCall = false; + public int mAttemptedSarScenario = RESET_SAR_SCENARIO; + + private boolean mAllWifiDisabled = true; + + /* Variables representing the last successfully reported values to hal */ + private int mLastReportedSensorState = SAR_SENSOR_FREE_SPACE; + private boolean mLastReportedIsWifiSapEnabled = false; + private boolean mLastReportedIsVoiceCall = false; + private int mLastReportedScenario = INITIAL_SAR_SCENARIO; + + SarInfo(boolean sarSensorEnabled) { + mSarSensorEnabled = sarSensorEnabled; + } + + /** + * shouldReport() + * This method returns false in the following cases: + * 1. If all Wifi modes are disabled. + * 2. Values contributing to the SAR scenario selection have not changed + * since last successful reporting. + * + * Special cases to allow for devices that require setting the SAR scenario value + * when the chip comes up (initial startup, or during operation) + * 1. This method would report true even with unchanged values from last reporting, + * if any wifi mode is just enabled after all wifi modes were disabled. + * 2. This method would report true the first time it is called with any wifi mode enabled. + */ + public boolean shouldReport() { + /* Check if all Wifi modes are disabled */ + if (!mIsWifiClientEnabled && !mIsWifiSapEnabled && !mIsWifiScanOnlyEnabled) { + mAllWifiDisabled = true; + return false; + } + + /* Check if Wifi was all disabled before this call */ + if (mAllWifiDisabled) { + return true; + } + + /* Check if some change happened since last successful reporting */ + if ((mSensorState != mLastReportedSensorState) + || (mIsWifiSapEnabled != mLastReportedIsWifiSapEnabled) + || (mIsVoiceCall != mLastReportedIsVoiceCall)) { + return true; + } else { + return false; + } + } + + /** + * reportingSuccessful() + * This method is called when reporting SAR scenario is fully successful + * This results in caching the last reported inputs for future comparison. + */ + public void reportingSuccessful() { + mLastReportedSensorState = mSensorState; + mLastReportedIsWifiSapEnabled = mIsWifiSapEnabled; + mLastReportedIsVoiceCall = mIsVoiceCall; + mLastReportedScenario = mAttemptedSarScenario; + + mAllWifiDisabled = false; + } + + /** + * resetSarScenarioNeeded() + * Returns true if a call towards HAL to reset SAR scenario would be necessary. + * Returns false if the last call to HAL was already a reset, and hence + * another call to reset the SAR scenario would be redundant. + */ + public boolean resetSarScenarioNeeded() { + return setSarScenarioNeeded(RESET_SAR_SCENARIO); + } + + /** + * setSarScenarioNeeded() + * Returns true if a call towards HAL to set SAR scenario to that value would be + * necessary. + * Returns false if the last call to HAL was to set the scenario to that value, hence, + * another call to set the SAR scenario to the same value would be redundant. + */ + public boolean setSarScenarioNeeded(int scenario) { + mAttemptedSarScenario = scenario; + return (mLastReportedScenario != scenario); + } + + /** + * dump() + * Dumps the state of SarInfo + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("*** WiFi SAR Info Dump ***"); + pw.println("Current values:"); + pw.println(" Sensor state is: " + sensorStateToString(mSensorState)); + pw.println(" Voice Call state is: " + mIsVoiceCall); + pw.println(" Wifi Client state is: " + mIsWifiClientEnabled); + pw.println(" Wifi Soft AP state is: " + mIsWifiSapEnabled); + pw.println(" Wifi ScanOnly state is: " + mIsWifiScanOnlyEnabled); + 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); + } + + /** + * Convert SAR sensor state to string + */ + public static String sensorStateToString(int sensorState) { + switch(sensorState) { + case SAR_SENSOR_FREE_SPACE: + return SAR_SENSOR_FREE_SPACE_STR; + case SAR_SENSOR_NEAR_BODY: + return SAR_SENSOR_NEAR_BODY_STR; + case SAR_SENSOR_NEAR_HAND: + return SAR_SENSOR_NEAR_HAND_STR; + case SAR_SENSOR_NEAR_HEAD: + return SAR_SENSOR_NEAR_HEAD_STR; + default: + return "Invalid SAR sensor state"; + } + } +} diff --git a/tests/wifitests/src/com/android/server/wifi/SarInfoTest.java b/tests/wifitests/src/com/android/server/wifi/SarInfoTest.java new file mode 100644 index 000000000..1f0e44c8a --- /dev/null +++ b/tests/wifitests/src/com/android/server/wifi/SarInfoTest.java @@ -0,0 +1,287 @@ +/* + * 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 org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.support.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * unit tests for {@link com.android.server.wifi.SarInfo}. + */ +@SmallTest +public class SarInfoTest { + private static final String TAG = "WifiSarInfoTest"; + + private SarInfo mSarInfo; + + private static final int SAR_SCENARIO_1 = 1; + private static final int SAR_SCENARIO_2 = 2; + + @Before + public void setUp() throws Exception { + mSarInfo = new SarInfo(true); + } + + @After + public void cleanUp() throws Exception { + } + + /** + * Test that at start, resetSarScenarioNeeded returns true, + * to allow for initial setting of normal scenario. + */ + @Test + public void testSarInfo_resetSarScenarioNeed_atStart() throws Exception { + assertTrue(mSarInfo.resetSarScenarioNeeded()); + } + + /** + * Test that at start, setSarScenarioNeeded returns true. + */ + @Test + public void testSarInfo_setSarScenarioNeeded_atStart() throws Exception { + assertTrue(mSarInfo.setSarScenarioNeeded(SAR_SCENARIO_1)); + } + + /** + * Test performing two successive reset of SAR scenario. + * The first should succeed, while the second should fail, since it is redundant. + */ + @Test + public void testSarInfo_repeat_reset_scenario() throws Exception { + /* Initial reset is allowed */ + assertTrue(mSarInfo.resetSarScenarioNeeded()); + mSarInfo.reportingSuccessful(); + + /* Now resetting again should not be allowed */ + assertFalse(mSarInfo.resetSarScenarioNeeded()); + } + + /** + * Test performing set SAR scenario after reset. + * The two attempts should succeed. + */ + @Test + public void testSarInfo_set_after_reset_scenario() throws Exception { + assertTrue(mSarInfo.resetSarScenarioNeeded()); + mSarInfo.reportingSuccessful(); + + /* Setting scenario should be allowed, since last call was for a reset */ + assertTrue(mSarInfo.setSarScenarioNeeded(SAR_SCENARIO_1)); + } + + /** + * Test performing setting SAR scenario twice with same value. + * The second attempt should fail. + */ + @Test + public void testSarInfo_set_twice_same_value_scenario() throws Exception { + assertTrue(mSarInfo.setSarScenarioNeeded(SAR_SCENARIO_1)); + mSarInfo.reportingSuccessful(); + + /* Second attempt should fail */ + assertFalse(mSarInfo.setSarScenarioNeeded(SAR_SCENARIO_1)); + } + + /** + * Test performing setting SAR scenario twice with different values. + * Both attempts should succeed. + */ + @Test + public void testSarInfo_set_twice_different_values_scenario() throws Exception { + assertTrue(mSarInfo.setSarScenarioNeeded(SAR_SCENARIO_1)); + mSarInfo.reportingSuccessful(); + + /* Setting scenario should be allowed */ + assertTrue(mSarInfo.setSarScenarioNeeded(SAR_SCENARIO_2)); + } + + /** + * Test performing reset of SAR scenario after setting it. + * Both attempts should succeed. + */ + @Test + public void testSarInfo_reset_after_set_scenario() throws Exception { + assertTrue(mSarInfo.setSarScenarioNeeded(SAR_SCENARIO_1)); + mSarInfo.reportingSuccessful(); + + /* Resetting scenario should be allowed */ + assertTrue(mSarInfo.resetSarScenarioNeeded()); + } + + /** + * Test that at start, shouldReport returns false (wifi modes still disabled). + */ + @Test + public void testSarInfo_shouldReport_all_wifi_disabled() throws Exception { + assertFalse(mSarInfo.shouldReport()); + } + + /** + * Test that once Wifi (any mode) is enabled, shouldReport returns true. + */ + @Test + public void testSarInfo_shouldReport_wifi_enabled() throws Exception { + mSarInfo.mIsWifiClientEnabled = true; + assertTrue(mSarInfo.shouldReport()); + } + + /** + * Test that setting sensor (with wifi disabled), shouldReport returns false. + */ + @Test + public void testSarInfo_check_sensor_wifi_disabled() throws Exception { + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + assertFalse(mSarInfo.shouldReport()); + } + + /** + * Test that setting sensor (with some wifi mode enabled), shouldReport returns true. + */ + @Test + public void testSarInfo_check_sensor_wifi_enabled() throws Exception { + mSarInfo.mIsWifiSapEnabled = true; + assertTrue(mSarInfo.shouldReport()); + mSarInfo.reportingSuccessful(); + + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + assertTrue(mSarInfo.shouldReport()); + } + + /** + * Test that setting sensor (with some wifi mode enabled), shouldReport returns true + * only the first time, following attempts should return false (since sensor state + * did not change) + */ + @Test + public void testSarInfo_check_sensor_multiple_wifi_enabled() throws Exception { + mSarInfo.mIsWifiScanOnlyEnabled = true; + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + assertTrue(mSarInfo.shouldReport()); + mSarInfo.reportingSuccessful(); + + assertFalse(mSarInfo.shouldReport()); + } + + /** + * Test that setting sensor with different values (with wifi enabled), + * shouldReport returns true every time. + */ + @Test + public void testSarInfo_check_sensor_multiple_values_wifi_enabled() throws Exception { + mSarInfo.mIsWifiClientEnabled = true; + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + assertTrue(mSarInfo.shouldReport()); + mSarInfo.reportingSuccessful(); + + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_BODY; + assertTrue(mSarInfo.shouldReport()); + mSarInfo.reportingSuccessful(); + + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + assertTrue(mSarInfo.shouldReport()); + } + + /** + * Test setting sensor while wifi is disabled, then enable wifi. + */ + @Test + public void testSarInfo_change_sensors_while_wifi_disabled() throws Exception { + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + assertFalse(mSarInfo.shouldReport()); + + mSarInfo.mIsWifiClientEnabled = true; + assertTrue(mSarInfo.shouldReport()); + mSarInfo.reportingSuccessful(); + } + + /** + * Test having a voice call, shouldReport should return true + * Note: will need to report once before starting the call to remove + * the effect of sensor state change. + */ + @Test + public void testSarInfo_voice_call_wifi_enabled() throws Exception { + mSarInfo.mIsWifiClientEnabled = true; + assertTrue(mSarInfo.shouldReport()); + mSarInfo.reportingSuccessful(); + + mSarInfo.mIsVoiceCall = 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. + */ + @Test + public void testSarInfo_sap_wifi_enabled() throws Exception { + mSarInfo.mIsWifiClientEnabled = true; + assertTrue(mSarInfo.shouldReport()); + mSarInfo.reportingSuccessful(); + + mSarInfo.mIsWifiSapEnabled = true; + assertTrue(mSarInfo.shouldReport()); + } + + /** + * Test that setting sensor (with wifi enabled), reporting not successful + * Then, we should expect that shouldReport returns true evne if we have + * no further changes until reporting is successful. + */ + @Test + public void testSarInfo_check_sensor_reporting_no_success_reporting() throws Exception { + mSarInfo.mIsWifiClientEnabled = true; + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + assertTrue(mSarInfo.shouldReport()); + + /* No call to reportingSuccessful() will be done */ + assertTrue(mSarInfo.shouldReport()); + + /* Now call reportingSuccessful() */ + mSarInfo.reportingSuccessful(); + assertFalse(mSarInfo.shouldReport()); + } + + /** + * Test that setting sensor (with wifi enabled), reporting successful + * Then, changing the sensor state with no successful reporting. + * Followed by reverting to the previous state. + */ + @Test + public void testSarInfo_check_sensor_reporting_no_success_reporting_revert() throws Exception { + mSarInfo.mIsWifiClientEnabled = true; + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + assertTrue(mSarInfo.shouldReport()); + mSarInfo.reportingSuccessful(); + + /* Changing the sensor state and fail to report */ + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_BODY; + assertTrue(mSarInfo.shouldReport()); + + /* Changing the sensor back to the same value as last reported */ + mSarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + assertFalse(mSarInfo.shouldReport()); + } +} -- cgit v1.2.3 From 2e6fdb80cbcd264d774c6e13dd350c2665d21bb1 Mon Sep 17 00:00:00 2001 From: Ahmed ElArabawy Date: Tue, 22 May 2018 22:55:16 -0700 Subject: WiFi: SAR Support: Use SarInfo in WifiVendorHal In a previous commit, the class SarInfo was introduced. This commit utilizes this class to pass SAR related inputs to WifiVendorHal to select the SAR scenario. Note that the functionality introduced in this commit is not called yet during operation. Hence the legacy code is left in place until the new functionality is fully utilized. Bug: 65174506 Test: Run Wifi unit test suite Test: ./frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: Idb00f2c265d64ea4e8fc3a6e2ed7fc9bc08539fb Signed-off-by: Ahmed ElArabawy --- .../java/com/android/server/wifi/WifiNative.java | 17 + .../com/android/server/wifi/WifiVendorHal.java | 229 ++++++++++- .../com/android/server/wifi/WifiNativeTest.java | 22 + .../com/android/server/wifi/WifiVendorHalTest.java | 449 ++++++++++++++++++++- 4 files changed, 713 insertions(+), 4 deletions(-) diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java index 0f785873b..5369b0e1e 100644 --- a/service/java/com/android/server/wifi/WifiNative.java +++ b/service/java/com/android/server/wifi/WifiNative.java @@ -2820,12 +2820,18 @@ public class WifiNative { } /** + * TODO: These constants will be removed in a future change + * They are left for the interim use + * * Tx power level scenarios that can be selected. */ public static final int TX_POWER_SCENARIO_NORMAL = 0; public static final int TX_POWER_SCENARIO_VOICE_CALL = 1; /** + * TODO: This method is deprecated and will be removed in a future change to use + * the interface with sarInfo as an input instead. + * * Select one of the pre-configured TX power level scenarios or reset it back to normal. * Primarily used for meeting SAR requirements during voice calls. * @@ -2837,6 +2843,17 @@ public class WifiNative { return mWifiVendorHal.selectTxPowerScenario(scenario); } + /** + * Select one of the pre-configured transmit power level scenarios or reset it back to normal. + * Primarily used for meeting SAR requirements. + * + * @param sarInfo The collection of inputs used to select the SAR scenario. + * @return true for success; false for failure or if the HAL version does not support this API. + */ + public boolean selectTxPowerScenario(SarInfo sarInfo) { + return mWifiVendorHal.selectTxPowerScenario(sarInfo); + } + /******************************************************** * JNI operations ********************************************************/ diff --git a/service/java/com/android/server/wifi/WifiVendorHal.java b/service/java/com/android/server/wifi/WifiVendorHal.java index 0d73459b5..fdbd97fb0 100644 --- a/service/java/com/android/server/wifi/WifiVendorHal.java +++ b/service/java/com/android/server/wifi/WifiVendorHal.java @@ -2653,7 +2653,7 @@ public class WifiVendorHal { return android.hardware.wifi.V1_2.IWifiStaIface.castFrom(iface); } - + /* TODO: This method is to be removed in a future change */ private int frameworkToHalTxPowerScenario(int scenario) { switch (scenario) { case WifiNative.TX_POWER_SCENARIO_VOICE_CALL: @@ -2664,6 +2664,10 @@ public class WifiVendorHal { } /** + * TODO: This method will be removed in a future change + * It will be replaced with another method receiving SarInfo + * instance instead of the int for scenario + * * Select one of the pre-configured TX power level scenarios or reset it back to normal. * Primarily used for meeting SAR requirements during voice calls. * @@ -2699,6 +2703,229 @@ public class WifiVendorHal { } } + /** + * sarPowerBackoffRequired_1_1() + * This method checks if we need to backoff wifi Tx power due to SAR requirements. + * It handles the case when the device is running the V1_1 version of WifiChip HAL + * In that HAL version, it is required to perform wifi Tx power backoff only if + * a voice call is ongoing. + */ + private boolean sarPowerBackoffRequired_1_1(SarInfo sarInfo) { + /* As long as no voice call is active, no backoff is needed */ + return sarInfo.mIsVoiceCall; + } + + /** + * frameworkToHalTxPowerScenario_1_1() + * This method maps the information inside the SarInfo instance into a SAR scenario + * when device is running the V1_1 version of WifiChip HAL. + * In this HAL version, only one scenario is defined which is for VOICE_CALL + * otherwise, an exception is thrown. + */ + private int frameworkToHalTxPowerScenario_1_1(SarInfo sarInfo) { + if (sarInfo.mIsVoiceCall) { + return android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL; + } else { + throw new IllegalArgumentException("bad scenario: voice call not active"); + } + } + + /** + * sarPowerBackoffRequired_1_2() + * This method checks if we need to backoff wifi Tx power due to SAR requirements. + * It handles the case when the device is running the V1_2 version of WifiChip HAL + * In that HAL version, behavior depends on if SAR sensor input is considered in this device. + * If it is, then whenever the device is near the user body/hand/head, back-off is required. + * Otherwise, we should revert to the V1_1 HAL behavior which is only to perform backoff when + * a voice call is ongoing. + */ + private boolean sarPowerBackoffRequired_1_2(SarInfo sarInfo) { + if (sarInfo.mSarSensorEnabled) { + return (sarInfo.mSensorState != SarInfo.SAR_SENSOR_FREE_SPACE); + } else { + return sarInfo.mIsVoiceCall; + } + } + + /** + * frameworkToHalTxPowerScenario_1_2() + * This method maps the information inside the SarInfo instance into a SAR scenario + * when device is running the V1_2 version of WifiChip HAL. + * In this HAL version, behavior depends on if SAR sensor input is considered in this device. + * If it is, then based on regulatory compliance requirements, + * - There is no need to treat NEAR_HAND different from NEAR_BODY, both can be considered + * near the user body. + * - Running in softAP mode can be treated the same way as running a voice call from tx power + * backoff perspective. + * If SAR sensor input is not considered in this device, then we should revert to the V1_1 HAL + * behavior, and the only valid scenario would be when a voice call is ongoing. + */ + private int frameworkToHalTxPowerScenario_1_2(SarInfo sarInfo) { + if (sarInfo.mSarSensorEnabled) { + switch(sarInfo.mSensorState) { + case SarInfo.SAR_SENSOR_NEAR_BODY: + case SarInfo.SAR_SENSOR_NEAR_HAND: + if (sarInfo.mIsVoiceCall || sarInfo.mIsWifiSapEnabled) { + return android.hardware.wifi.V1_2.IWifiChip + .TxPowerScenario.ON_BODY_CELL_ON; + } else { + return android.hardware.wifi.V1_2.IWifiChip + .TxPowerScenario.ON_BODY_CELL_OFF; + } + + case SarInfo.SAR_SENSOR_NEAR_HEAD: + if (sarInfo.mIsVoiceCall || sarInfo.mIsWifiSapEnabled) { + return android.hardware.wifi.V1_2.IWifiChip + .TxPowerScenario.ON_HEAD_CELL_ON; + } else { + return android.hardware.wifi.V1_2.IWifiChip + .TxPowerScenario.ON_HEAD_CELL_OFF; + } + + default: + throw new IllegalArgumentException("bad scenario: Invalid sensor state"); + } + } else { + /* SAR Sensors not enabled, act like V1_1 */ + if (sarInfo.mIsVoiceCall) { + return android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL; + } else { + throw new IllegalArgumentException("bad scenario: voice call not active"); + } + } + } + + /** + * Select one of the pre-configured TX power level scenarios or reset it back to normal. + * Primarily used for meeting SAR requirements during voice calls. + * + * Note: If it was found out that the scenario to be reported is the same as last reported one, + * then exit with success. + * This is to handle the case when some HAL versions deal with different inputs equally, + * in that case, we should not call the hal unless there is a change in scenario. + * Note: It is assumed that this method is only called if SAR is enabled. The logic of whether + * to call it or not resides in SarManager class. + * Note: This method is called whether SAR sensor is supported or not. The passed SarInfo object + * contains a flag to indicate the SAR sensor support. + * + * @param sarInfo The collection of inputs to select the SAR scenario. + * @return true for success; false for failure or if the HAL version does not support this API. + */ + public boolean selectTxPowerScenario(SarInfo sarInfo) { + synchronized (sLock) { + // First attempt to get a V_1_2 instance of the Wifi HAL. + android.hardware.wifi.V1_2.IWifiChip iWifiChipV12 = getWifiChipForV1_2Mockable(); + if (iWifiChipV12 != null) { + return selectTxPowerScenario_1_2(iWifiChipV12, sarInfo); + } + + // Now attempt to get a V_1_1 instance of the Wifi HAL. + android.hardware.wifi.V1_1.IWifiChip iWifiChipV11 = getWifiChipForV1_1Mockable(); + if (iWifiChipV11 != null) { + return selectTxPowerScenario_1_1(iWifiChipV11, sarInfo); + } + + // HAL version does not support SAR + return false; + } + } + + private boolean selectTxPowerScenario_1_1( + android.hardware.wifi.V1_1.IWifiChip iWifiChip, SarInfo sarInfo) { + WifiStatus status; + try { + if (sarPowerBackoffRequired_1_1(sarInfo)) { + // Power backoff is needed, so calculate the required scenario, + // and attempt to set it. + int halScenario = frameworkToHalTxPowerScenario_1_1(sarInfo); + if (sarInfo.setSarScenarioNeeded(halScenario)) { + status = iWifiChip.selectTxPowerScenario(halScenario); + if (ok(status)) { + mLog.e("Setting SAR scenario to " + halScenario); + return true; + } else { + mLog.e("Failed to set SAR scenario to " + halScenario); + return false; + } + } + + // Reaching here means setting SAR scenario would be redundant, + // do nothing and return with success. + return true; + } + + // We don't need to perform power backoff, so attempt to reset SAR scenario. + if (sarInfo.resetSarScenarioNeeded()) { + status = iWifiChip.resetTxPowerScenario(); + if (ok(status)) { + mLog.d("Resetting SAR scenario"); + return true; + } else { + mLog.e("Failed to reset SAR scenario"); + return false; + } + } + + // Resetting SAR scenario would be redundant, + // do nothing and return with success. + return true; + } catch (RemoteException e) { + handleRemoteException(e); + return false; + } catch (IllegalArgumentException e) { + mLog.err("Illegal argument for selectTxPowerScenario_1_1()").c(e.toString()).flush(); + return false; + } + } + + private boolean selectTxPowerScenario_1_2( + android.hardware.wifi.V1_2.IWifiChip iWifiChip, SarInfo sarInfo) { + WifiStatus status; + try { + if (sarPowerBackoffRequired_1_2(sarInfo)) { + // Power backoff is needed, so calculate the required scenario, + // and attempt to set it. + int halScenario = frameworkToHalTxPowerScenario_1_2(sarInfo); + if (sarInfo.setSarScenarioNeeded(halScenario)) { + status = iWifiChip.selectTxPowerScenario_1_2(halScenario); + if (ok(status)) { + mLog.e("Setting SAR scenario to " + halScenario); + return true; + } else { + mLog.e("Failed to set SAR scenario to " + halScenario); + return false; + } + } + + // Reaching here means setting SAR scenario would be redundant, + // do nothing and return with success. + return true; + } + + // We don't need to perform power backoff, so attempt to reset SAR scenario. + if (sarInfo.resetSarScenarioNeeded()) { + status = iWifiChip.resetTxPowerScenario(); + if (ok(status)) { + mLog.d("Resetting SAR scenario"); + return true; + } else { + mLog.e("Failed to reset SAR scenario"); + return false; + } + } + + // Resetting SAR scenario would be redundant, + // do nothing and return with success. + return true; + } catch (RemoteException e) { + handleRemoteException(e); + return false; + } catch (IllegalArgumentException e) { + mLog.err("Illegal argument for selectTxPowerScenario_1_2()").c(e.toString()).flush(); + return false; + } + } + // This creates a blob of IE elements from the array received. // TODO: This ugly conversion can be removed if we put IE elements in ScanResult. private static byte[] hidlIeArrayToFrameworkIeBlob(ArrayList ies) { diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java index 31e7e553c..fbdc8f507 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java @@ -586,4 +586,26 @@ public class WifiNativeTest { verify(mWificondControl).startHostapd(WIFI_IFACE_NAME, mockListener); verify(mWifiMetrics).incrementNumSetupSoftApInterfaceFailureDueToHostapd(); } + + /** + * Test that selectTxPowerScenario() calls into WifiVendorHal (success case) + */ + @Test + public void testSelectTxPowerScenario_success() throws Exception { + when(mWifiVendorHal.selectTxPowerScenario(any(SarInfo.class))).thenReturn(true); + SarInfo sarInfo = new SarInfo(true); + assertTrue(mWifiNative.selectTxPowerScenario(sarInfo)); + verify(mWifiVendorHal).selectTxPowerScenario(sarInfo); + } + + /** + * Test that selectTxPowerScenario() calls into WifiVendorHal (failure case) + */ + @Test + public void testSelectTxPowerScenario_failure() throws Exception { + when(mWifiVendorHal.selectTxPowerScenario(any(SarInfo.class))).thenReturn(false); + SarInfo sarInfo = new SarInfo(true); + assertFalse(mWifiNative.selectTxPowerScenario(sarInfo)); + verify(mWifiVendorHal).selectTxPowerScenario(sarInfo); + } } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java index 0f8f56f66..60b5f81ad 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -99,6 +100,7 @@ import com.android.server.wifi.util.NativeUtil; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.stubbing.Answer; @@ -119,6 +121,7 @@ public class WifiVendorHalTest { private static final String TEST_IFACE_NAME = "wlan0"; private static final String TEST_IFACE_NAME_1 = "wlan1"; private static final MacAddress TEST_MAC_ADDRESS = MacAddress.fromString("ee:33:a2:94:10:92"); + private static final int SAR_SENSOR_INVALID_STATE = -6; WifiVendorHal mWifiVendorHal; private WifiStatus mWifiStatusSuccess; @@ -1997,11 +2000,14 @@ public class WifiVendorHalTest { } /** + * TODO: This test will be removed in a following change + * It is going to be replaced by another test using the new structure + * * Test the new selectTxPowerScenario HIDL method invocation. This should return failure if the * HAL service is exposing the 1.0 interface. */ @Test - public void testSelectTxPowerScenario() throws RemoteException { + public void testSelectTxPowerScenario_old() throws RemoteException { assertTrue(mWifiVendorHal.startVendorHalSta()); // Should fail because we exposed the 1.0 IWifiChip. assertFalse( @@ -2023,11 +2029,14 @@ public class WifiVendorHalTest { } /** + * TODO: This test will be removed in a following change + * It is going to be replaced by another test using the new structure + * * Test the new resetTxPowerScenario HIDL method invocation. This should return failure if the * HAL service is exposing the 1.0 interface. */ @Test - public void testResetTxPowerScenario() throws RemoteException { + public void testResetTxPowerScenario_old() throws RemoteException { assertTrue(mWifiVendorHal.startVendorHalSta()); // Should fail because we exposed the 1.0 IWifiChip. assertFalse(mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_NORMAL)); @@ -2046,10 +2055,13 @@ public class WifiVendorHalTest { } /** + * TODO: This test will be removed in a following change + * It is going to be replaced by another test using the new structure + * * Test the new selectTxPowerScenario HIDL method invocation with a bad scenario index. */ @Test - public void testInvalidSelectTxPowerScenario() throws RemoteException { + public void testInvalidSelectTxPowerScenario_old() throws RemoteException { // Expose the 1.1 IWifiChip. mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper()); when(mIWifiChipV11.selectTxPowerScenario(anyInt())).thenReturn(mWifiStatusSuccess); @@ -2061,6 +2073,437 @@ public class WifiVendorHalTest { mWifiVendorHal.stopVendorHal(); } + /** + * Test the selectTxPowerScenario HIDL method invocation for 1.0 interface. + * This should return failure since SAR is not supported for this interface version. + */ + @Test + public void testSelectTxPowerScenario_1_0() throws RemoteException { + // Create a SAR info record (no sensor support) + SarInfo sarInfo = new SarInfo(false); + sarInfo.mIsVoiceCall = true; + + assertTrue(mWifiVendorHal.startVendorHalSta()); + // Should fail because we exposed the 1.0 IWifiChip. + assertFalse(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt()); + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the selectTxPowerScenario HIDL method invocation for 1.1 interface. + * This should return success. + */ + @Test + public void testSelectTxPowerScenario_1_1() throws RemoteException { + // Create a SAR info record (no sensor support) + SarInfo sarInfo = new SarInfo(false); + sarInfo.mIsVoiceCall = true; + + // Now expose the 1.1 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV11.selectTxPowerScenario(anyInt())).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV11).selectTxPowerScenario( + eq(android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL)); + verify(mIWifiChipV11, never()).resetTxPowerScenario(); + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the selectTxPowerScenario HIDL method invocation for 1.2 interface. + * This should return success. + */ + @Test + public void testSelectTxPowerScenario_1_2() throws RemoteException { + // Create a SAR info record (no sensor support) + SarInfo sarInfo = new SarInfo(false); + sarInfo.mIsVoiceCall = true; + + // Now expose the 1.2 IWifiChip + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV12).selectTxPowerScenario_1_2( + eq(android.hardware.wifi.V1_2.IWifiChip.TxPowerScenario.VOICE_CALL)); + verify(mIWifiChipV12, never()).resetTxPowerScenario(); + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the resetTxPowerScenario HIDL method invocation for 1.0 interface. + * This should return failure since it does not supprt SAR. + */ + @Test + public void testResetTxPowerScenario_1_0() throws RemoteException { + // Create a SAR info record (no sensor support) + SarInfo sarInfo = new SarInfo(false); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + // Should fail because we exposed the 1.0 IWifiChip. + assertFalse(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV11, never()).resetTxPowerScenario(); + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the resetTxPowerScenario HIDL method invocation for 1.1 interface. + * This should return success. + */ + @Test + public void testResetTxPowerScenario_1_1() throws RemoteException { + // Create a SAR info record (no sensor support) + SarInfo sarInfo = new SarInfo(false); + + // Now expose the 1.1 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV11.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV11).resetTxPowerScenario(); + verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt()); + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test resetting SAR scenario when not needed, should return true without invoking + * the HAL method. + * This is using HAL 1.1 interface. + */ + @Test + public void testResetTxPowerScenario_not_needed_1_1() throws RemoteException { + InOrder inOrder = inOrder(mIWifiChipV11); + + // Create a SAR info record (no sensor support) + SarInfo sarInfo = new SarInfo(false); + + // Now expose the 1.1 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV11.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + + /* Calling reset once */ + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + inOrder.verify(mIWifiChipV11).resetTxPowerScenario(); + inOrder.verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt()); + sarInfo.reportingSuccessful(); + + /* Calling reset second time */ + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + inOrder.verify(mIWifiChipV11, never()).resetTxPowerScenario(); + inOrder.verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt()); + + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the new resetTxPowerScenario HIDL method invocation for 1.2 interface. + * This should return success. + */ + @Test + public void testResetTxPowerScenario_1_2() throws RemoteException { + // Create a SAR info record (no sensor support) + SarInfo sarInfo = new SarInfo(false); + + // Now expose the 1.2 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV12.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV12).resetTxPowerScenario(); + verify(mIWifiChipV12, never()).selectTxPowerScenario_1_2(anyInt()); + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test resetting SAR scenario when not needed, should return true without invoking + * the HAL method. + * This is using HAL 1.2 interface. + */ + @Test + public void testResetTxPowerScenario_not_needed_1_2() throws RemoteException { + InOrder inOrder = inOrder(mIWifiChipV12); + + // Create a SAR info record (no sensor support) + SarInfo sarInfo = new SarInfo(false); + + // Now expose the 1.2 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV12.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + + /* Calling reset once */ + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + inOrder.verify(mIWifiChipV12).resetTxPowerScenario(); + inOrder.verify(mIWifiChipV12, never()).selectTxPowerScenario(anyInt()); + sarInfo.reportingSuccessful(); + + /* Calling reset second time */ + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + inOrder.verify(mIWifiChipV12, never()).resetTxPowerScenario(); + inOrder.verify(mIWifiChipV12, never()).selectTxPowerScenario(anyInt()); + + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the selectTxPowerScenario HIDL method invocation with sensor related scenarios + * to IWifiChip 1.2 interface + */ + @Test + public void testHeadSensorScenarios_SelectTxPowerV1_2() throws RemoteException { + // Create a SAR info record (with sensor support) + SarInfo sarInfo = new SarInfo(true); + sarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + + // Expose the 1.2 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess); + + // ON_HEAD_CELL_OFF + assertTrue(mWifiVendorHal.startVendorHalSta()); + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV12).selectTxPowerScenario_1_2( + eq(android.hardware.wifi.V1_2.IWifiChip.TxPowerScenario.ON_HEAD_CELL_OFF)); + verify(mIWifiChipV12, never()).resetTxPowerScenario(); + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test setting SAR scenario when not needed, should return true without invoking + * the HAL method. + * This is using HAL 1.2 interface. + */ + @Test + public void testSetTxPowerScenario_not_needed_1_2() throws RemoteException { + InOrder inOrder = inOrder(mIWifiChipV12); + + // Create a SAR info record (no sensor support) + SarInfo sarInfo = new SarInfo(true); + sarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + + // Now expose the 1.2 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + + /* Calling set once */ + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + inOrder.verify(mIWifiChipV12).selectTxPowerScenario_1_2( + eq(android.hardware.wifi.V1_2.IWifiChip.TxPowerScenario.ON_HEAD_CELL_OFF)); + inOrder.verify(mIWifiChipV12, never()).resetTxPowerScenario(); + sarInfo.reportingSuccessful(); + + /* Calling set second time */ + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + inOrder.verify(mIWifiChipV12, never()).resetTxPowerScenario(); + inOrder.verify(mIWifiChipV12, never()).selectTxPowerScenario(anyInt()); + + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the selectTxPowerScenairo HIDL method invocation with sensor events for + * IWifiChip 1.2 interface (Near hand event) along with a voice call. + * This should be reverted to BODY events (First with CELL_OFF followed by CELL_ON). + */ + @Test + public void testHandSensorScenarios_SelectTxPowerV1_2() throws RemoteException { + // Create a SAR info record (with sensor support) + SarInfo sarInfo = new SarInfo(true); + sarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HAND; + + // Expose the 1.2 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + + // First select a scenario with cell off + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV12).selectTxPowerScenario_1_2( + eq(android.hardware.wifi.V1_2.IWifiChip.TxPowerScenario.ON_BODY_CELL_OFF)); + + // Then select a scenario with cell on + sarInfo.mIsVoiceCall = true; + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV12).selectTxPowerScenario_1_2( + eq(android.hardware.wifi.V1_2.IWifiChip.TxPowerScenario.ON_BODY_CELL_ON)); + + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the selectTxPowerScenario HIDL method invocation with a sensor info to IWifiChip + * 1.1 interface. + * Sensor mode should be ignored, and act only based on Cell on/off. + */ + @Test + public void testOnHeadCellOffOn_SelectTxPowerScenarioV1_1() throws RemoteException { + // Create a SAR info record (with sensor support) + SarInfo sarInfo = new SarInfo(true); + sarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + + // Expose the 1.1 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV11.selectTxPowerScenario(anyInt())).thenReturn(mWifiStatusSuccess); + when(mIWifiChipV11.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + + // First select a scenario with cell off + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV11).resetTxPowerScenario(); + + // Then select a scenario with cell on + sarInfo.mIsVoiceCall = true; + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV11).selectTxPowerScenario( + eq(android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL)); + + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the new selectTxPowerScenario HIDL method invocation with a bad input. + * This should not result into any calls to the HAL. + * Use IWifiChip 1.2 interface + */ + @Test + public void testInvalidSelectTxPowerScenario_1_2() throws RemoteException { + // Create a SAR info record (with sensor support) + SarInfo sarInfo = new SarInfo(true); + sarInfo.mSensorState = SAR_SENSOR_INVALID_STATE; + + // Expose the 1.2 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + assertFalse(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV12, never()).selectTxPowerScenario(anyInt()); + verify(mIWifiChipV12, never()).resetTxPowerScenario(); + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the selectTxPowerScenario HIDL method invocation with IWifiChip 1.2 interface. + * The following inputs: + * - Sensor support is enabled + * - Sensor state is NEAR_HEAD + * - SAP is enabled + * - No voice call + */ + @Test + public void testSelectTxPowerScenario_1_2_head_sap() throws RemoteException { + // Create a SAR info record (with sensor support) + SarInfo sarInfo = new SarInfo(true); + sarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + sarInfo.mIsWifiSapEnabled = true; + sarInfo.mIsVoiceCall = false; + + // Expose the 1.2 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV12).selectTxPowerScenario_1_2( + eq(android.hardware.wifi.V1_2.IWifiChip.TxPowerScenario.ON_HEAD_CELL_ON)); + + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the selectTxPowerScenario HIDL method invocation with IWifiChip 1.2 interface. + * The following inputs: + * - Sensor support is enabled + * - Sensor state is NEAR_HEAD + * - SAP is enabled + * - voice call is enabled + */ + @Test + public void testSelectTxPowerScenario_1_2_head_sap_call() throws RemoteException { + // Create a SAR info record (with sensor support) + SarInfo sarInfo = new SarInfo(true); + sarInfo.mSensorState = SarInfo.SAR_SENSOR_NEAR_HEAD; + sarInfo.mIsWifiSapEnabled = true; + sarInfo.mIsVoiceCall = true; + + // Expose the 1.2 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV12.selectTxPowerScenario_1_2(anyInt())).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + verify(mIWifiChipV12).selectTxPowerScenario_1_2( + eq(android.hardware.wifi.V1_2.IWifiChip.TxPowerScenario.ON_HEAD_CELL_ON)); + + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the selectTxPowerScenario HIDL method invocation with IWifiChip 1.2 interface. + * The following inputs: + * - Sensor support is enabled + * - Sensor state is FREE_SPACE + * - SAP is enabled + * - No voice call + */ + @Test + public void testSelectTxPowerScenario_1_2_freespace_sap() throws RemoteException { + // Create a SAR info record (with sensor support) + SarInfo sarInfo = new SarInfo(true); + sarInfo.mSensorState = SarInfo.SAR_SENSOR_FREE_SPACE; + sarInfo.mIsWifiSapEnabled = true; + sarInfo.mIsVoiceCall = false; + + // Expose the 1.2 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV12.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + + verify(mIWifiChipV12).resetTxPowerScenario(); + verify(mIWifiChipV12, never()).selectTxPowerScenario_1_2(anyInt()); + mWifiVendorHal.stopVendorHal(); + } + + /** + * Test the selectTxPowerScenario HIDL method invocation with IWifiChip 1.2 interface. + * The following inputs: + * - Sensor support is disabled + * - SAP is enabled + * - No voice call + */ + @Test + public void testSelectTxPowerScenario_1_2_no_sensors_sap() throws RemoteException { + // Create a SAR info record (with no sensor support) + SarInfo sarInfo = new SarInfo(false); + sarInfo.mIsWifiSapEnabled = true; + sarInfo.mIsVoiceCall = false; + + // Expose the 1.2 IWifiChip. + mWifiVendorHal = new WifiVendorHalSpyV1_2(mHalDeviceManager, mLooper.getLooper()); + when(mIWifiChipV12.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess); + + assertTrue(mWifiVendorHal.startVendorHalSta()); + assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo)); + + verify(mIWifiChipV12).resetTxPowerScenario(); + verify(mIWifiChipV12, never()).selectTxPowerScenario_1_2(anyInt()); + mWifiVendorHal.stopVendorHal(); + } + /** * Test the STA Iface creation failure due to iface name retrieval failure. */ -- cgit v1.2.3 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 From 1b8216207b5ab9c5cc64932c9c731843fe819539 Mon Sep 17 00:00:00 2001 From: Ahmed ElArabawy Date: Wed, 23 May 2018 14:23:59 -0700 Subject: WiFi: SAR: Cleanup dead code In previous commits, new SAR functionality was added to support handling of the SAR sensor. As part of the migration towards the new functionality, some old code was kept. This commit removes this old code (currently unused). Bug: 65174506 Test: Run Wifi unit test suite Test: ./frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: I2f9a789f0ae464e812c87a7c3c02c1955333a7a1 Signed-off-by: Ahmed ElArabawy --- .../java/com/android/server/wifi/WifiNative.java | 24 ------- .../com/android/server/wifi/WifiVendorHal.java | 50 --------------- .../com/android/server/wifi/WifiVendorHalTest.java | 74 ---------------------- 3 files changed, 148 deletions(-) diff --git a/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java index 5369b0e1e..bc599c141 100644 --- a/service/java/com/android/server/wifi/WifiNative.java +++ b/service/java/com/android/server/wifi/WifiNative.java @@ -2819,30 +2819,6 @@ public class WifiNative { return mWifiVendorHal.configureRoaming(ifaceName, new RoamingConfig()); } - /** - * TODO: These constants will be removed in a future change - * They are left for the interim use - * - * Tx power level scenarios that can be selected. - */ - public static final int TX_POWER_SCENARIO_NORMAL = 0; - public static final int TX_POWER_SCENARIO_VOICE_CALL = 1; - - /** - * TODO: This method is deprecated and will be removed in a future change to use - * the interface with sarInfo as an input instead. - * - * Select one of the pre-configured TX power level scenarios or reset it back to normal. - * Primarily used for meeting SAR requirements during voice calls. - * - * @param scenario Should be one {@link #TX_POWER_SCENARIO_NORMAL} or - * {@link #TX_POWER_SCENARIO_VOICE_CALL}. - * @return true for success; false for failure or if the HAL version does not support this API. - */ - public boolean selectTxPowerScenario(int scenario) { - return mWifiVendorHal.selectTxPowerScenario(scenario); - } - /** * Select one of the pre-configured transmit power level scenarios or reset it back to normal. * Primarily used for meeting SAR requirements. diff --git a/service/java/com/android/server/wifi/WifiVendorHal.java b/service/java/com/android/server/wifi/WifiVendorHal.java index fdbd97fb0..361387e2f 100644 --- a/service/java/com/android/server/wifi/WifiVendorHal.java +++ b/service/java/com/android/server/wifi/WifiVendorHal.java @@ -2653,56 +2653,6 @@ public class WifiVendorHal { return android.hardware.wifi.V1_2.IWifiStaIface.castFrom(iface); } - /* TODO: This method is to be removed in a future change */ - private int frameworkToHalTxPowerScenario(int scenario) { - switch (scenario) { - case WifiNative.TX_POWER_SCENARIO_VOICE_CALL: - return android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL; - default: - throw new IllegalArgumentException("bad scenario: " + scenario); - } - } - - /** - * TODO: This method will be removed in a future change - * It will be replaced with another method receiving SarInfo - * instance instead of the int for scenario - * - * Select one of the pre-configured TX power level scenarios or reset it back to normal. - * Primarily used for meeting SAR requirements during voice calls. - * - * @param scenario Should be one {@link WifiNative#TX_POWER_SCENARIO_NORMAL} or - * {@link WifiNative#TX_POWER_SCENARIO_VOICE_CALL}. - * @return true for success; false for failure or if the HAL version does not support this API. - */ - public boolean selectTxPowerScenario(int scenario) { - synchronized (sLock) { - try { - android.hardware.wifi.V1_1.IWifiChip iWifiChipV11 = getWifiChipForV1_1Mockable(); - if (iWifiChipV11 == null) return boolResult(false); - WifiStatus status; - if (scenario != WifiNative.TX_POWER_SCENARIO_NORMAL) { - int halScenario; - try { - halScenario = frameworkToHalTxPowerScenario(scenario); - } catch (IllegalArgumentException e) { - mLog.err("Illegal argument for select tx power scenario") - .c(e.toString()).flush(); - return false; - } - status = iWifiChipV11.selectTxPowerScenario(halScenario); - } else { - status = iWifiChipV11.resetTxPowerScenario(); - } - if (!ok(status)) return false; - } catch (RemoteException e) { - handleRemoteException(e); - return false; - } - return true; - } - } - /** * sarPowerBackoffRequired_1_1() * This method checks if we need to backoff wifi Tx power due to SAR requirements. diff --git a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java index 60b5f81ad..fc030b83c 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java @@ -1999,80 +1999,6 @@ public class WifiVendorHalTest { verify(mVendorHalDeathHandler).onDeath(); } - /** - * TODO: This test will be removed in a following change - * It is going to be replaced by another test using the new structure - * - * Test the new selectTxPowerScenario HIDL method invocation. This should return failure if the - * HAL service is exposing the 1.0 interface. - */ - @Test - public void testSelectTxPowerScenario_old() throws RemoteException { - assertTrue(mWifiVendorHal.startVendorHalSta()); - // Should fail because we exposed the 1.0 IWifiChip. - assertFalse( - mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_VOICE_CALL)); - verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt()); - mWifiVendorHal.stopVendorHal(); - - // Now expose the 1.1 IWifiChip. - mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper()); - when(mIWifiChipV11.selectTxPowerScenario(anyInt())).thenReturn(mWifiStatusSuccess); - - assertTrue(mWifiVendorHal.startVendorHalSta()); - assertTrue( - mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_VOICE_CALL)); - verify(mIWifiChipV11).selectTxPowerScenario( - eq(android.hardware.wifi.V1_1.IWifiChip.TxPowerScenario.VOICE_CALL)); - verify(mIWifiChipV11, never()).resetTxPowerScenario(); - mWifiVendorHal.stopVendorHal(); - } - - /** - * TODO: This test will be removed in a following change - * It is going to be replaced by another test using the new structure - * - * Test the new resetTxPowerScenario HIDL method invocation. This should return failure if the - * HAL service is exposing the 1.0 interface. - */ - @Test - public void testResetTxPowerScenario_old() throws RemoteException { - assertTrue(mWifiVendorHal.startVendorHalSta()); - // Should fail because we exposed the 1.0 IWifiChip. - assertFalse(mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_NORMAL)); - verify(mIWifiChipV11, never()).resetTxPowerScenario(); - mWifiVendorHal.stopVendorHal(); - - // Now expose the 1.1 IWifiChip. - mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper()); - when(mIWifiChipV11.resetTxPowerScenario()).thenReturn(mWifiStatusSuccess); - - assertTrue(mWifiVendorHal.startVendorHalSta()); - assertTrue(mWifiVendorHal.selectTxPowerScenario(WifiNative.TX_POWER_SCENARIO_NORMAL)); - verify(mIWifiChipV11).resetTxPowerScenario(); - verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt()); - mWifiVendorHal.stopVendorHal(); - } - - /** - * TODO: This test will be removed in a following change - * It is going to be replaced by another test using the new structure - * - * Test the new selectTxPowerScenario HIDL method invocation with a bad scenario index. - */ - @Test - public void testInvalidSelectTxPowerScenario_old() throws RemoteException { - // Expose the 1.1 IWifiChip. - mWifiVendorHal = new WifiVendorHalSpyV1_1(mHalDeviceManager, mLooper.getLooper()); - when(mIWifiChipV11.selectTxPowerScenario(anyInt())).thenReturn(mWifiStatusSuccess); - - assertTrue(mWifiVendorHal.startVendorHalSta()); - assertFalse(mWifiVendorHal.selectTxPowerScenario(-6)); - verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt()); - verify(mIWifiChipV11, never()).resetTxPowerScenario(); - mWifiVendorHal.stopVendorHal(); - } - /** * Test the selectTxPowerScenario HIDL method invocation for 1.0 interface. * This should return failure since SAR is not supported for this interface version. -- cgit v1.2.3 From 26744a15cfead946e7a4ca8b028fe4ea9bf96efe Mon Sep 17 00:00:00 2001 From: Ahmed ElArabawy Date: Tue, 1 May 2018 09:11:13 -0700 Subject: WiFi: Extend SAR to support sap/scan-only modes This commit is part of the feature of extending SAR Support in WiFi. It adds support for both AP and Scan-Only modes. This commit also adds some more general tests for SarManager class. Bug: 65174506 Test: Run Wifi unit test suite Test: ./frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: I04b61aaf69308600919c5f6e9ea61be130be9750 Signed-off-by: Ahmed ElArabawy --- .../java/com/android/server/wifi/SarManager.java | 55 ++- .../android/server/wifi/ScanOnlyModeManager.java | 7 +- .../com/android/server/wifi/SoftApManager.java | 11 +- .../java/com/android/server/wifi/WifiInjector.java | 5 +- .../com/android/server/wifi/SarManagerTest.java | 374 +++++++++++++++++++++ .../server/wifi/ScanOnlyModeManagerTest.java | 5 +- .../com/android/server/wifi/SoftApManagerTest.java | 25 +- 7 files changed, 469 insertions(+), 13 deletions(-) diff --git a/service/java/com/android/server/wifi/SarManager.java b/service/java/com/android/server/wifi/SarManager.java index f157ac41e..d38eab981 100644 --- a/service/java/com/android/server/wifi/SarManager.java +++ b/service/java/com/android/server/wifi/SarManager.java @@ -42,6 +42,8 @@ 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 @@ -181,6 +183,58 @@ public class SarManager { } } + /** + * Update Wifi SoftAP State + */ + public void setSapWifiState(int state) { + boolean newIsEnabled; + /* No action is taken if SAR is not enabled */ + if (!mEnableSarTxPowerLimit) { + 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.mIsWifiSapEnabled != newIsEnabled) { + mSarInfo.mIsWifiSapEnabled = newIsEnabled; + updateSarScenario(); + } + } + + /** + * Update Wifi ScanOnly State + */ + public void setScanOnlyWifiState(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; + } + + /* Report change to HAL if needed */ + if (mSarInfo.mIsWifiScanOnlyEnabled != newIsEnabled) { + mSarInfo.mIsWifiScanOnlyEnabled = newIsEnabled; + updateSarScenario(); + } + } + /** * Report Cell state event */ @@ -238,7 +292,6 @@ public class SarManager { * Enable/disable verbose logging. */ public void enableVerboseLogging(int verbose) { - Log.d(TAG, "Inside enableVerboseLogging: " + verbose); if (verbose > 0) { mVerboseLoggingEnabled = true; } else { diff --git a/service/java/com/android/server/wifi/ScanOnlyModeManager.java b/service/java/com/android/server/wifi/ScanOnlyModeManager.java index 346d2ca67..991657929 100644 --- a/service/java/com/android/server/wifi/ScanOnlyModeManager.java +++ b/service/java/com/android/server/wifi/ScanOnlyModeManager.java @@ -48,6 +48,7 @@ public class ScanOnlyModeManager implements ActiveModeManager { private final Listener mListener; private final ScanRequestProxy mScanRequestProxy; private final WakeupController mWakeupController; + private final SarManager mSarManager; private String mClientInterfaceName; private boolean mIfaceIsUp = false; @@ -58,13 +59,15 @@ public class ScanOnlyModeManager implements ActiveModeManager { @NonNull WifiNative wifiNative, @NonNull Listener listener, @NonNull WifiMetrics wifiMetrics, @NonNull ScanRequestProxy scanRequestProxy, - @NonNull WakeupController wakeupController) { + @NonNull WakeupController wakeupController, + @NonNull SarManager sarManager) { mContext = context; mWifiNative = wifiNative; mListener = listener; mWifiMetrics = wifiMetrics; mScanRequestProxy = scanRequestProxy; mWakeupController = wakeupController; + mSarManager = sarManager; mStateMachine = new ScanOnlyModeStateMachine(looper); } @@ -242,6 +245,7 @@ public class ScanOnlyModeManager implements ActiveModeManager { mIfaceIsUp = false; onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName)); + mSarManager.setScanOnlyWifiState(WifiManager.WIFI_STATE_ENABLED); } @Override @@ -281,6 +285,7 @@ public class ScanOnlyModeManager implements ActiveModeManager { mClientInterfaceName = null; } updateWifiState(WifiManager.WIFI_STATE_DISABLED); + mSarManager.setScanOnlyWifiState(WifiManager.WIFI_STATE_DISABLED); // once we leave started, nothing else to do... stop the state machine mStateMachine.quitNow(); diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java index 6c52918a5..1afe9ed9d 100644 --- a/service/java/com/android/server/wifi/SoftApManager.java +++ b/service/java/com/android/server/wifi/SoftApManager.java @@ -93,6 +93,8 @@ public class SoftApManager implements ActiveModeManager { private int mNumAssociatedStations = 0; private boolean mTimeoutEnabled = false; + private final SarManager mSarManager; + /** * Listener for soft AP events. */ @@ -118,7 +120,8 @@ public class SoftApManager implements ActiveModeManager { @NonNull WifiManager.SoftApCallback callback, @NonNull WifiApConfigStore wifiApConfigStore, @NonNull SoftApModeConfiguration apConfig, - @NonNull WifiMetrics wifiMetrics) { + @NonNull WifiMetrics wifiMetrics, + @NonNull SarManager sarManager) { mContext = context; mFrameworkFacade = framework; mWifiNative = wifiNative; @@ -133,6 +136,7 @@ public class SoftApManager implements ActiveModeManager { mApConfig = config; } mWifiMetrics = wifiMetrics; + mSarManager = sarManager; mStateMachine = new SoftApStateMachine(looper); } @@ -491,6 +495,9 @@ public class SoftApManager implements ActiveModeManager { if (mSettingObserver != null) { mSettingObserver.register(); } + + mSarManager.setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED); + Log.d(TAG, "Resetting num stations on start"); mNumAssociatedStations = 0; scheduleTimeoutMessage(); @@ -512,6 +519,8 @@ public class SoftApManager implements ActiveModeManager { mWifiMetrics.addSoftApUpChangedEvent(false, mMode); updateApState(WifiManager.WIFI_AP_STATE_DISABLED, WifiManager.WIFI_AP_STATE_DISABLING, 0); + + mSarManager.setSapWifiState(WifiManager.WIFI_AP_STATE_DISABLED); mApInterfaceName = null; mIfaceIsUp = false; mStateMachine.quitNow(); diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java index 5a4fe570b..199548090 100644 --- a/service/java/com/android/server/wifi/WifiInjector.java +++ b/service/java/com/android/server/wifi/WifiInjector.java @@ -465,7 +465,7 @@ public class WifiInjector { @NonNull SoftApModeConfiguration config) { return new SoftApManager(mContext, mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mWifiNative, mCountryCode.getCountryCode(), callback, - mWifiApConfigStore, config, mWifiMetrics); + mWifiApConfigStore, config, mWifiMetrics, mSarManager); } /** @@ -477,7 +477,8 @@ public class WifiInjector { public ScanOnlyModeManager makeScanOnlyModeManager( @NonNull ScanOnlyModeManager.Listener listener) { return new ScanOnlyModeManager(mContext, mWifiStateMachineHandlerThread.getLooper(), - mWifiNative, listener, mWifiMetrics, mScanRequestProxy, mWakeupController); + mWifiNative, listener, mWifiMetrics, mScanRequestProxy, mWakeupController, + mSarManager); } /** diff --git a/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java index 69878cdcf..369dcba33 100644 --- a/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java @@ -374,6 +374,10 @@ public class SarManagerTest { /* Device should set tx power scenario to NORMAL again */ inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); assertFalse(mSarInfo.mIsVoiceCall); + + /* Disable WiFi State */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_DISABLED); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); } /** @@ -397,6 +401,134 @@ public class SarManagerTest { inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); } + /** + * Test that for devices supporting SAR the following scenario: + * - Wifi enabled + * - A call starts + * - Wifi disabled + * - Call ends + * - Wifi back on + */ + @Test + public void testSarMgr_enabledSar_wifiOn_offHook_wifiOff_onHook() throws Exception { + 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(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + + /* Set phone state to OFFHOOK */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertTrue(mSarInfo.mIsVoiceCall); + + /* Disable WiFi State */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_DISABLED); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + + /* Set state back to ONHOOK */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_IDLE, ""); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + + /* Enable WiFi State again */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + } + + /** + * Test that for devices supporting SAR, Wifi disabled, a call starts, a call ends, Wifi + * enabled. + */ + @Test + public void testSarMgr_enabledSar_wifiOff_offHook_onHook_wifiOn() throws Exception { + createSarManager(true, false); + + InOrder inOrder = inOrder(mWifiNative); + + /* Set phone state to OFFHOOK */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + + /* Set state back to ONHOOK */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_IDLE, ""); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + + /* Enable WiFi State */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + } + + /** + * Test that for devices supporting SAR, Wifi disabled, a call starts, wifi on, wifi off, + * call ends. + */ + @Test + public void testSarMgr_enabledSar_offHook_wifiOnOff_onHook() throws Exception { + createSarManager(true, false); + + InOrder inOrder = inOrder(mWifiNative); + + /* Set phone state to OFFHOOK */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + + /* Enable WiFi State */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + assertNotNull(mSarInfo); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertTrue(mSarInfo.mIsVoiceCall); + + /* Disable WiFi State */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_DISABLED); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + + /* Set state back to ONHOOK */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_IDLE, ""); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + } + + /** + * Test the error case for for devices supporting SAR, Wifi enabled, a call starts, + * call ends. With all of these cases, the call to set Tx power scenario fails. + */ + @Test + public void testSarMgr_enabledSar_error_wifiOn_offOnHook() throws Exception { + createSarManager(true, false); + + when(mWifiNative.selectTxPowerScenario(any(SarInfo.class))).thenReturn(false); + InOrder inOrder = inOrder(mWifiNative); + + /* Enable WiFi State */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + assertNotNull(mSarInfo); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + + /* Set phone state to OFFHOOK */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_OFFHOOK, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertTrue(mSarInfo.mIsVoiceCall); + + /* Set state back to ONHOOK */ + mPhoneStateListener.onCallStateChanged(CALL_STATE_IDLE, ""); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + } + /** * 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 @@ -654,4 +786,246 @@ public class SarManagerTest { assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); assertFalse(mSarInfo.mIsVoiceCall); } + + /** + * Test that Start of SoftAP for a device that does not have SAR enabled does not result in + * setting the Tx power scenario + */ + @Test + public void testSarMgr_disabledTxPowerScenario_sapOn() throws Exception { + createSarManager(false, false); + + /* Enable WiFi SoftAP State */ + mSarMgr.setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED); + + verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + } + + /** + * Test that Start of SoftAP for a device that has SAR enabled, SAR sensor disabled. + */ + @Test + public void testSarMgr_enabledTxPowerScenario_sapOn() throws Exception { + createSarManager(true, false); + + InOrder inOrder = inOrder(mWifiNative); + + /* Enable WiFi SoftAP State */ + mSarMgr.setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + assertTrue(mSarInfo.mIsWifiSapEnabled); + assertFalse(mSarInfo.mIsWifiScanOnlyEnabled); + } + + /** + * Test that for a device that has SAR enabled, SAR sensor enabled, near head, and when + * wifi sta is enabled, turning on sap then turning it off. + */ + @Test + public void testSarMgr_enabledTxPowerScenario_staOn_sapOnOff() throws Exception { + createSarManager(true, true); + + InOrder inOrder = inOrder(mWifiNative); + + /* Sensor event */ + sendSensorEvent(SAR_SENSOR_EVENT_HEAD); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + + /* Enable WiFi Client State */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsWifiSapEnabled); + + /* Enable WiFi SoftAP State */ + mSarMgr.setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertTrue(mSarInfo.mIsWifiSapEnabled); + + /* Disable Wifi SoftAP state */ + mSarMgr.setSapWifiState(WifiManager.WIFI_AP_STATE_DISABLED); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_HEAD, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsWifiSapEnabled); + } + + /** + * Test that for a device that has SAR enabled, SAR sensor enabled, Near body, and when + * disabling wifi softAP while Wifi Sta is also disabled, no update to the HAL for Tx + * power scenario is issued. + * Then, when wifi client is enabled, the Tx Power scenario is set. + * This is to verify that no call to update tx power when all wifi modes are disabled. + */ + @Test + public void testSarMgr_enabledTxPowerScenario_sapOnOff_staOffOn() 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 softAP State */ + mSarMgr.setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_BODY, mSarInfo.mSensorState); + assertTrue(mSarInfo.mIsWifiSapEnabled); + + /* Disable Wifi SoftAP state */ + mSarMgr.setSapWifiState(WifiManager.WIFI_AP_STATE_DISABLED); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + + /* Enable WiFi Clinet State */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertEquals(SarInfo.SAR_SENSOR_NEAR_BODY, mSarInfo.mSensorState); + assertFalse(mSarInfo.mIsWifiSapEnabled); + } + + /** + * Test that for a device that has SAR enabled, when scan-only state is enabled with both SoftAP + * and Client states disabled, the SarInfo is reported with proper values. + */ + @Test + public void testSarMgr_enabledTxPowerScenario_staOff_sapOff_scanOnlyOn() throws Exception { + createSarManager(true, false); + + /* Enable Wifi ScanOnly State */ + mSarMgr.setScanOnlyWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsWifiSapEnabled); + assertTrue(mSarInfo.mIsWifiScanOnlyEnabled); + } + + /** + * Test that for a device that has SAR enabled, when scan-only state is enabled, and then + * client state is enabled, no additional setting of Tx power scenario is initiated + */ + @Test + public void testSarMgr_enabledTxPowerScenario_staOn_sapOff_scanOnlyOn() throws Exception { + createSarManager(true, false); + + InOrder inOrder = inOrder(mWifiNative); + + /* Enable Wifi ScanOnly State */ + mSarMgr.setScanOnlyWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsWifiSapEnabled); + assertTrue(mSarInfo.mIsWifiScanOnlyEnabled); + + /* Now enable Client state */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + } + + /** + * Test the success case for for devices supporting SAR, with no SAR sensor support, + * Wifi enabled, SoftAP enabled, wifi disabled, scan-only enabled, SoftAP disabled. + * + * SarManager should report these changes as they occur(only when changes occur to + * inputs affecting the SAR scenario). + */ + @Test + public void testSarMgr_enabledTxPowerScenario_wifi_sap_scanOnly() throws Exception { + createSarManager(true, false); + + InOrder inOrder = inOrder(mWifiNative); + + /* Enable WiFi State */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + assertFalse(mSarInfo.mIsWifiSapEnabled); + assertFalse(mSarInfo.mIsWifiScanOnlyEnabled); + + /* Enable SoftAP state */ + mSarMgr.setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + assertTrue(mSarInfo.mIsWifiSapEnabled); + assertFalse(mSarInfo.mIsWifiScanOnlyEnabled); + + /* Disable WiFi State */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_DISABLED); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + + /* Enable ScanOnly state */ + mSarMgr.setScanOnlyWifiState(WifiManager.WIFI_STATE_ENABLED); + inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class)); + + /* Disable SoftAP state */ + mSarMgr.setSapWifiState(WifiManager.WIFI_AP_STATE_DISABLED); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + assertFalse(mSarInfo.mIsWifiSapEnabled); + assertTrue(mSarInfo.mIsWifiScanOnlyEnabled); + } + + /** + * Test the error case for devices supporting SAR, with no SAR sensor support, + * Wifi enabled, SoftAP enabled, wifi disabled, scan-only enabled, SoftAP disabled + * Throughout this test case, calls to the hal return with error. + */ + @Test + public void testSarMgr_enabledTxPowerScenario_error_wifi_sap_scanOnly() throws Exception { + createSarManager(true, false); + + when(mWifiNative.selectTxPowerScenario(any(SarInfo.class))).thenReturn(false); + InOrder inOrder = inOrder(mWifiNative); + + /* Enable WiFi State */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_ENABLED); + captureSarInfo(mWifiNative); + + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + assertFalse(mSarInfo.mIsWifiSapEnabled); + assertFalse(mSarInfo.mIsWifiScanOnlyEnabled); + + /* Enable SoftAP state */ + mSarMgr.setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + assertTrue(mSarInfo.mIsWifiSapEnabled); + assertFalse(mSarInfo.mIsWifiScanOnlyEnabled); + + /* Disable WiFi State, reporting should still happen */ + mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_DISABLED); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + assertTrue(mSarInfo.mIsWifiSapEnabled); + assertFalse(mSarInfo.mIsWifiScanOnlyEnabled); + assertFalse(mSarInfo.mIsWifiClientEnabled); + + /* Enable ScanOnly state */ + mSarMgr.setScanOnlyWifiState(WifiManager.WIFI_STATE_ENABLED); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + assertTrue(mSarInfo.mIsWifiSapEnabled); + assertTrue(mSarInfo.mIsWifiScanOnlyEnabled); + assertFalse(mSarInfo.mIsWifiClientEnabled); + + /* Disable SoftAP state */ + mSarMgr.setSapWifiState(WifiManager.WIFI_AP_STATE_DISABLED); + inOrder.verify(mWifiNative).selectTxPowerScenario(eq(mSarInfo)); + assertFalse(mSarInfo.mIsVoiceCall); + assertFalse(mSarInfo.mIsWifiSapEnabled); + assertTrue(mSarInfo.mIsWifiScanOnlyEnabled); + assertFalse(mSarInfo.mIsWifiClientEnabled); + } } diff --git a/tests/wifitests/src/com/android/server/wifi/ScanOnlyModeManagerTest.java b/tests/wifitests/src/com/android/server/wifi/ScanOnlyModeManagerTest.java index 41362eec6..8c2444592 100644 --- a/tests/wifitests/src/com/android/server/wifi/ScanOnlyModeManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/ScanOnlyModeManagerTest.java @@ -71,6 +71,7 @@ public class ScanOnlyModeManagerTest { @Mock WifiMonitor mWifiMonitor; @Mock ScanRequestProxy mScanRequestProxy; @Mock WakeupController mWakeupController; + @Mock SarManager mSarManager; final ArgumentCaptor mInterfaceCallbackCaptor = ArgumentCaptor.forClass(WifiNative.InterfaceCallback.class); @@ -86,7 +87,7 @@ public class ScanOnlyModeManagerTest { private ScanOnlyModeManager createScanOnlyModeManager() { return new ScanOnlyModeManager(mContext, mLooper.getLooper(), mWifiNative, mListener, - mWifiMetrics, mScanRequestProxy, mWakeupController); + mWifiMetrics, mScanRequestProxy, mWakeupController, mSarManager); } private void startScanOnlyModeAndVerifyEnabled() throws Exception { @@ -112,6 +113,7 @@ public class ScanOnlyModeManagerTest { checkWifiStateChangeListenerUpdate(WIFI_STATE_ENABLED); verify(mScanRequestProxy, atLeastOnce()).clearScanResults(); verify(mScanRequestProxy, atLeastOnce()).enableScanningForHiddenNetworks(false); + verify(mSarManager).setScanOnlyWifiState(eq(WIFI_STATE_ENABLED)); } private void checkWifiScanStateChangedBroadcast(Intent intent, int expectedCurrentState) { @@ -170,6 +172,7 @@ public class ScanOnlyModeManagerTest { mLooper.dispatchAll(); verify(mWifiNative).teardownInterface(TEST_INTERFACE_NAME); verify(mContext, never()).sendStickyBroadcastAsUser(any(), eq(UserHandle.ALL)); + verify(mSarManager).setScanOnlyWifiState(eq(WIFI_STATE_DISABLED)); verifyNoMoreInteractions(mListener); } diff --git a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java index 0b937849d..6a2c21242 100644 --- a/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java @@ -90,6 +90,7 @@ public class SoftApManagerTest { @Mock FrameworkFacade mFrameworkFacade; @Mock WifiApConfigStore mWifiApConfigStore; @Mock WifiMetrics mWifiMetrics; + @Mock SarManager mSarManager; final ArgumentCaptor mWifiNativeInterfaceCallbackCaptor = ArgumentCaptor.forClass(WifiNative.InterfaceCallback.class); final ArgumentCaptor mSoftApListenerCaptor = @@ -133,7 +134,8 @@ public class SoftApManagerTest { mCallback, mWifiApConfigStore, config, - mWifiMetrics); + mWifiMetrics, + mSarManager); mLooper.dispatchAll(); return newSoftApManager; @@ -205,7 +207,8 @@ public class SoftApManagerTest { mCallback, mWifiApConfigStore, nullApConfig, - mWifiMetrics); + mWifiMetrics, + mSarManager); mLooper.dispatchAll(); newSoftApManager.start(); mLooper.dispatchAll(); @@ -247,7 +250,8 @@ public class SoftApManagerTest { mCallback, mWifiApConfigStore, nullApConfig, - mWifiMetrics); + mWifiMetrics, + mSarManager); mLooper.dispatchAll(); newSoftApManager.start(); mLooper.dispatchAll(); @@ -288,7 +292,8 @@ public class SoftApManagerTest { mCallback, mWifiApConfigStore, nullApConfig, - mWifiMetrics); + mWifiMetrics, + mSarManager); mLooper.dispatchAll(); newSoftApManager.start(); mLooper.dispatchAll(); @@ -329,7 +334,8 @@ public class SoftApManagerTest { mCallback, mWifiApConfigStore, softApConfig, - mWifiMetrics); + mWifiMetrics, + mSarManager); mLooper.dispatchAll(); newSoftApManager.start(); mLooper.dispatchAll(); @@ -371,7 +377,8 @@ public class SoftApManagerTest { mCallback, mWifiApConfigStore, softApConfig, - mWifiMetrics); + mWifiMetrics, + mSarManager); mLooper.dispatchAll(); newSoftApManager.start(); mLooper.dispatchAll(); @@ -408,7 +415,8 @@ public class SoftApManagerTest { mCallback, mWifiApConfigStore, softApModeConfig, - mWifiMetrics); + mWifiMetrics, + mSarManager); mLooper.dispatchAll(); newSoftApManager.start(); @@ -429,6 +437,7 @@ public class SoftApManagerTest { mLooper.dispatchAll(); /* Verify no state changes. */ verify(mCallback, never()).onStateChanged(anyInt(), anyInt()); + verify(mSarManager, never()).setSapWifiState(anyInt()); verify(mContext, never()).sendStickyBroadcastAsUser(any(), any()); verify(mWifiNative, never()).teardownInterface(anyString()); } @@ -459,6 +468,7 @@ public class SoftApManagerTest { softApModeConfig.getTargetMode()); order.verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_DISABLED, 0); + verify(mSarManager).setSapWifiState(WifiManager.WIFI_AP_STATE_DISABLED); order.verify(mContext).sendStickyBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL)); checkApStateChangedBroadcast(intentCaptor.getValue(), WIFI_AP_STATE_DISABLED, @@ -938,6 +948,7 @@ public class SoftApManagerTest { mLooper.dispatchAll(); order.verify(mCallback).onStateChanged(WifiManager.WIFI_AP_STATE_ENABLED, 0); order.verify(mCallback).onNumClientsChanged(0); + verify(mSarManager).setSapWifiState(WifiManager.WIFI_AP_STATE_ENABLED); verify(mContext, times(2)).sendStickyBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL)); List capturedIntents = intentCaptor.getAllValues(); -- cgit v1.2.3