Merge "Add build of Realtek WIFI HAL support"
am: 81e8432a1e

Change-Id: Ib45c30cb496bc21c826e456d3c7fe10990debfa9
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index 15b9cc7..26cb4ff 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -136,17 +136,6 @@
        return true;
     }
 
-    /**
-     * Create a new instance of WifiApConfigStore.
-     * @param context reference to a Context
-     * @param backupManagerProxy reference to a BackupManagerProxy
-     * @return an instance of WifiApConfigStore
-     */
-    public WifiApConfigStore makeApConfigStore(Context context,
-                                               BackupManagerProxy backupManagerProxy) {
-        return new WifiApConfigStore(context, backupManagerProxy);
-    }
-
     public long getTxPackets(String iface) {
         return TrafficStats.getTxPackets(iface);
     }
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 0000000..6eb777c
--- /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/service/java/com/android/server/wifi/SarManager.java b/service/java/com/android/server/wifi/SarManager.java
index da48a85..d38eab9 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_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;
@@ -40,24 +42,28 @@
  * 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.
- * - 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 +71,8 @@
     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 +81,157 @@
     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();
 
-        registerListeners();
+        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;
+        }
+
+        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();
+    }
 
-            /* Listen for Phone State changes */
-            registerPhoneListener();
+    /**
+     * 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;
+        }
+
+        /* Report change to HAL if needed */
+        if (mSarInfo.mIsWifiClientEnabled != newIsEnabled) {
+            mSarInfo.mIsWifiClientEnabled = newIsEnabled;
+            updateSarScenario();
+        }
+    }
+
+    /**
+     * 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();
         }
     }
 
@@ -104,42 +239,52 @@
      * 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;
+    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;
+        }
 
-        if (state == WifiManager.WIFI_STATE_DISABLED && mWifiStaEnabled) {
-            mWifiStaEnabled = false;
-        } else if (state == WifiManager.WIFI_STATE_ENABLED && !mWifiStaEnabled) {
-            mWifiStaEnabled = true;
-
-            /* 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();
         }
     }
 
@@ -147,7 +292,6 @@
      * Enable/disable verbose logging.
      */
     public void enableVerboseLogging(int verbose) {
-        Log.d(TAG, "Inside enableVerboseLogging: " + verbose);
         if (verbose > 0) {
             mVerboseLoggingEnabled = true;
         } else {
@@ -155,18 +299,16 @@
         }
     }
 
+    /**
+     * dump()
+     * Dumps SarManager state (as well as its SarInfo member variable state)
+     */
     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.
-     */
-    private void registerPhoneListener() {
-        Log.i(TAG, "Registering for telephony call state changes");
-        mTelephonyManager.listen(
-                mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+        pw.println("isSarEnabled: " + mEnableSarTxPowerLimit);
+        pw.println("isSarSensorEnabled: " + mEnableSarBodyProximity);
+        pw.println("");
+        mSarInfo.dump(fd, pw, args);
     }
 
     /**
@@ -177,69 +319,96 @@
             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;
 
-        if (newSarScenario != mCurrentSarScenario) {
-
-            // Only update HAL with new scenario if WiFi interface is enabled
-            if (mWifiStaEnabled) {
-                Log.d(TAG, "Sending SAR Scenario #" + scenarioToString(newSarScenario));
-                sendTxPowerScenario(newSarScenario);
+        /**
+         * 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;
             }
 
-            mCurrentSarScenario = newSarScenario;
+            /* Get the sensor object */
+            Sensor sensor = null;
+            List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+            for (Sensor s : sensorList) {
+                if (sensorType.equals(s.getStringType())) {
+                    sensor = s;
+                    break;
+                }
+            }
+            if (sensor == null) {
+                Log.e(TAG, "Failed to Find the SAR Sensor");
+                return false;
+            }
+
+            /* Now register the listener */
+            if (!mSensorManager.registerListener(this, sensor,
+                    SensorManager.SENSOR_DELAY_NORMAL)) {
+                Log.e(TAG, "Failed to register SAR Sensor Listener");
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * onSensorChanged()
+         * This callback is called when a SAR sensor event is received
+         * Note that this runs in the 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) {
         }
     }
 
     /**
-     * sendTxPowerScenario()
-     * Update HAL with the new power scenario.
+     * updateSarScenario()
+     * Update HAL with the new SAR scenario if needed.
      */
-    private void sendTxPowerScenario(int newSarScenario) {
-        if (!mWifiNative.selectTxPowerScenario(newSarScenario)) {
-            Log.e(TAG, "Failed to set TX power scenario");
-        }
-    }
-
-    /**
-     * Convert SAR Scenario to string
-     */
-    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;
         }
 
-        return str;
+        /* Report info to HAL*/
+        if (mWifiNative.selectTxPowerScenario(mSarInfo)) {
+            mSarInfo.reportingSuccessful();
+        } else {
+            Log.e(TAG, "Failed in WifiNative.selectTxPowerScenario()");
+        }
+
+        return;
     }
 }
diff --git a/service/java/com/android/server/wifi/ScanOnlyModeManager.java b/service/java/com/android/server/wifi/ScanOnlyModeManager.java
index 346d2ca..9916579 100644
--- a/service/java/com/android/server/wifi/ScanOnlyModeManager.java
+++ b/service/java/com/android/server/wifi/ScanOnlyModeManager.java
@@ -48,6 +48,7 @@
     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 @@
                         @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 @@
 
                 mIfaceIsUp = false;
                 onUpChanged(mWifiNative.isInterfaceUp(mClientInterfaceName));
+                mSarManager.setScanOnlyWifiState(WifiManager.WIFI_STATE_ENABLED);
             }
 
             @Override
@@ -281,6 +285,7 @@
                     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 6c52918..1afe9ed 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -93,6 +93,8 @@
     private int mNumAssociatedStations = 0;
     private boolean mTimeoutEnabled = false;
 
+    private final SarManager mSarManager;
+
     /**
      * Listener for soft AP events.
      */
@@ -118,7 +120,8 @@
                          @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 @@
             mApConfig = config;
         }
         mWifiMetrics = wifiMetrics;
+        mSarManager = sarManager;
         mStateMachine = new SoftApStateMachine(looper);
     }
 
@@ -491,6 +495,9 @@
                 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 @@
                 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/WifiApConfigStore.java b/service/java/com/android/server/wifi/WifiApConfigStore.java
index 109c0a7..d21452c 100644
--- a/service/java/com/android/server/wifi/WifiApConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiApConfigStore.java
@@ -17,15 +17,25 @@
 package com.android.server.wifi;
 
 import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.notification.SystemNotificationChannels;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -44,6 +54,10 @@
  */
 public class WifiApConfigStore {
 
+    // Intent when user has interacted with the softap settings change notification
+    public static final String ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT =
+            "com.android.server.wifi.WifiApConfigStoreUtil.HOTSPOT_CONFIG_USER_TAPPED_CONTENT";
+
     private static final String TAG = "WifiApConfigStore";
 
     private static final String DEFAULT_AP_CONFIG_FILE =
@@ -71,19 +85,26 @@
     private ArrayList<Integer> mAllowed2GChannel = null;
 
     private final Context mContext;
+    private final Handler mHandler;
     private final String mApConfigFile;
     private final BackupManagerProxy mBackupManagerProxy;
+    private final FrameworkFacade mFrameworkFacade;
     private boolean mRequiresApBandConversion = false;
 
-    WifiApConfigStore(Context context, BackupManagerProxy backupManagerProxy) {
-        this(context, backupManagerProxy, DEFAULT_AP_CONFIG_FILE);
+    WifiApConfigStore(Context context, Looper looper,
+            BackupManagerProxy backupManagerProxy, FrameworkFacade frameworkFacade) {
+        this(context, looper, backupManagerProxy, frameworkFacade, DEFAULT_AP_CONFIG_FILE);
     }
 
     WifiApConfigStore(Context context,
+                      Looper looper,
                       BackupManagerProxy backupManagerProxy,
+                      FrameworkFacade frameworkFacade,
                       String apConfigFile) {
         mContext = context;
+        mHandler = new Handler(looper);
         mBackupManagerProxy = backupManagerProxy;
+        mFrameworkFacade = frameworkFacade;
         mApConfigFile = apConfigFile;
 
         String ap2GChannelListStr = mContext.getResources().getString(
@@ -111,8 +132,30 @@
             /* Save the default configuration to persistent storage. */
             writeApConfiguration(mApConfigFile, mWifiApConfig);
         }
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT);
+        mContext.registerReceiver(
+                mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler);
     }
 
+    private final BroadcastReceiver mBroadcastReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    // For now we only have one registered listener, but we easily could expand this
+                    // to support multiple signals.  Starting off with a switch to support trivial
+                    // expansion.
+                    switch(intent.getAction()) {
+                        case ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT:
+                            handleUserHotspotConfigTappedContent();
+                            break;
+                        default:
+                            Log.e(TAG, "Unknown action " + intent.getAction());
+                    }
+                }
+            };
+
     /**
      * Return the current soft access point configuration.
      */
@@ -145,6 +188,43 @@
         return mAllowed2GChannel;
     }
 
+    /**
+     * Helper method to create and send notification to user of apBand conversion.
+     *
+     * @param packageName name of the calling app
+     */
+    public void notifyUserOfApBandConversion(String packageName) {
+        Log.w(TAG, "ready to post notification - triggered by " + packageName);
+        Notification notification = createConversionNotification();
+        NotificationManager notificationManager = (NotificationManager)
+                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.notify(SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED, notification);
+    }
+
+    private Notification createConversionNotification() {
+        CharSequence title = mContext.getText(R.string.wifi_softap_config_change);
+        CharSequence contentSummary = mContext.getText(R.string.wifi_softap_config_change_summary);
+        CharSequence content = mContext.getText(R.string.wifi_softap_config_change_detailed);
+        int color = mContext.getResources()
+                .getColor(R.color.system_notification_accent_color, mContext.getTheme());
+
+        return new Notification.Builder(mContext, SystemNotificationChannels.NETWORK_STATUS)
+                .setSmallIcon(R.drawable.ic_wifi_settings)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setCategory(Notification.CATEGORY_SYSTEM)
+                .setContentTitle(title)
+                .setContentText(contentSummary)
+                .setContentIntent(getPrivateBroadcast(ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT))
+                .setTicker(title)
+                .setShowWhen(false)
+                .setLocalOnly(true)
+                .setColor(color)
+                .setStyle(new Notification.BigTextStyle().bigText(content)
+                                                         .setBigContentTitle(title)
+                                                         .setSummaryText(contentSummary))
+                .build();
+    }
+
     private WifiConfiguration apBandCheckConvert(WifiConfiguration config) {
         if (mRequiresApBandConversion) {
             // some devices are unable to support 5GHz only operation, check for 5GHz and
@@ -386,4 +466,29 @@
 
         return true;
     }
+
+    /**
+     * Helper method to start up settings on the softap config page.
+     */
+    private void startSoftApSettings() {
+        mContext.startActivity(
+                new Intent("com.android.settings.WIFI_TETHER_SETTINGS")
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
+
+    /**
+     * Helper method to trigger settings to open the softap config page
+     */
+    private void handleUserHotspotConfigTappedContent() {
+        startSoftApSettings();
+        NotificationManager notificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.cancel(SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED);
+    }
+
+    private PendingIntent getPrivateBroadcast(String action) {
+        Intent intent = new Intent(action).setPackage("android");
+        return mFrameworkFacade.getBroadcast(
+                mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiController.java b/service/java/com/android/server/wifi/WifiController.java
index 05ce473..d9afc9f 100644
--- a/service/java/com/android/server/wifi/WifiController.java
+++ b/service/java/com/android/server/wifi/WifiController.java
@@ -308,11 +308,7 @@
                     break;
                 case CMD_EMERGENCY_CALL_STATE_CHANGED:
                 case CMD_EMERGENCY_MODE_CHANGED:
-                    boolean configWiFiDisableInECBM =
-                            mFacade.getConfigWiFiDisableInECBM(mContext);
-                    log("WifiController msg " + msg + " getConfigWiFiDisableInECBM "
-                            + configWiFiDisableInECBM);
-                    if ((msg.arg1 == 1) && configWiFiDisableInECBM) {
+                    if (msg.arg1 == 1) {
                         transitionTo(mEcmState);
                     }
                     break;
@@ -598,8 +594,15 @@
         private int mEcmEntryCount;
         @Override
         public void enter() {
-            mWifiStateMachinePrime.shutdownWifi();
-            mWifiStateMachine.clearANQPCache();
+            mWifiStateMachinePrime.stopSoftAPMode();
+            boolean configWiFiDisableInECBM =
+                    mFacade.getConfigWiFiDisableInECBM(mContext);
+            log("WifiController msg getConfigWiFiDisableInECBM "
+                    + configWiFiDisableInECBM);
+            if (configWiFiDisableInECBM) {
+                mWifiStateMachinePrime.shutdownWifi();
+                mWifiStateMachine.clearANQPCache();
+            }
             mEcmEntryCount = 1;
         }
 
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 0e30af8..dbf730a 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.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;
@@ -204,7 +205,8 @@
                 SystemProperties.get(BOOT_DEFAULT_WIFI_COUNTRY_CODE),
                 mContext.getResources()
                         .getBoolean(R.bool.config_wifi_revert_country_code_on_cellular_loss));
-        mWifiApConfigStore = new WifiApConfigStore(mContext, mBackupManagerProxy);
+        mWifiApConfigStore = new WifiApConfigStore(
+                mContext, wifiStateMachineLooper, mBackupManagerProxy, mFrameworkFacade);
 
         // WifiConfigManager/Store objects and their dependencies.
         // New config store
@@ -253,7 +255,7 @@
                 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,
@@ -464,7 +466,7 @@
                                            @NonNull SoftApModeConfiguration config) {
         return new SoftApManager(mContext, mWifiStateMachineHandlerThread.getLooper(),
                 mFrameworkFacade, mWifiNative, mCountryCode.getCountryCode(), callback,
-                mWifiApConfigStore, config, mWifiMetrics);
+                mWifiApConfigStore, config, mWifiMetrics, mSarManager);
     }
 
     /**
@@ -476,7 +478,8 @@
     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/service/java/com/android/server/wifi/WifiNative.java b/service/java/com/android/server/wifi/WifiNative.java
index 0f78587..bc599c1 100644
--- a/service/java/com/android/server/wifi/WifiNative.java
+++ b/service/java/com/android/server/wifi/WifiNative.java
@@ -2820,21 +2820,14 @@
     }
 
     /**
-     * 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;
-
-    /**
-     * 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.
+     * Select one of the pre-configured transmit power level scenarios or reset it back to normal.
+     * Primarily used for meeting SAR requirements.
      *
-     * @param scenario Should be one {@link #TX_POWER_SCENARIO_NORMAL} or
-     *        {@link #TX_POWER_SCENARIO_VOICE_CALL}.
+     * @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(int scenario) {
-        return mWifiVendorHal.selectTxPowerScenario(scenario);
+    public boolean selectTxPowerScenario(SarInfo sarInfo) {
+        return mWifiVendorHal.selectTxPowerScenario(sarInfo);
     }
 
     /********************************************************
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index a0bbded..cb16efd 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -1583,6 +1583,21 @@
     }
 
     /**
+     * Method used to inform user of Ap Configuration conversion due to hardware.
+     */
+    @Override
+    public void notifyUserOfApBandConversion(String packageName) {
+        enforceNetworkSettingsPermission();
+
+        if (mVerboseLoggingEnabled) {
+            mLog.info("notifyUserOfApBandConversion uid=% packageName=%")
+                    .c(Binder.getCallingUid()).c(packageName).flush();
+        }
+
+        mWifiApConfigStore.notifyUserOfApBandConversion(packageName);
+    }
+
+    /**
      * see {@link android.net.wifi.WifiManager#isScanAlwaysAvailable()}
      */
     @Override
diff --git a/service/java/com/android/server/wifi/WifiVendorHal.java b/service/java/com/android/server/wifi/WifiVendorHal.java
index e281209..7c02206 100644
--- a/service/java/com/android/server/wifi/WifiVendorHal.java
+++ b/service/java/com/android/server/wifi/WifiVendorHal.java
@@ -200,7 +200,7 @@
 
         mVerboseLog.err("% returns %")
                 .c(niceMethodName(trace, 3))
-                .c(HexDump.dumpHexString(result))
+                .c(result == null ? "(null)" : HexDump.dumpHexString(result))
                 .flush();
 
         return result;
@@ -2652,13 +2652,95 @@
         return android.hardware.wifi.V1_2.IWifiStaIface.castFrom(iface);
     }
 
+    /**
+     * 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;
+    }
 
-    private int frameworkToHalTxPowerScenario(int scenario) {
-        switch (scenario) {
-            case WifiNative.TX_POWER_SCENARIO_VOICE_CALL:
+    /**
+     * 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;
-            default:
-                throw new IllegalArgumentException("bad scenario: " + scenario);
+            } else {
+                throw new IllegalArgumentException("bad scenario: voice call not active");
+            }
         }
     }
 
@@ -2666,35 +2748,130 @@
      * 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}.
+     * 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(int scenario) {
+    public boolean selectTxPowerScenario(SarInfo sarInfo) {
         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();
+            // 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;
                     }
-                    status = iWifiChipV11.selectTxPowerScenario(halScenario);
-                } else {
-                    status = iWifiChipV11.resetTxPowerScenario();
                 }
-                if (!ok(status)) return false;
-            } catch (RemoteException e) {
-                handleRemoteException(e);
-                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;
         }
     }
 
diff --git a/service/java/com/android/server/wifi/rtt/RttMetrics.java b/service/java/com/android/server/wifi/rtt/RttMetrics.java
index c1bec8f..ecc1c48 100644
--- a/service/java/com/android/server/wifi/rtt/RttMetrics.java
+++ b/service/java/com/android/server/wifi/rtt/RttMetrics.java
@@ -116,6 +116,7 @@
             return "numCalls=" + numCalls + ", numIndividualCalls=" + numIndividualCalls
                     + ", perUidInfo=" + perUidInfo + ", numRequestsHistogram="
                     + numRequestsHistogram + ", requestGapHistogram=" + requestGapHistogram
+                    + ", statusHistogram=" + statusHistogram
                     + ", measuredDistanceHistogram=" + measuredDistanceHistogram;
         }
     }
diff --git a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
index cf43eb3..1a85c28 100644
--- a/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
+++ b/service/java/com/android/server/wifi/util/WifiPermissionsUtil.java
@@ -173,27 +173,28 @@
      */
     public void enforceCanAccessScanResults(String pkgName, int uid) throws SecurityException {
         mAppOps.checkPackage(uid, pkgName);
+
+        // Apps with NETWORK_SETTINGS & NETWORK_SETUP_WIZARD are granted a bypass.
+        if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid)) {
+            return;
+        }
+
+        // Location mode must be enabled
+        if (!isLocationModeEnabled()) {
+            // Location mode is disabled, scan results cannot be returned
+            throw new SecurityException("Location mode is disabled for the device");
+        }
+
         // Check if the calling Uid has CAN_READ_PEER_MAC_ADDRESS permission.
         boolean canCallingUidAccessLocation = checkCallerHasPeersMacAddressPermission(uid);
-        // LocationAccess by App: Location Mode must be enabled and caller must have
+        // LocationAccess by App: caller must have
         // Coarse Location permission to have access to location information.
-        boolean canAppPackageUseLocation = isLocationModeEnabled(pkgName)
-                        && checkCallersLocationPermission(pkgName, uid);
-        // "Connectivity" apps can access scan results if they have both the location permission and
-        // (ACCESS_WIFI_STATE or CHANGE_WIFI_STATE), if wifi is enabled and location is off.
-        // While subtle, the requirement of having wifi enabled is enforced by the lack of private
-        // information when wifi is toggled off and we will not enter Scan mode if Location is
-        // toggled off.
-        boolean appTypeConnectivity = checkCallersLocationPermission(pkgName, uid)
-                && (checkChangePermission(uid) || checkWifiAccessPermission(uid));
+        boolean canAppPackageUseLocation = checkCallersLocationPermission(pkgName, uid);
 
         // If neither caller or app has location access, there is no need to check
         // any other permissions. Deny access to scan results.
         if (!canCallingUidAccessLocation && !canAppPackageUseLocation) {
-            // also check if it is a connectivity app
-            if (!appTypeConnectivity) {
-                throw new SecurityException("UID " + uid + " has no location permission");
-            }
+            throw new SecurityException("UID " + uid + " has no location permission");
         }
         // Check if Wifi Scan request is an operation allowed for this App.
         if (!isScanAllowedbyApps(pkgName, uid)) {
@@ -273,8 +274,7 @@
         return mAppOps.noteOp(op, uid, pkgName) == AppOpsManager.MODE_ALLOWED;
     }
 
-    private boolean isLocationModeEnabled(String pkgName) {
-        // Location mode check on applications that are later than version.
+    private boolean isLocationModeEnabled() {
         return (mSettingsStore.getLocationModeSetting(mContext)
                  != Settings.Secure.LOCATION_MODE_OFF);
     }
diff --git a/tests/wifitests/src/com/android/server/wifi/MockResources.java b/tests/wifitests/src/com/android/server/wifi/MockResources.java
index ab40c6f..1a061e2 100644
--- a/tests/wifitests/src/com/android/server/wifi/MockResources.java
+++ b/tests/wifitests/src/com/android/server/wifi/MockResources.java
@@ -23,11 +23,13 @@
     private HashMap<Integer, Boolean> mBooleanValues;
     private HashMap<Integer, Integer> mIntegerValues;
     private HashMap<Integer, String>  mStringValues;
+    private HashMap<Integer, CharSequence> mTextValues;
 
     public MockResources() {
         mBooleanValues = new HashMap<Integer, Boolean>();
         mIntegerValues = new HashMap<Integer, Integer>();
         mStringValues  = new HashMap<Integer, String>();
+        mTextValues    = new HashMap<Integer, CharSequence>();
     }
 
     @Override
@@ -57,6 +59,15 @@
         }
     }
 
+    @Override
+    public CharSequence getText(int id) {
+        if (mTextValues.containsKey(id))  {
+            return mTextValues.get(id);
+        } else {
+            return null;
+        }
+    }
+
     public void setBoolean(int id, boolean value) {
         mBooleanValues.put(id, value);
     }
@@ -68,4 +79,8 @@
     public void setString(int id, String value) {
         mStringValues.put(id, value);
     }
+
+    public void setText(int id, CharSequence value) {
+        mTextValues.put(id, value);
+    }
 }
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 0000000..1f0e44c
--- /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());
+    }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java b/tests/wifitests/src/com/android/server/wifi/SarManagerTest.java
index 163280a..369dcba 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 org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-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 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 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 @@
 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 @@
     private TestLooper mLooper;
     private MockResources mResources;
     private PhoneStateListener mPhoneStateListener;
+    private List<Sensor> 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();
 
@@ -105,15 +119,78 @@
     }
 
     /**
+     * Helper function to capture SarInfo object
+     */
+    private void captureSarInfo(WifiNative wifiNative) {
+        /* Capture the SensorEventListener */
+        ArgumentCaptor<SarInfo> 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<Sensor> constructor =
+                (Constructor<Sensor>) 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<Sensor>();
+        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,6 +199,17 @@
             verify(mTelephonyManager).listen(phoneStateListenerCaptor.capture(),
                     eq(PhoneStateListener.LISTEN_CALL_STATE));
             mPhoneStateListener = phoneStateListenerCaptor.getValue();
+            assertNotNull(mPhoneStateListener);
+        }
+
+        if (isSarSensorEnabled) {
+            /* Capture the SensorEventListener */
+            ArgumentCaptor<SensorEventListener> sensorEventListenerCaptor =
+                    ArgumentCaptor.forClass(SensorEventListener.class);
+            verify(mSensorManager).registerListener(sensorEventListenerCaptor.capture(),
+                    any(Sensor.class), anyInt());
+            mSensorEventListener = sensorEventListenerCaptor.getValue();
+            assertNotNull(mSensorEventListener);
         }
 
         /* Enable logs from SarManager */
@@ -129,12 +217,70 @@
     }
 
     /**
+     * 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<PhoneStateListener> 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<SensorEvent> constructor =
+                (Constructor<SensorEvent>) 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 @@
      */
     @Test
     public void testSarMgr_disabledTxPowerScenario_registerPhone() throws Exception {
-        createSarManager(false);
+        createSarManager(false, false);
         verify(mTelephonyManager, never()).listen(any(), anyInt());
     }
 
@@ -153,27 +299,24 @@
      * 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,70 @@
      * 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);
+
+        /* Disable WiFi State */
+        mSarMgr.setClientWifiState(WifiManager.WIFI_STATE_DISABLED);
+        inOrder.verify(mWifiNative, never()).selectTxPowerScenario(any(SarInfo.class));
     }
 
     /**
      * 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 +398,634 @@
         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 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
+     */
+    @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);
+    }
+
+    /**
+     * 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 41362ee..8c24445 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 @@
     @Mock WifiMonitor mWifiMonitor;
     @Mock ScanRequestProxy mScanRequestProxy;
     @Mock WakeupController mWakeupController;
+    @Mock SarManager mSarManager;
 
     final ArgumentCaptor<WifiNative.InterfaceCallback> mInterfaceCallbackCaptor =
             ArgumentCaptor.forClass(WifiNative.InterfaceCallback.class);
@@ -86,7 +87,7 @@
 
     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 @@
         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 @@
         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 0b93784..6a2c212 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 @@
     @Mock FrameworkFacade mFrameworkFacade;
     @Mock WifiApConfigStore mWifiApConfigStore;
     @Mock WifiMetrics mWifiMetrics;
+    @Mock SarManager mSarManager;
     final ArgumentCaptor<WifiNative.InterfaceCallback> mWifiNativeInterfaceCallbackCaptor =
             ArgumentCaptor.forClass(WifiNative.InterfaceCallback.class);
     final ArgumentCaptor<WifiNative.SoftApListener> mSoftApListenerCaptor =
@@ -133,7 +134,8 @@
                                                            mCallback,
                                                            mWifiApConfigStore,
                                                            config,
-                                                           mWifiMetrics);
+                                                           mWifiMetrics,
+                                                           mSarManager);
         mLooper.dispatchAll();
 
         return newSoftApManager;
@@ -205,7 +207,8 @@
                                                            mCallback,
                                                            mWifiApConfigStore,
                                                            nullApConfig,
-                                                           mWifiMetrics);
+                                                           mWifiMetrics,
+                                                           mSarManager);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
@@ -247,7 +250,8 @@
                                                            mCallback,
                                                            mWifiApConfigStore,
                                                            nullApConfig,
-                                                           mWifiMetrics);
+                                                           mWifiMetrics,
+                                                           mSarManager);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
@@ -288,7 +292,8 @@
                                                            mCallback,
                                                            mWifiApConfigStore,
                                                            nullApConfig,
-                                                           mWifiMetrics);
+                                                           mWifiMetrics,
+                                                           mSarManager);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
@@ -329,7 +334,8 @@
                                                            mCallback,
                                                            mWifiApConfigStore,
                                                            softApConfig,
-                                                           mWifiMetrics);
+                                                           mWifiMetrics,
+                                                           mSarManager);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
@@ -371,7 +377,8 @@
                                                            mCallback,
                                                            mWifiApConfigStore,
                                                            softApConfig,
-                                                           mWifiMetrics);
+                                                           mWifiMetrics,
+                                                           mSarManager);
         mLooper.dispatchAll();
         newSoftApManager.start();
         mLooper.dispatchAll();
@@ -408,7 +415,8 @@
                                                            mCallback,
                                                            mWifiApConfigStore,
                                                            softApModeConfig,
-                                                           mWifiMetrics);
+                                                           mWifiMetrics,
+                                                           mSarManager);
 
         mLooper.dispatchAll();
         newSoftApManager.start();
@@ -429,6 +437,7 @@
         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 @@
                 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 @@
         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<Intent> capturedIntents = intentCaptor.getAllValues();
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
index 1672dca..88312ce 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
@@ -19,25 +19,38 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.os.Build;
+import android.os.test.TestLooper;
 import android.support.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
 import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Random;
 
 /**
@@ -54,19 +67,34 @@
     private static final String TEST_CONFIGURED_AP_SSID = "ConfiguredAP";
     private static final String TEST_DEFAULT_HOTSPOT_SSID = "TestShare";
     private static final String TEST_DEFAULT_HOTSPOT_PSK = "TestPassword";
+    private static final String TEST_APCONFIG_CHANGE_NOTIFICATION_TITLE = "Notification title";
+    private static final String TEST_APCONFIG_CHANGE_NOTIFICATION_SUMMARY = "Notification summary";
+    private static final String TEST_APCONFIG_CHANGE_NOTIFICATION_DETAILED =
+            "Notification detailed";
     private static final int RAND_SSID_INT_MIN = 1000;
     private static final int RAND_SSID_INT_MAX = 9999;
     private static final String TEST_CHAR_SET_AS_STRING = "abcdefghijklmnopqrstuvwxyz0123456789";
 
-    @Mock Context mContext;
-    @Mock BackupManagerProxy mBackupManagerProxy;
-    File mApConfigFile;
-    Random mRandom;
-    MockResources mResources;
+    @Mock private Context mContext;
+    private TestLooper mLooper;
+    @Mock private BackupManagerProxy mBackupManagerProxy;
+    @Mock private FrameworkFacade mFrameworkFacade;
+    private File mApConfigFile;
+    private Random mRandom;
+    private MockResources mResources;
+    @Mock private ApplicationInfo mMockApplInfo;
+    private BroadcastReceiver mBroadcastReceiver;
+    @Mock private NotificationManager mNotificationManager;
+    private ArrayList<Integer> mKnownGood2GChannelList;
 
     @Before
     public void setUp() throws Exception {
+        mLooper = new TestLooper();
         MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE))
+                .thenReturn(mNotificationManager);
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.P;
+        when(mContext.getApplicationInfo()).thenReturn(mMockApplInfo);
 
         /* Create a temporary file for AP config file storage. */
         mApConfigFile = File.createTempFile(TEST_AP_CONFIG_FILE_PREFIX, "");
@@ -74,15 +102,24 @@
         /* Setup expectations for Resources to return some default settings. */
         mResources = new MockResources();
         mResources.setString(R.string.config_wifi_framework_sap_2G_channel_list,
-                            TEST_DEFAULT_2G_CHANNEL_LIST);
+                             TEST_DEFAULT_2G_CHANNEL_LIST);
         mResources.setString(R.string.wifi_tether_configure_ssid_default,
-                            TEST_DEFAULT_AP_SSID);
+                             TEST_DEFAULT_AP_SSID);
         mResources.setString(R.string.wifi_localhotspot_configure_ssid_default,
-                            TEST_DEFAULT_HOTSPOT_SSID);
+                             TEST_DEFAULT_HOTSPOT_SSID);
         /* Default to device that does not require ap band conversion */
         mResources.setBoolean(R.bool.config_wifi_convert_apband_5ghz_to_any, false);
+        mResources.setText(R.string.wifi_softap_config_change,
+                           TEST_APCONFIG_CHANGE_NOTIFICATION_TITLE);
+        mResources.setText(R.string.wifi_softap_config_change_summary,
+                           TEST_APCONFIG_CHANGE_NOTIFICATION_SUMMARY);
+        mResources.setText(R.string.wifi_softap_config_change_detailed,
+                           TEST_APCONFIG_CHANGE_NOTIFICATION_DETAILED);
         when(mContext.getResources()).thenReturn(mResources);
 
+        // build the known good 2G channel list: TEST_DEFAULT_2G_CHANNEL_LIST
+        mKnownGood2GChannelList = new ArrayList(Arrays.asList(1, 2, 3, 4, 5, 6));
+
         mRandom = new Random();
     }
 
@@ -93,6 +130,22 @@
     }
 
     /**
+     * Helper method to create and verify actions for the ApConfigStore used in the following tests.
+     */
+    private WifiApConfigStore createWifiApConfigStore() {
+        WifiApConfigStore store = new WifiApConfigStore(
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
+
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+        mBroadcastReceiver = broadcastReceiverCaptor.getValue();
+
+        return store;
+    }
+
+    /**
      * Generate a WifiConfiguration based on the specified parameters.
      */
     private WifiConfiguration setupApConfig(
@@ -149,7 +202,8 @@
     @Test
     public void initWithDefaultConfiguration() throws Exception {
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
     }
 
@@ -167,7 +221,8 @@
                 40                 /* AP channel */);
         writeApConfigFile(expectedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(expectedConfig, store.getApConfiguration());
     }
 
@@ -187,7 +242,8 @@
                 40                 /* AP channel */);
         writeApConfigFile(expectedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(expectedConfig, store.getApConfiguration());
 
         store.setApConfiguration(null);
@@ -202,7 +258,8 @@
     public void updateApConfiguration() throws Exception {
         /* Initialize WifiApConfigStore with default configuration. */
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
 
         /* Update with a valid configuration. */
@@ -226,7 +283,8 @@
     public void convertSingleModeDeviceAnyTo5Ghz() throws Exception {
         /* Initialize WifiApConfigStore with default configuration. */
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
 
         /* Update with a valid configuration. */
@@ -257,7 +315,8 @@
     public void singleModeDevice5GhzNotConverted() throws Exception {
         /* Initialize WifiApConfigStore with default configuration. */
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
 
         /* Update with a valid configuration. */
@@ -282,7 +341,8 @@
 
         /* Initialize WifiApConfigStore with default configuration. */
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
 
         /* Update with a valid configuration. */
@@ -315,7 +375,8 @@
 
         /* Initialize WifiApConfigStore with default configuration. */
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
 
         /* Update with a valid configuration. */
@@ -353,7 +414,8 @@
 
         writeApConfigFile(persistedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(expectedConfig, store.getApConfiguration());
         verify(mBackupManagerProxy).notifyDataChanged();
     }
@@ -374,7 +436,8 @@
 
         writeApConfigFile(persistedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(persistedConfig, store.getApConfiguration());
         verify(mBackupManagerProxy, never()).notifyDataChanged();
     }
@@ -403,7 +466,8 @@
 
         writeApConfigFile(persistedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(expectedConfig, store.getApConfiguration());
         verify(mBackupManagerProxy).notifyDataChanged();
     }
@@ -426,7 +490,8 @@
 
         writeApConfigFile(persistedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(persistedConfig, store.getApConfiguration());
         verify(mBackupManagerProxy, never()).notifyDataChanged();
     }
@@ -437,7 +502,8 @@
     @Test
     public void getDefaultApConfigurationIsValid() {
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         WifiConfiguration config = store.getApConfiguration();
         assertTrue(WifiApConfigStore.validateApWifiConfiguration(config));
     }
@@ -597,4 +663,51 @@
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
     }
+
+    /**
+     * Verify the default 2GHz channel list is properly returned.
+     */
+    @Test
+    public void testDefault2GHzChannelListReturned() {
+        // first build known good list
+        WifiApConfigStore store = createWifiApConfigStore();
+        ArrayList<Integer> channels = store.getAllowed2GChannel();
+
+        assertEquals(mKnownGood2GChannelList.size(), channels.size());
+        for (int channel : channels) {
+            assertTrue(mKnownGood2GChannelList.contains(channel));
+        }
+    }
+
+    /**
+     * Verify a notification is posted when triggered when the ap config was converted.
+     */
+    @Test
+    public void testNotifyUserOfApBandConversion() throws Exception {
+        WifiApConfigStore store = createWifiApConfigStore();
+        store.notifyUserOfApBandConversion(TAG);
+        // verify the notification is posted
+        ArgumentCaptor<Notification> notificationCaptor =
+                ArgumentCaptor.forClass(Notification.class);
+        verify(mNotificationManager).notify(eq(SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED),
+                                            notificationCaptor.capture());
+        Notification notification = notificationCaptor.getValue();
+        assertEquals(TEST_APCONFIG_CHANGE_NOTIFICATION_TITLE,
+                     notification.extras.getCharSequence(Notification.EXTRA_TITLE));
+        assertEquals(TEST_APCONFIG_CHANGE_NOTIFICATION_DETAILED,
+                     notification.extras.getCharSequence(Notification.EXTRA_BIG_TEXT));
+        assertEquals(TEST_APCONFIG_CHANGE_NOTIFICATION_SUMMARY,
+                     notification.extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT));
+    }
+
+    /**
+     * Verify the posted notification is cleared when the user interacts with it.
+     */
+    @Test
+    public void testNotificationClearedWhenContentIsTapped() throws Exception {
+        WifiApConfigStore store = createWifiApConfigStore();
+        Intent intent = new Intent(WifiApConfigStore.ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT);
+        mBroadcastReceiver.onReceive(mContext, intent);
+        verify(mNotificationManager).cancel(eq(SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java b/tests/wifitests/src/com/android/server/wifi/WifiNativeTest.java
index 31e7e55..fbdc8f5 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 @@
         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/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
index 6a24816..f04f531 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
@@ -2838,4 +2838,31 @@
         mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
         verifyNoMoreInteractions(mWifiCountryCode);
     }
+
+    /**
+     * Verify calls to notify users of a softap config change check the NETWORK_SETTINGS permission.
+     */
+    @Test
+    public void testNotifyUserOfApBandConversionChecksNetworkSettingsPermission() {
+        mWifiServiceImpl.notifyUserOfApBandConversion(TEST_PACKAGE_NAME);
+        verify(mContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.NETWORK_SETTINGS),
+                eq("WifiService"));
+        verify(mWifiApConfigStore).notifyUserOfApBandConversion(eq(TEST_PACKAGE_NAME));
+    }
+
+    /**
+     * Verify calls to notify users do not trigger a notification when NETWORK_SETTINGS is not held
+     * by the caller.
+     */
+    @Test
+    public void testNotifyUserOfApBandConversionThrowsExceptionWithoutNetworkSettingsPermission() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                                                eq("WifiService"));
+        try {
+            mWifiServiceImpl.notifyUserOfApBandConversion(TEST_PACKAGE_NAME);
+            fail("Expected Security exception");
+        } catch (SecurityException e) { }
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java b/tests/wifitests/src/com/android/server/wifi/WifiVendorHalTest.java
index 0f8f56f..fc030b8 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.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 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 @@
     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,67 +2000,433 @@
     }
 
     /**
-     * Test the new selectTxPowerScenario HIDL method invocation. This should return failure if the
-     * HAL service is exposing the 1.0 interface.
+     * 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() throws RemoteException {
+    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(WifiNative.TX_POWER_SCENARIO_VOICE_CALL));
+        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(WifiNative.TX_POWER_SCENARIO_VOICE_CALL));
+        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 new resetTxPowerScenario HIDL method invocation. This should return failure if the
-     * HAL service is exposing the 1.0 interface.
+   /**
+     * Test the selectTxPowerScenario HIDL method invocation for 1.2 interface.
+     * This should return success.
      */
     @Test
-    public void testResetTxPowerScenario() throws RemoteException {
+    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(WifiNative.TX_POWER_SCENARIO_NORMAL));
+        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(WifiNative.TX_POWER_SCENARIO_NORMAL));
+        assertTrue(mWifiVendorHal.selectTxPowerScenario(sarInfo));
         verify(mIWifiChipV11).resetTxPowerScenario();
         verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt());
         mWifiVendorHal.stopVendorHal();
     }
 
     /**
-     * Test the new selectTxPowerScenario HIDL method invocation with a bad scenario index.
+     * 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 testInvalidSelectTxPowerScenario() throws RemoteException {
+    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());
-        assertFalse(mWifiVendorHal.selectTxPowerScenario(-6));
-        verify(mIWifiChipV11, never()).selectTxPowerScenario(anyInt());
-        verify(mIWifiChipV11, never()).resetTxPowerScenario();
+
+        // 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();
     }
 
diff --git a/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
index 1e7bbd2..956598e 100644
--- a/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/util/WifiPermissionsUtilTest.java
@@ -158,6 +158,7 @@
 
     /**
      * Test case setting: Package is valid
+     *                    Location mode is enabled
      *                    Caller can read peers mac address
      *                    This App has permission to request WIFI_SCAN
      *                    User is current
@@ -171,6 +172,7 @@
         mPermissionsList.put(mMacAddressPermission, mUid);
         mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
         mCurrentUser = UserHandle.USER_CURRENT_OR_SELF;
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
                 mMockContext, mMockWifiSettingsStore, mMockUserManager, mWifiInjector);
@@ -179,6 +181,7 @@
 
     /**
      * Test case setting: Package is valid
+     *                    Location mode is enabled
      *                    Caller can read peers mac address
      *                    This App has permission to request WIFI_SCAN
      *                    User profile is current
@@ -192,6 +195,7 @@
         mPermissionsList.put(mMacAddressPermission, mUid);
         mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
         mMockUserInfo.id = mCallingUser;
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
                 mMockContext, mMockWifiSettingsStore, mMockUserManager, mWifiInjector);
@@ -220,6 +224,7 @@
 
     /**
      * Test case setting: Package is valid
+     *                    Location mode is enabled
      *                    Caller can read peers mac address
      *                    This App has permission to request WIFI_SCAN
      *                    User or profile is not current but the uid has
@@ -234,6 +239,7 @@
         mPermissionsList.put(mMacAddressPermission, mUid);
         mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
         mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
         setupTestCase();
         WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
                 mMockContext, mMockWifiSettingsStore, mMockUserManager, mWifiInjector);
@@ -372,15 +378,14 @@
      *                    Location Mode Disabled
      *                    Caller has location permisson
      *                    Caller has CHANGE_WIFI_STATE
-     * Validate no Exceptions are thrown
+     * Validate SecurityException is thrown
      * - Doesn't have Peer Mac Address read permission
      * - Uid is not an active network scorer
-     * - Location Mode is enabled but the uid
-     * - doesn't have Coarse Location Access
-     * - which implies scan result access
+     * - Location Mode is disabled
+     * - which implies no scan result access
      */
     @Test
-    public void testenforceCanAccessScanResults_LocationModeDisabledHasChangeWifiState()
+    public void testEnforceCannotAccessScanResults_LocationModeDisabledHasChangeWifiState()
             throws Exception {
         mThrowSecurityException = false;
         mUid = MANAGED_PROFILE_UID;
@@ -397,8 +402,8 @@
                 mMockContext, mMockWifiSettingsStore, mMockUserManager, mWifiInjector);
         try {
             codeUnderTest.enforceCanAccessScanResults(TEST_PACKAGE_NAME, mUid);
+            fail("Expected SecurityException is not thrown");
         } catch (SecurityException e) {
-            throw e;
         }
     }
 
@@ -407,15 +412,15 @@
      *                    Location Mode Disabled
      *                    Caller has location permisson
      *                    Caller has ACCESS_WIFI_STATE
-     * Validate no Exceptions are thrown
+     * Validate Exception is thrown
      * - Doesn't have Peer Mac Address read permission
      * - Uid is not an active network scorer
-     * - Location Mode is enabled but the uid
+     * - Location Mode is disabled
      * - doesn't have Coarse Location Access
-     * - which implies scan result access
+     * - which implies no scan result access
      */
     @Test
-    public void testenforceCanAccessScanResults_LocationModeDisabledHasAccessWifiState()
+    public void testEnforceCannotAccessScanResults_LocationModeDisabledHasAccessWifiState()
             throws Exception {
         mThrowSecurityException = false;
         mUid = MANAGED_PROFILE_UID;
@@ -432,12 +437,148 @@
                 mMockContext, mMockWifiSettingsStore, mMockUserManager, mWifiInjector);
         try {
             codeUnderTest.enforceCanAccessScanResults(TEST_PACKAGE_NAME, mUid);
+            fail("Expected SecurityException is not thrown");
         } catch (SecurityException e) {
-            throw e;
         }
     }
 
     /**
+     * Test case setting: Package is valid
+     *                    Location Mode Disabled
+     *                    Caller has location permisson
+     *                    Caller does not have NETWORK_SETTINGS
+     * Validate Exception is thrown
+     * - Doesn't have Peer Mac Address read permission
+     * - Uid is not an active network scorer
+     * - Location Mode is disabled
+     * - doesn't have Coarse Location Access
+     * - which implies no scan result access
+     */
+    @Test
+    public void testEnforceCannotAccessScanResults_LocationModeDisabledHasNoNetworkSettings()
+            throws Exception {
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mPermissionsList.put(mMacAddressPermission, mUid);
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_OFF;
+
+        setupTestCase();
+        when(mMockPermissionsWrapper.getUidPermission(
+                Manifest.permission.NETWORK_SETTINGS, MANAGED_PROFILE_UID))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mWifiInjector);
+        try {
+            codeUnderTest.enforceCanAccessScanResults(TEST_PACKAGE_NAME, mUid);
+            fail("Expected SecurityException is not thrown");
+        } catch (SecurityException e) {
+        }
+    }
+
+    /**
+     * Test case setting: Package is valid
+     *                    Location Mode Disabled
+     *                    Caller has location permisson
+     *                    Caller has NETWORK_SETTINGS
+     * Validate Exception is thrown
+     * - Doesn't have Peer Mac Address read permission
+     * - Uid is not an active network scorer
+     * - Location Mode is disabled
+     * - doesn't have Coarse Location Access
+     * - which implies no scan result access
+     */
+    @Test
+    public void testEnforceCanAccessScanResults_LocationModeDisabledHasNetworkSettings()
+            throws Exception {
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mPermissionsList.put(mMacAddressPermission, mUid);
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_OFF;
+
+        setupTestCase();
+        when(mMockPermissionsWrapper.getUidPermission(
+                Manifest.permission.NETWORK_SETTINGS, MANAGED_PROFILE_UID))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mWifiInjector);
+        codeUnderTest.enforceCanAccessScanResults(TEST_PACKAGE_NAME, mUid);
+    }
+
+    /**
+     * Test case setting: Package is valid
+     *                    Location Mode Disabled
+     *                    Caller has location permisson
+     *                    Caller does not have NETWORK_SETUP_WIZARD
+     * Validate Exception is thrown
+     * - Doesn't have Peer Mac Address read permission
+     * - Uid is not an active network scorer
+     * - Location Mode is disabled
+     * - doesn't have Coarse Location Access
+     * - which implies no scan result access
+     */
+    @Test
+    public void testEnforceCannotAccessScanResults_LocationModeDisabledHasNoNetworkSetupWizard()
+            throws Exception {
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mPermissionsList.put(mMacAddressPermission, mUid);
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_OFF;
+
+        setupTestCase();
+        when(mMockPermissionsWrapper.getUidPermission(
+                android.Manifest.permission.NETWORK_SETUP_WIZARD, MANAGED_PROFILE_UID))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mWifiInjector);
+        try {
+            codeUnderTest.enforceCanAccessScanResults(TEST_PACKAGE_NAME, mUid);
+            fail("Expected SecurityException is not thrown");
+        } catch (SecurityException e) {
+        }
+    }
+
+    /**
+     * Test case setting: Package is valid
+     *                    Location Mode Disabled
+     *                    Caller has location permisson
+     *                    Caller has NETWORK_SETUP_WIZARD
+     * Validate Exception is thrown
+     * - Doesn't have Peer Mac Address read permission
+     * - Uid is not an active network scorer
+     * - Location Mode is disabled
+     * - doesn't have Coarse Location Access
+     * - which implies no scan result access
+     */
+    @Test
+    public void testEnforceCanAccessScanResults_LocationModeDisabledHasNetworkSetupWizard()
+            throws Exception {
+        mThrowSecurityException = false;
+        mUid = MANAGED_PROFILE_UID;
+        mPermissionsList.put(mMacAddressPermission, mUid);
+        mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+        mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+        mLocationModeSetting = Settings.Secure.LOCATION_MODE_OFF;
+
+        setupTestCase();
+        when(mMockPermissionsWrapper.getUidPermission(
+                android.Manifest.permission.NETWORK_SETUP_WIZARD, MANAGED_PROFILE_UID))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        WifiPermissionsUtil codeUnderTest = new WifiPermissionsUtil(mMockPermissionsWrapper,
+                mMockContext, mMockWifiSettingsStore, mMockUserManager, mWifiInjector);
+        codeUnderTest.enforceCanAccessScanResults(TEST_PACKAGE_NAME, mUid);
+    }
+
+    /**
      * Test case setting: Invalid Package
      * Expect a securityException
      */